Posts tagged ‘iphone programming’

ARC (Automatic Reference Counting) Part II

เอาละครับ ในเรื่องของ ARC ตอนที่ 1 ผมอธิบายคร่าวๆไปแล้วว่ามันคืออะไร มีดียังไง ใน Entry นี้ ผมจะลงลึกรายละเอียดของ ARC มากขึ้น เพื่อรีดเค้นประสิทธิภาพของ ARC ให้มันดียิ่งขึ้นไปอีก ใครอยากอ่านตอนเก่าอยู่ ดูที่นี่ได้เลยครับ

Part I : https://onoaonoa.wordpress.com/2011/10/15/arc-automatic-reference-counting-part-i/

เอาละครับ เริ่มกันเลย

ก่อนหน้านี้ ผมเคยเปรียบเทียบการใช้ ARC กับการลดหุ่น ซึ่งใช้การควบคุมอาหารเป็นหลัก ถ้าเรากินอาหารแต่พออิ่มไม่กินมากเกินไป ไม่กินเป็นบุฟเฟต์ เราก็ควรจะมีหุ่นสมส่วนไม่อ้วนไม่ผอม ซึ่งก็เหมือนกับการใช้ ARC แบบ default คือระบุเป็น strong type ทั้งหมด แน่นอนว่าโปรแกรมเราทำงานปกติ และโอกาสเกิด memory leak ก็ไม่น่าจะมี เพราะ ARC จะจัดการให้เราตามหลัก ถูกมั้ยครับ?

งั้น ลองคิดดูว่า เรากินแต่พออิ่มเหมือนเดิม ข้าวจานเดียว น้ำแก้วเดียว ผลไม้อีกจานนึงต่อมื้อ แต่เรากิน ข้าวขาหมู+เป๊ปซี่+ทุเรียน คุณยังคิดว่าเราจะผอมอยู่มั้ย? ไม่แน่นอน อาหารแคลลอรี่สูงเหล่านี้ไม่ช่วยให้คุณผอมได้แม้จะกินแค่พออิ่ม …ARC ก็เช่นกันครับ การที่เราจัดการตัวแปรแบบ strong ทั้งหมด  นั่นก็ทำให้ใน App เราใช้ memory เยอะในช่วงเวลา runtime เพราะค่าทุกค่าถูกเก็บไว้ตลอดและจะถูกปล่อยคืนเมื่อ App ปิดตัวลง นี่เป็นสาเหตุที่เราต้องจัดการ ARC แม้ว่ามันจะบอกว่าเป็น Automatic อยู่ก็ตาม

type ของ ARC หลักๆ คือ strong กับ weak ถ้าเรากำหนด type strong ให้กับตัวแปร สบายใจได้ว่า value ของตัวแปรนี้ จะไม่หายไปหรือเปลี่ยนไปเป็นอย่างอื่นแน่นอน ตราบที่ไม่เราไม่เป็นคนไปเปลี่ยนเอง แต่ข้อเสียก็คือ เปลือง memory เพราะจำค่านั้นๆตลอดเวลาที่แอพทำงาน และยิ่งกว่านั้นคือ “ทำให้เกิด memory leak ได้ด้วย”

…เป็นไปได้อย่างไร? แต่เป็นไปได้ครับ เช่นในกรณีที่มีการ reference หากับแบบ parent กับ child อย่าง delegate หรือ datasource ใน UITableView เป็นต้น ลองดูเคสนี้ครับ สมมุติว่า myTableView ระบุให้ ARC จัดการ “delegate, datasource แบบ strong ทั้งหมด” ลองนับดูนะครับว่า Object “MyArcLeak” ที่เราสร้างขึ้น จะมี retain count เท่าไหร่เมื่อโค้ดผ่าน -viewDidLoad ไป โดยเริ่มต้นจาก 1 จากการ allocate object


@implementation MyArcLeak
- (void)viewDidLoad {
[super viewDidLoad];
self.myTableView.delegate = self;     // retain count + อีก 1
self.myTableView.datasource = self;   // retain count + อีก 1
}

จะเห็นว่า object ที่เราสร้างขึ้นมี retain count กลายเป็น 3!! เมื่อ arc release object ที่เราสร้างขึ้น มันก็ลบ retain count ออกไป 1 เท่านั้น เห็นมั้ยครับว่าแม้จะใช้ ARC ก็ยัง Memory leak ได้อยู่ นี่เป็นเหตุผลที่เราต้องรู้จักการใช้ ARC ทั้ง strong และ weak

strong อธิบายไปแล้ว แล้ว weak เป็นอย่างไร? ผมบอกไปแล้วว่า weak type เป็น type ที่ค่าจะถูกเปลี่ยนเป็น nil ได้ตลอดเวลา ซึ่งตลอดเวลาที่ว่าคือ เมื่อ value ที่ weak variable ชี้ไปหาถูกเปลี่ยนไปเมื่อไหร่ weak variable จะถูกเปลี่ยนเป็น nil ทันที ลองดูโค้ดตามนี้ครับ


NSString *a = @"strong";
__strong NSString *b = a;
a = @"A is Strong";

ผลที่ได้คือ a => A is Strong และ b => strong

แต่ถ้า b เป็น weak type ผลที่ได้จะต่างออกไปดังนี้


NSString *a = @"strong";
__weak NSString *b = a;
a = @"A is still Strong";

ผลที่ได้จะเป็น a => A is still Strong และ b => nil

นี่แหละครับ ความต่างของ strong และ weak และจะต้องใช้แบบไหนยังไง สรุปแบบสั้นๆง่ายๆครับ

  1. ถ้าเป็นพวกที่เมื่อก่อน property assign เมื่อไหร่ ก็ใช้ __weak ครับ เช่น delegate, datasource
  2. ถ้าเป็นตัวแปรที่ reference จาก xib หรืออะไรก็ตามที่เป็น IBOutlet ให้ตั้งเป็น weak เสมอ เพราะ Object ถูก retain ด้วย IB อยู่แล้ว

หลักๆสองข้อเท่านี้แหละครับ หลักของการใช้ weak แบบทั่วไป แต่คราวนี้ยังมีิอีกอย่างที่ยังคงต้องพูดถึง ปัญหาอีกอย่างนึงที่ยังคงอยู่คือเรื่อง iOS4 กับ ARC ครับ

จริงอยู่ว่า ARC support iOS ตั้งแต่เวอร์ชั่น 4 ขึ้นไป เท่าที่ apple support iOS version จนถึงปัจจุบันนี้ แต่ข่าวร้ายคือ iOS4 ไม่ support ARC type แบบ weak ครับ วิธีการแก้ไขคือ เราต้องใช้ ARC type แบบ __unsafe_unretain แทน เท่านี้แหละครับใช้ได้แล้ว ดูเหมือนไม่มีปัญหา แต่จริงๆก็มีครับ unsafe_unretain ต่างกับ weak แค่ตรงที่ เวลาที่ value ต้นทางเปลี่ยน มันไม่เป็น nil ให้เหมือน weak ครับ

เพราะเหตุนี้เองมันถึงได้ unsafe ที่ว่าไม่ปลอดภัย เพราะการที่ pointer ชี้ไปตำแหน่งที่ไม่ถูกต้องตามค่าที่คาดการณ์ไว้นั้น อาจจะทำให้เกิดบั๊กที่เรียกว่า Dangling pointer และทำให้ application crash ตามมาใน worst case การแก้ไขคือเราก็ต้องตรวจสอบให้ดีๆ อาจใช้ tool ใน instrument หรือเปิด feature zombie เพื่อหาข้อผิดพลาดได้ครับ

เอาละครับ สำหรับเรื่อง ARC ตอนที่ 2 ผมก็ได้อธิบายเกี่ยวกับรายละเอียดของการใช้ ARC ทั้ง 3 type ที่จำเป็นและอาจจะได้เจอบ่อยๆ ได้แก่ strong, weak และ unsafe_unretain ซึ่งต้องเจอแน่ๆ 80% ในการพัฒนา iOS application ณ เวลานี้

จริงๆแล้ว ยังมีรายละเอียดอีกเกี่ยวกับ ARC เรื่อง เช่น bridge, การใช้ arc กับ block variable แต่ขอเก็บไว้ Entry หน้าแล้วกันครับ กลัวว่าจะหนักเกินไป หวังว่าจะสนุกกับการสร้างหุ่นสวยๆให้กับแอพของคุณด้วย ARC นะครับ …Happy  Coding ครับ ;)

พฤศจิกายน 12, 2011 at 6:22 am 4 ของความคิดเห็น

Magic of NSLog

การ Log ที่ดี ก็มีผลให้การทำงานเราสะดวกรวดเร็วขึ้นนะครับ แน่นอน การใส่สัญลักษณ์ไว้ใน Log ก็เป็นวิธีการแบบลูกทุ่งๆอย่างนึงที่ใช้ง่ายและได้ผล แต่เยอะๆเข้าแทนที่จะช่วยให้เร็วขึ้นมันจะเป็นตรงกันข้าม เพราะลายตากับ Log ไปหมด หรือจะให้ระบุรายละเอียดกันทุกบรรทัด ก็เสียเวลาเกินไป ใน Entry นี้ ผมเอา Trick การ Log ให้มีประสิทธิภาพมากขึ้นมาฝากครับ

Trick #1 : รู้รึเปล่า ว่าการทำงานพร้อมกับการ Log ค่าไปด้วย ทำให้เป็นการลดประสิทธิภาพการทำงานของ App ลงไปเยอะเลย ก่อน  Submit app ขึ้น App store เราควรจะ remove log ของเราออกไปด้วย …จริงอยู่ว่า App ทั่วไปอาจไม่มีผลต่อประสิทธิภาพโดยนัย แต่ถ้าเป็นเกม …มีนัยครับ ดังนั้น เราจะสร้าง Log ของเราเองขึ้นมา ที่ให้ Log ค่า ตอนที่ XCode ตั้งค่าเป็น Debug mode เท่านั้น ใส่โค้ดพวกนี้ในไฟล์ xxx_Prefix.pch ของโปรเจค เพื่อให้มีผลกับทั้งโปรเจคครับ

#ifdef DEBUG
# define JELog(fmt,...) NSLog(@"%@",[NSString stringWithFormat:(fmt), ##__VA_ARGS__]);
#else
# define JELog(...)
#endif

อธิบายโค้ดดังกล่าวได้ว่า เราสร้าง JELog ขึ้นมาเพื่อให้มันทำงานเหมือน NSLog เลย แต่จะพิมพ์ออกทางหน้าจอเมื่อ Preprocessor Macro ถูกเซตคำว่า DEBUG เอาไว้ แต่ถ้าไม่ได้เซตก็ไม่ต้องทำอะไร

ส่วนการเซตค่า  Preprocessor Macro ไปเลือกที่ Project->Edit Project Settings เลือก Debug ในช่อง Configuration และใส่ค่า DEBUG (หรือคำอื่นๆที่นิยามไว้) ใน Preprocessor Macro เซคชั่น GCC 4.2 – Preprocessing

…แค่นี้แหละครับ เสร็จแล้ว คราวนี้เรา Build ในโหมด Release ก็ไม่มี Log มาบั่นทอนประสิทธิภาพ App เราแล้ว

Trick #2: จะดีแค่ไหน ถ้าการ Log ทำให้เรารู้ได้ว่า มันโดน Log มาจาก Function อะไร และบรรทัดไหน เราสามารถเพิ่มมันได้เอง ในรูปแบบที่เราต้องการ และไม่ต้องเหนื่อยมาใส่แบบลูกทุ่งทีละบรรทัดทีละ Log ด้วย ทำได้ง่ายๆแบบนี้ครับ

#define JELog(fmt,...) NSLog((@"%s-%d:" fmt),__PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);

เพียงเท่านี้ เราก็ Log สิ่งที่เราต้องการพร้อมชื่อ Function และบรรทัดนั้นได้ง่ายๆแล้วครับ นอกจากนี้ยังมี __FILE__ เพื่อ log ชื่อไฟล์ชื่อนั้นอีก แต่ผมไม่ใส่เพราะผมไม่ใช้ อิอิ

ครับ สำหรับ Entry นี้ก็คงกล่าวถึงแค่นี้ ส่วนถ้าใครยังไม่สะใจอยากแต่งอยากเติมอีก ก็เข้าไปดูในเครดิตครับ มีคนทำไว้หลายแบบเหมือนกันสำหรับผม แค่นี้ก็สบายแล้ว หุหุ …ว่าแล้วก็จบเท่านี้ดีกว่า Happy Logging นะครับ ;)

Credits:

@memogames
iphoneprogrammingfordummies.blogspot.com
Stack Overflow : NSLog Tip & Trick

มีนาคม 24, 2011 at 5:23 pm ใส่ความเห็น


del.icio.us For iPhone dev

Post Calendar

พฤษภาคม 2024
จ. อ. พ. พฤ. ศ. ส. อา.
 12345
6789101112
13141516171819
20212223242526
2728293031