Chapter 20: Debugging and Unit Testing (Part2)

Unit testing with XCTest

ถ้าหากหาทางป้องกันและการหลีกเลี่ยง bug ไว้ตั้งแต่เนิ่นๆ เวลาที่เสียไปกับการ debug ก็จะน้อยลง โปรแกรมมีประสิทธิภาพมากชึ้น หนึ่งในวิธีการป้องกันการเกิด bug คือการใช้ unit testing การทำ unit testing นั้นมีจุดประสงค์เพื่อใช้ทดสอบโค้ดบางส่วนของโปรแกรม เพื่อหาว่าโค้ดส่วนนั้นมีผลลัพธ์ตามที่คาดหวังหรือไม่ ซึ่งต้องอาศัยการเขียนโค้ดขึ้นมาทดสอบหรือที่เรียกว่า test cases ในปัจจุบันมีเฟรมเวิร์คสำหรับ unit testing ให้ใช้มากมายเช่น SenTestingKit , OCTest แต่หลังจาก XCode 5 ได้ออกมา ก็ได้เพิ่ม framework ใหม่เข้ามาสำหรับ unit testing ชื่อว่า XCTest ซึ่งเป็นเฟรมเวิร์คหลักของ Objective-C ในหัวข้อนี้เราจะเขียนโปรแกรมง่ายๆเพื่อใช้สำหรับหัดเขียน unit test โดยมีโค้ดดังต่อไปนี้

Program 20.4
Student.h

Student.m

เมื่อเขียนคลาสทั้งสองเสร็จแล้ว สิ่งที่จะทำต่อไปคือ unit test เพราะต้องการจะทดสอบก่อนว่าคลาสนี้ทำงานถูกต้องตามที่ต้องการ การใช้ unit test สามารถทำได้ด้วยเลือกเมนู File > New > Target … และเลือก Objective-C Unit Testing Bundle ดังรูป แต่ถ้าหากโปรเจคที่สร้างขึ้นมาเป็น iOS App หรือ Mac App จะมี unit test target มาให้เรียบร้อยแล้ว

unit1

เมื่อกด Next จะพบหน้าต่างให้ตั้งชื่อของ Target จากนั้นให้กด Finish ก็เป็นอันเสร็จสิ้นการสร้าง Unit Testing Target

unit2

ถ้าทุกอย่างเรียบร้อยก็จะเห็น folder ใหม่และไฟล์ต่างๆ พร้อมกับ target ใหม่ที่ได้เพิ่มเข้ามา

tartget

ในรูปจะเห็นว่ามีไฟล์ Program_20_4_Tests.m เมื่อเปิดไฟล์ Program_20_4_Tests.m ก็จะเจอโค้ดดังนี้

Program_20_4_Tests.m

ไฟล์ Program_20_4_Tests.m นี้ที่ใช้สำหรับเขียน test case จากโค้ดจะเห็นว่าเทสเคสนี้เป็นซับคลาสของ XCTestCase และมีเมธอดทั้งหมด 3 เมธอดด้วยกันคือ

setUp เมื่อสั่งให้ run test case เมธอดนี้จะถูกเรียกก่อนเสมอ ซึ่งมักใช้สำหรับการกำหนดค่าต่างก่อนจะเริ่มการทดสอบ
tearDown เป็นเมธอดที่จะเรียกหลังจาก test case ทั้งหมดได้ทำงานเสร็จสิ้น
testExample เรียกเมธอดนี้ว่าเป็น test case ซึ่งเมธอดนี้แสดงตัวอย่างวิธีการเขียน test case และเราสามารถประกาศ test case เองได้ โดยการขึ้นต้นชื่อเมธอดด้วย “test” เช่น testSum , testConnection , testValidEmail เป็นต้น และไม่ต้องกำหนดพารามิเตอร์และไม่ส่งค่าใดๆกลับไป

นอกจากไฟล์ Program_20_4_Tests.m แล้ว ยังสามารถเพิ่มไฟล์สำหรับเขียน Test Case โดยเลือกได้จากเมนู File > New > File .. และเลือก Objective-C Test case class

Run test cases

การสั่งให้ชุดทดสอบหรือ test case ทำงานนั้นสามารถทำได้โดยไปที่ Unit Test Navigation ดังรูป และคลิกที่รูปสามเหลี่ยมด้านท้ายของ test case ที่ต้องการ

run

จากนั้น test case จะเริ่มการทดสอบและ XCode จะแสดงผลลัพธ์จากการทดสอบดังนี้

test_fail

เทสเคสที่ได้ทำงานเสร็จสิ้นไป ได้แจ้งว่า testExample นั้นไม่ผ่านการทดสอบ พร้อมกับแสดงข้อความ failed – No implementation for “-[Program_20_4_Tests testExample]” ดังที่เห็นในรูป ไม่ต้องแปลกใจว่าทำไมชุดทดสอบนี้ไม่ผ่าน นั่นเป็นเพราะว่า testExample เรียกใช้ฟังก์ชัน XCFail ซึ่งจะให้ผลลัพธ์ว่าไม่ผ่านหรือเป็น fail เสมอ และในส่วนของ console จะแสดงผล ดังนี้

Test Suite ‘All tests’ started at 2014-02-18 11:02:37 +0000
Test Suite ‘Program 20.4 Tests.xctest’ started at 2014-02-18 11:02:37 +0000
Test Suite ‘Program_20_4_Tests’ started at 2014-02-18 11:02:37 +0000
Test Case ‘-[Program_20_4_Tests testExample]’ started.
/Program_20_4_Tests.m:31: error: -[Program_20_4_Tests testExample] : failed – No implementation for “-[Program_20_4_Tests testExample]”
Test Case ‘-[Program_20_4_Tests testExample]’ failed (0.000 seconds).
Test Suite ‘Program_20_4_Tests’ finished at 2014-02-18 11:02:37 +0000.
Executed 1 test, with 1 failure (0 unexpected) in 0.000 (0.000) seconds
Test Suite ‘Program 20.4 Tests.xctest’ finished at 2014-02-18 11:02:37 +0000.
Executed 1 test, with 1 failure (0 unexpected) in 0.000 (0.000) seconds
Test Suite ‘All tests’ finished at 2014-02-18 11:02:37 +0000.
Executed 1 test, with 1 failure (0 unexpected) in 0.000 (0.003) seconds

รายละเอียดของการทดสอบได้บอก จำนวนของ test case ที่ใช้ในการทดสอบ รวมถึงเวลาที่ใช้สำหรับการทดสอบแต่ละ test case และถ้าหากเทสเคสใด ไม่ผ่านก็ทดสอบก็แจ้ง fail เช่นเดียวกัน

Assert

ในการเขียน test case นั้นจะใช้ชุดคำสั่งที่เรียกว่า XCTAssert เช่น XCTFail ใน testExample คำสั่งเหล่านี้จะเป็นการทดสอบเงื่อนไขตามที่กำหนดเช่น XCTAssertFalse ใช้ทดสอบว่าเงื่อนไขนั้นต้องเป็นเท็จ หรือ XCTAssertEqual ใช้ทดสอบว่าค่าทั้งสองมีค่าเท่ากัน คำสั่ง XCTAssert ที่ใช้บ่อยๆ มีตามตารางดังต่อไปนี้

table

Your First Unit Test

ก่อนที่จะลงมือเขียน unit test แรก ให้ลบเมธอดทั้งสามที่มีอยู่แล้วออกไปเสียก่อน จากนั้นเขียน test case ใหม่ชื่อ testCreateStudent และมีโค้ดดังนี้

Program_20_4_Tests.m

ชุดทดสอบแรกที่เขียนคือ testCreateStudent จากโค้ดได้เรียกใช้ฟังก์ชั่น XCTAssertEqual เพื่อทดสอบว่าค่าของ score จะมีค่าเท่ากับ 60 ตามที่ได้กำหนดหรือไม่ ต่อมาใช้ XCTAssertEqualObject เพ่ือทดสอบว่าชื่อของนักเรียนที่ได้กำหนดให้กับอ็อบเจ็ก student ต้องเป็นค่าเดียวกัน เมื่อให้ test case ทำการทดสอบ ก็จะมักจะเกิดความผิดพลาดดังนี้

build error

สิ่งที่เกิดขึ้นคือโปรแกรมแจ้งเตือนว่ามีความผิดพลาด และความผิดพลาดนี้ไม่ใช่ข้อความแจ้งผลการทดสอบของ unit test เช่นที่ผ่านมา แต่กลับเป็นความผิดพลาดของการคอมไพล์ซึ่ง XCode ได้แจ้งว่า

Undefined symbols for architecture x86_64:
“_OBJC_CLASS_$_Student”, referenced from:
objc-class-ref in Program_20_4_Tests.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

หมายความว่า Program_20_4_Test ไม่รู้จัก symbols ที่ชื่อ _OBJC_CLASS_$_Student หรือพูดง่ายๆว่ามีการเรียกใช้คลาส Student จากไฟล์ Program_20_4_Test แต่กลับไม่เจอคลาส Student นั่นเอง สาเหตุที่ไม่เจอคลาส Student ก็เพราะว่าใน Unit Test Target ไม่ได้กำหนดให้คอมไพล์ไฟล์ Student เข้ามาใน target ด้วย จึงทำให้เกิดข้อผิดพลาด ดังนั้นสิ่งที่ต้องแก้ไขคือกำหนดให้  Unit Test Target นั้นคอมไพล์ไฟล์ Student ด้วย โดยการปรับที่ Build Phase และเพิ่มไฟล์ Student.m

setting

 

เมื่อทดสอบ test case อีกครั้งก็จะได้ผลลัพธ์ดังรูป

pass

สัญลักษณ์สีเขียวด้านหน้าของ test case ได้แสดงว่าการทดสอบผ่านเรียบร้อยดี ต่อไปจะเพิ่มคลาส Course เข้ามาในโปรแกรม ในขั้นตอนของการสร้างคลาสใหม่นี้ให้เลือกทั้งสอง Targets เพื่อจะได้ไม่เกิดปัญหาการคอมไพล์เหมือนที่ผ่านมา

target

คลาส Course ที่จะเขียนต่อไปนี้มีเมธอดให้นักเรียนลงทะเบียน , หาค่าเฉลี่ยคะแนนของนักเรียน , แสดงนักเรียนที่มีผลการเรียนดีที่สุด ,ค้นหานักเรียนจากชื่อได้, บอกจำนวนนักเรียนทั้งชั้น และสุดท้ายแสดงรายชื่อนักเรียนที่สอบผ่าน และมีข้อบังคับคือนักเรียนคนเดิมลงทะเบียนซ้ำไม่ได้, นักเรียนที่ผ่านสอบต้องมีคะแนนมากกว่าหรือเท่ากับ 50 คะแนน

Course.h

Course.m

ดูโค้ดผ่านๆ คลาสที่เขียนขึ้นมานี้น่าจะทำงานได้อย่างถูกต้อง แต่เพื่อความแน่ใจ ก็ต้องเขียนเทสเคสขึ้นมาทดสอบเมธอดต่างๆของคลาส Course ในครั้งนี้ให้สร้างไฟล์เทสเคสขึ้นมาใหม่ โดยเลือก template ของไฟล์เป็น Objective-C Test case class และตั้งชื่อว่า Course_Test จากนั้นเขียน test case ดังต่อไปนี้

Course_Test.m

เนื่องจากคลาส Course ต้องใช้ Student แทบจะทุกเมธอด ดังนั้นใน test case ที่เขียนขึ้นใหม่จึงได้ประกาศ Student เพื่อใช้ในการทดสอบทั้งหมด 5 อ็อบเจ็กด้วยกัน และเขียนส่วนของการสร้างอ็อบเจ็กไว้ที่เมธอด setUp

เทสเคสแรก testSearchStudent ใช้สำหรับทดสอบการหาอ็อบเจ็ก student จากชื่อที่กำหนด ถ้าหากไม่มีอ็อบเจ็กนั้นอยู่ในรายชื่อ สิ่งที่คาดหวังคือเมธอดต้องส่งค่า nil กลับมา

ส่วนเทสเคสที่สองใช้ทดสอบเมธอด registerStudent ที่มีข้อกำหนดว่า ห้ามนักเรียนคนเดิมลงทะเบียนซ้ำ ดังนั้นในเมื่อเพิ่มนักเรียนคนเดิมจำนวนนักเรียนทั้งหมดต้องเท่าเดิม ไม่เช่นกันก็ถือว่าไม่ผ่านการทดสอบ

เทสเคส testMaxScore และ testAverage ใช้สำหรับการทดสอบคะแนนมากที่สุด และหาคะแนนเฉลี่ยของนักเรียน

และสุดท้าย testPassedStudent ไว้ทดสอบผู้ที่มีคะแนนผ่านเกณฑ์ที่กำหนด เมื่อเขียนเทสเคสเสร็จเรียบร้อย จากนั้นให้ทำการทดสอบ test case ที่ได้เขียนไป ก็จะพบกับผลลัพธ์ดังนี้

Test Case ‘-[Course_Tests testAverage]’ started.
/Course_Tests.m:78: error: -[Course_Tests testAgerage] : (([course averageScore]) equal to (0)) failed: (“nan”) is not equal to (“0”) – average score fail

Test Case ‘-[Course_Tests testPassedStudent]’ started.
/Course_Tests.m:97: error: -[Course_Tests testPassedStudent] : (([passed count]) equal to (4)) failed: (“3”) is not equal to (“4”) – passedStudent fail

Test Case ‘-[Course_Tests testSearchStudent]’ started.
/Course_Tests.m:51: error: -[Course_Tests testSearchStudent] : ((studentB) == nil) failed: “Weerapong : 65” – search fail

ผลของการทดสอบมี test case ที่ไม่ผ่านถึง 3 เคสด้วย โดยเคสแรก testAverage ได้แจ้งว่าทดสอบไม่ผ่านเพราะเกิดค่า “nan” ค่านี้จะเกิดขึ้นได้ก็ต่อเมื่อหารด้วย 0 เมื่อย้อนกลับไปดูเมธอด averageScore จะเห็นว่ามีข้อผิดพลาดคือ ในกรณีที่ไม่มีนักเรียนอยู่เลยจำนวนนักเรียนจะเป็น 0 ดังนั้นโปรแกรมจะหารด้วย 0 นั่นเอง การแก้ปัญหานี้คือให้ส่งค่า 0 กลับเมื่อ studentList ไม่มีอ็อบเจ็ก Student อยู่เลย

เทสเคสต่อมาคือ testPassedStudent ซึ่งได้คาดหวังผลลัพธ์ว่าจำนวนนักเรียนที่ผ่านสอบควรจะเป็น 4 แต่ผลการทดสอบแจ้งว่าเป็น 3 เมื่อพิจาณาจากโค้ดของเมธอด passedStudent จะเห็นว่าเงื่อนไข score > 50 ที่ใช้ในการเปรียบเทียบนั้นผิด ทำให้นักเรียนที่มีคะแนน 50 สอบตก เงื่อนไขที่ถูกต้องคือ score >= 50
ส่วนเคสสุดท้าย testSearchStudent สิ่งที่ต้องการคือหานักเรียนชื่อ Weera โดยผลลัพธ์ที่คาดหวังคือต้องไม่เจอนักเรียนคนดังกล่าว แม้ว่าโปรแกรมจะไม่เจอ Weera แต่จากผลการทดสอบก็ยังพบข้อผิดพลาด เพราะเมธอดได้พบนักเรียนชื่อ Weerapong เมื่อกลับไปดูโค้ด studentWithName: ก็จะเห็นว่า predicate ที่ใช้คือ @”name contains %@” นั้นไม่ถูกต้อง เพราะเป็นการหาชื่อที่มีส่วนใดส่วนหนึ่งประกอบด้วยคำที่กำหนด ดังนั้นเมื่อค้นหา Weera ก็จะได้ Weerapong ด้วย เพราะ Weerapong มีคำว่า “Weera” อยู่นั่นเอง เราควารแก้ไขเงื่อนไขให้เป็น @”name contains %@” โปรแกรมก็จะทำงานได้อย่างถูกต้อง

จากผลของการทดสอบเห็นได้ว่า เมื่อดูผ่านๆแล้วคลาส Course น่าจะทำงานได้อย่างถูก แต่กลับมีข้อผิดพลาดในโปรแกรมถึง 3 แห่งด้วยกัน การใช้ Unit test ยังมีข้อดีอีกอย่างก็คือ เมื่อไหร่ก็ตามที่แก้ไขโค้ดของโปรแกรม เช่น แก้ไขการเปรียบเทียบในเมธอด maxScore ให้เป็น

เมื่อให้ชุดทดสอบได้ทำงานอีกครั้ง ก็จะพบว่าเมธอดนี้ไม่ผ่านการทดสอบ การใช้ Unit Test นั้นอาจจะดูเป็นเรื่องไม่จำเป็น เพราะต้องเขียนโค้ดที่ใช้ทดสอบมากมายหลายอย่างด้วยกัน และยังต้องคิด test case เพื่อใช้ในการทดสอบ แต่การใช้ Unit Test นั้นช่วยป้องกันปัญหาความผิดพลาดของโปรแกรมในระยะยาวได้เป็นอย่างดี โดยเฉพาะเมื่อต้องเขียนโปรแกรมขนาดใหญ่ และมีโปรแกรมเมอร์หลายคนในโปรเจค  การใช้ Unit Test จะเป็นสิ่งช่วยป้องกันความผิดพลาดในโปรแกรมที่จะเกิดขึ้นได้เป็นอย่างดี

Summary

 

สิ่งที่ได้เรียนรู้ในบทนี้ แม้ไม่เกี่ยวกับภาษา Objective-C โดยตรง แต่ก็เป็นประโยชน์สำหรับการเขียนโปรแกรมอย่างแน่นอน เพราะนอกจากความรู้ความเข้าใจเกี่ยวกับภาษาแล้ว ยังต้องมีทักษะอย่างอื่นเช่นการ debug หรือการใช้ unit testing หนังสือเล่มนี้ไม่อาจจะอธิบายการใช้งานต่างๆได้หมดเช่น การใช้ lldb command ซึ่งมีประโยชน์อย่างมากในการ debug สำหรับผู้ที่สนใจสามารถศึกษาเพิ่มเติมได้จากเอกสารของโครงการ lldb โดยตรง

It just beginning not the end

แม้ว่าหนังสือเล่มนี้ได้เดินทางมาถึงส่วนสุดท้ายแล้ว แต่นี่เป็นเพียงจุดเริ่มต้นของการเขียนโปรแกรมด้วยภาษา Objective-C หน้าที่ต่อไปของคุณคือ ค้นคว้าเพิ่มเติมด้วยตัวเอง เพราะยังมีอีกหลายเรื่องที่หนังสือเล่มนี้ไม่ได้อธิบายไว้ การหมั่นศึกษาเพิ่มเติมและลงมือเขียนโปรแกรมอย่างจริงจัง คือหนทางที่จะทำให้คุณมีทักษะการเขียนโปรแกรมเก่งขึ้น ผมได้พิสูจน์มันมาแล้ว และคุณก็สามารถทำได้เช่นเดียวกัน

สุดท้ายนี้มีคำพูดหนึ่งที่ สตีฟ จ็อบ ได้กล่าวตอนให้สุนทรพจน์แก่นักศึกษา Stanford University ที่จบใหม่ และผมก็จะขอนำคำพูดนั้นมาพูดกับคุณอีกครั้ง
“Stay foolish Stay hungry”

 

โหลด PDF ไปอ่านได้ครับ

ส่วน Source code ก็เช่นเดิม โหลดได้ที่ github

Chapter 20: Debugging and Unit Testing (Part1)

Debugging & Unit Testing

 

Bug คือข้อผิดพลาดที่ทำให้โปรแกรมทำงานไม่เป็นไปตามที่โปรแกรมเมอร์สิ่งกำหนด การเกิด bug อาจจะส่งผลกระทบต่อโปรแกรมเล็กน้อย และอาจทำให้เกิด error ในบางฟังก์ชัน หรือถ้าร้ายแรงมากก็ทำให้โปรแกรม crash และปิดตัวลง เคยมีคนได้กล่าวไว้ว่า “โปรแกรมที่ไม่มี bug คือโปรแกรมที่ยังไม่ได้เขียน” ไม่ว่าจะเขียนโปรแกรมง่ายหรือยาก ต่อให้เป็นโปรแกรมเมอร์เก่งแค่ไหนก็ต้องเขียนโปรแกรมผิดพลาดบ้าง ซึ่งถือเป็นเรื่องปกติธรรมดา เมื่อเกิด bug สิ่งที่โปรแกรมเมอร์ต้องทำก็คือการหาข้อผิดพลาดและแก้ไขหรือที่เรียกว่า Debug ทักษณะนี้จำเป็นอย่างยิ่งที่โปรแกรมเมอร์จะต้องเรียนรู้ และฝึกฝนให้ชำนาญ ยิ่งมีประสบการณ์ debug มากเท่าไหร่ ก็ยิ่งได้เปรียบมากเท่านั้น เพราะในบางครั้งเวลาที่ใช้สำหรับการ debug นั้นมากกว่าเวลาที่ใช้เขียนโปรแกรมเสียอีก เนื้อหาในบทนี้จะอธิบายถึงการใช้ debugger รวมถึงเทคนิควิธีการขั้นสูงที่ใช้ในการ debug เช่น exception breakpoint

LLDB

Debugger คือเครื่องมือที่ใช้สำหรับการ debug แต่เดิมทีนั้น debugger ที่ใช้ใน XCode คือ GNU Debugger หรือเรียกสั้นๆว่า GDB เป็นดีบั๊กเกอร์์มาตรฐานที่ใช้กันทั่วไปในระบบปฎิบัติการ linux , unix แต่ด้วยข้อจำกัดหลายๆอย่างของ gdb จึงได้เกิดการพัฒนาดีบั๊กเกอร์ใหม่ขึ้นมาภายใต้โครงการเดียวกันกับ llvm โดยมีชื่อว่า lldb และมากไปกว่านั้น lldb ไม่ได้เป็นเพียงแค่ดีบั๊กเกอร์ที่มีความสามารถเพิ่มมากขึ้น แต่ lldb ได้ถูกออกแบบมาให้เป็น framework โดยมี API ให้ใช้งานผ่านทางภาษา python ดีบั๊กเกอร์ lldb นี้ได้ถูกนำมาใช้งานร่วมกับ XCode ครั้งแรกใน XCode 4.3 โดยให้ผู้ใช้เลือกได้ว่าจะใช้ gdb หรือ lldb อย่างไรก็ตามตั้งแต่ XCode 5 เป็นต้นมา lldb ได้กลายเป็นดีบักเกอร์หลักของ XCode โดยทั่วๆไปการดีบั๊กแอปพิเคชั่นจะทำผ่านทาง user interface ของ XCode เช่น การวาง breakpoint อย่างไรก็ตามเราสามารถใช้ lldb ได้โดยตรงผ่านทาง debug console (หนังสือเล่มนี้ไม่ได้ครอบคลุมการใช้ lldb command)

Breakpoint

การดีบั๊กนั้นสามารถทำได้หลายวิธีหนึ่งในวิธีง่ายที่สุดคือการใช้ NSLog เพื่อแสดงค่าของตัวแปรที่ต้องการ หากโปรแกรมง่ายไม่ซับซ้อน ก็อาจจะใช้ NSLog ได้ แต่เมื่อโปรแกรมใหญ่และซับซ้อนขึ้น การใช้ NSLog อย่างเดียวนั้นไม่เพียงพอต่อการแก้ไข bug ที่เกิดขึ้นได้ การใช้ debugger เป็นทางออกที่ดีกว่าการใช้ NSLog เพราะดีบั๊กเกอร์สามารถวาง breakpoint เพื่อให้โปรแกรมหยุดทำงานชั่วขณะ จากนั้นก็จะสามารถดูค่าของตัวแปรต่างในขณะนั้นได้ทันที การกำหนด breakpoint นั้นสามารถทำได้ง่ายๆ ด้วยการคลิ๊กที่ด้านหน้าของบรรทัดที่ต้องการ จากนั้นก็จะเห็นสัญลักษณ์ ลูกศรสีฟ้าเข้ม

breakpoint_basic

หากต้องการจะลบ breakpoints ทำได้ด้วยการกดค้างที่เบรกพ้อนต์และลากทิ้ง หรือจะลบจาก Breakpoints Navigation ดังที่แสดงในรูป ได้เช่นเดียวกัน

breakpoint_nav

ในกรณีที่ไม่ต้องการให้เบรกพ้อนต์ทำงานชั่วคราว (disable) ก็ทำได้ด้วยการกดคลิ๊กที่ breakpoints ที่ต้องการอีกครั้งหนึ่ง จากนั้นสัญลักษณ์จะเปลี่ยนเป็นสีฟ้าอ่อน

disable_breakpoint

เมื่อสั่งให้โปรแกรมทำงาน โปรแกรมจะหยุดยังบรรทัด breakpoint ที่ได้กำหนดไว้ จากนั้นก็จะสามารถดูค่าตัวแปรต่างๆได้จาก debug area ดังที่แสดงในรูป

debug_consol

จากรูปข้างบน ข้อมูลที่ XCode แสดงจะประกอบไปด้วย

  • Call stack ข้อมูลในส่วนนี้จะแสดงเมธอดที่ทำงานในขณะนั้น และบอกถึงเทรดที่เมธอดทำงานอยู่ รวมถึงบอกว่าเมธอดนี้ถูกเรียกมาจากที่ใด เมื่อดูจากรูปจะเห็นว่าเมธอด +[Math isPrime:] คือเมธอด ณ จุดที่โปรแกรมทำงานกำลังทำงาน ซึ่งเรียกมาจาก main อีกทอดหนึ่ง และเมธอดนี้ทำงานที่ Thread 1 นั่นเอง
  • Variable ตรงส่วนนี้จะแสดงข้อมูลของตัวแปรรวมถึงพารามิเตอร์ที่เกี่ยวข้องกับเมธอดหรือฟังก์ชั่นที่ทำงานในขณะนั้น เรียกอีกอย่างว่า Frame จากตัวอย่างในรูปจะเห็นว่า frame ประกอบไปด้วย isPrime , num และ self เท่านั้นไม่ได้แสดงตัวแปรอื่นที่ไม่เกี่ยวข้องกับ frame นี้เลย (ในกรณีที่มี global variable ก็จะแสดงตัวแปรนั้นด้วย)
  • Debug console ไว้ใช้แสดงผลลัพธ์ต่างๆที่แจ้งโดย debugger และยังสามารถใช้พิมพ์คำสั่ง debugger command ผ่านทาง console นี้ได้โดยตรงอีกด้วย
  • Debug Navigation เมื่อโปรแกรมหยุดที่เบรกพ้อนต์ โปรแกรมเมอร์สามารถสั่งให้โปรแกรมทำงานต่อไปได้ด้วยการกดปุ่ม continue , step over , step into , step out
  1. continue เป็นคำสั่งให้โปรแกรมจะทำงานต่อไป ซึ่งจะหยุดอีกครั้งก็ต่อเมื่อเจอ breakpoint
  2. step over เมื่อใช้คำสั่งนี้โปรแกรมจะทำงานต่อไปอีก 1 คำสั่ง
  3. step into ใช้เพื่อเข้าไปดูการทำงานภายในของฟังก์ชั่นหรือเมธอด แต่ในกรณีที่ไม่มี source code โปรแกรมจะแสดงโค้ดด้วยภาษา assembly แทน
  4. step out ใช้เพื่อกลับไปยังเมธอดที่ถูกเรียกเข้ามาก่อนหน้านี้
  5. และสุดท้ายคือปุ่ม simulate location ไว้สำหรับจำลองตำแหน่ง GPRS ของ iOS Application

Exception Breakpoints

นอกจากการวาง breakpoint แบบปกติทั่วไป ที่ได้อธิบายไป ยังมีเบรกพ้อนต์แบบพิเศษที่เรียกว่า Exception Breakpoint ให้ลองพิจารณาโปรแกรมต่อไปนี้

เมื่อสั่งให้ทำงาน โปรแกรมจะเกิดข้อผิดพลาดขึ้นและปิดตัวลง เพราะอ็อบเจ็ก b มีค่าเป็น nil ทำให้เมธอด addObject: นั้นทำงานผิดพลาด และ debug console จะแจ้งข้อผิดพลาดดังนี้

*** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil’
*** First throw call stack:
(
0   CoreFoundation    0x00007fff84e9141c __exceptionPreprocess + 172
1   libobjc.A.dylib    0x00007fff84fe3e75 objc_exception_throw + 43
2   CoreFoundation    0x00007fff84d528b7 -[__NSArrayM insertObject:atIndex:] + 951
3   Program 20.1        0x0000000100000ec1 main + 177
4   libdyld.dylib    0x00007fff898985fd start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

ข้อความที่แจ้งมาคือ เกิดข้อผิดพลาดจากการเรียกเมธอด insertObject:atIndex: เนื่องจากอ็อบเจ็กเป็น nil (โค้ดโปรแกรมใช้เมธอด addObject: แต่เมธอดนี้จะเรียกใช้ insertObject:atIndex อีกทีดังนั้นจึงแจ้งข้อผิดพลาดที่เมธอดนี้แทน) และ XCode ก็ได้แสดงตำแหน่งของโค้ดที่เกิดข้อผิดพลาดขึ้น

bug1

การแก้ไข bug ในโปรแกรมนี้ทำงานได้ง่ายมากเพราะ XCode ได้หยุดโปรแกรมไว้ยังบรรทัดที่เกิดข้อผิดพลาด แต่ถ้าหากโปรแกรมมีซับซ้อนมากขึ้น เช่น โปรแกรมต่อไปนี้

Program 20.1
main.m

 

เมื่อให้สั่งให้โปรแกรมทำงาน debug console จะแสดงผลดังนี้

*** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil’
*** First throw call stack:
(
0   CoreFoundation    0x00007fff84e9141c __exceptionPreprocess + 172
1   libobjc.A.dylib    0x00007fff84fe3e75 objc_exception_throw + 43
2   CoreFoundation    0x00007fff84d528b7 -[__NSArrayM insertObject:atIndex:] + 951
3   Program 20.1        0x0000000100001cf5 __main_block_invoke13 + 101
4   Foundation        0x00007fff888b5055 -[NSBlockOperation main] + 75
5   Foundation        0x00007fff88894591 -[__NSOperationInternal _start:] + 631
6   Foundation        0x00007fff8889423b __NSOQSchedule_f + 64
7   libdispatch.dylib    0x00007fff86af22ad _dispatch_client_callout + 8
8   libdispatch.dylib    0x00007fff86af67ff _dispatch_async_redirect_invoke + 154
9   libdispatch.dylib    0x00007fff86af22ad _dispatch_client_callout + 8
10  libdispatch.dylib    0x00007fff86af409e _dispatch_root_queue_drain + 326
11  libdispatch.dylib    0x00007fff86af5193 _dispatch_worker_thread2 + 40
12  libsystem_pthread.dylib    0x00007fff84179ef8 _pthread_wqthread + 314
13  libsystem_pthread.dylib    0x00007fff8417cfb9 start_wqthread + 13
)
libc++abi.dylib: terminating with uncaught exception of type NSException

โปรแกรมได้แจ้งว่าเกิดข้อผิดพลาดที่เมธอด insertObject:atIndex: เช่นเดียวกันกับโปรแกรมที่แล้ว แต่เมื่อดูจาก debug area สิ่งที่ XCode ได้แสดงกลับแตกต่างจากไปโปรแกรมที่แล้ว

debug_area

จะเห็นว่า XCode ไม่ได้แจ้งบรรทัดที่เกิดข้อผิดพลาดเลยแต่กลับแสดงโค้ดด้วยภาษา assembly แทน นั่นเป็นเพราะว่าแท้ที่จริงแล้ว XCode จะหยุดเมื่อได้รับข้อผิดพลาดหรือ catch exception ไม่ใช่หยุดที่จุดเกิด exception (throw exception) เมื่อกลับไปพิจารณาโค้ดใหม่จะเห็นว่าในบล็อก one นั้น อาเรย์ได้รับเอาตัวแปรที่มีค่าเป็น nil จึงทำให้โปรแกรมนี้เกิดข้อผิดพลาดนั่นเอง โปรแกรมตัวอย่างแก้ไขง่ายเพราะมีโค้ดเพียงไม่กี่บรรทัด แต่เมื่อเขียนโปรแกรมเป็นร้อย เป็นพันบรรทัดขึ้นไป จะรู้ได้อย่างไรว่าผิดพลาดที่ส่วนใดของโปรแกรม ? วิธีการแก้ปัญหาก็คือใช้ Exception Breakpoint เข้ามาช่วย ซึ่งเบรกพ้อนต์ชนิดนี้จะให้โปรแกรมหยุดทำงานเมื่อเกิด exception วิธีการสร้าง exception breakpoint สามารถทำได้โดยเลือกเมนู Debug > Breakpoints > Create Exception Breakpoints หลังจากนั้น ก็จะเห็น exception breakpoint ที่ได้สร้างขึ้นโผล่เข้ามายัง Breakpoint Navigation ดังรูป

add_exception

จากรูปมี breakpoint ในโปรแกรมทั้งหมด 2 breakpoint คือในบรรทัดที่ 17 และ exception breakpoint (สัญลักษณ์ Ex) เมื่อสั่งให้โปรแกรมทำงานอีกครั้ง XCode จะหยุดโปรแกรมยังบรรทัดที่เกิดข้อผิดพลาด เท่านี้ก็แก้ไขปัญาที่เกิดขึ้นได้แล้ว

Symbolic Breakpoints

นอกจาก breakpoint แบบปกติและ exception breakpoint ที่ได้อธิบายไป ยังเหลือหนึ่งอย่างนั่นคือ symbolic breakpoint ซึ่งเป็นการกำหนด breakpoint โดยใช้ symbolic การกำหนด symbolic มีด้วยกันทั้งหมด 3 วิธีคือกำหนดด้วย เมธอด เช่น load วิธีที่สองใช้เมธอดโดยระบุเจาะจงคลาส เช่น [UIView load] และสุดท้ายคือ ฟังก์ชัน เราจะเขียนโปรแกรมขึ้นมาทดลองใช้งาน symbolic breakpoint ง่ายๆดังนี้

Program 20.2
Car.h

Car.m

main.m

เมื่อเขียนโค้ดเสร็จแล้วก็ให้สร้าง symbolic breakpoints โดยเลือกที่เมนู Debug > Breakpoints > Create Symbolic Breakpoints หลังจากนั้นก็จะเจอหน้าต่างดังนี้

car_symbolic

ในโปรแกรมนี้จะกำหนด symbolic breakpoint ด้วยเมธอด info ของคลาส Car ดังนั้นในช่อง Symbol จึงกำหนดเป็น info หรือจะใช้แบบระบุเจาะจงคลาส -[Car info] ก็ได้เช่นเดียวกัน แต่ต้องใช้รูปแบบที่กำหนดคือ -[class method] หรือในกรณีที่เป็นคลาสเมธอดให้ใช้ +[class method] เมื่อให้สั่งให้ทำงาน โปรแกรมก็จะหยุดที่เมธอด info ดังรูป

sym_brek

และถ้าหากสั่งให้ continue เพื่อให้โปรแกรมทำงานต่อไป ก็จะพบว่าโปรแกรมหยุดที่ info อีกรอบ เพราะโปรแกรมเรียกใช้เมธอด info นี้ถึงสามครั้งด้วยกัน การใช้เบรกพ้อนต์ลักษณะนี้มีประโยชน์ในหลายๆกรณี เช่น เมื่อเขียนโปรแกรม iOS หากต้องการวางเบรกพ้อนต์ที่เมธอด viewWillAppear ทุก UIViewController ถ้าโปรแกรมมี view controller จำนวนน้อยคงไม่ใช่ปัญหา แต่ถ้ามี UIViewController จำนวนมากก็จะกลายเป็นปัญหาทันทีเพราะต้องวาง breakpoint หลายที่มาก แต่ในทางกลับกันถ้าเปลี่ยนมาใช้ symbolic breakpoint และกำหนดให้หยุดที่เมธอด viewWillAppear ก็จะใช้เพียงแค่เบรกพ้อนต์เดียวเท่านั้น

Setting Breakpoint Actions and Options

เมื่อวาง breakpoint โปรแกรมจะทำงานไปจนกระทั่งถึงจุด breakpoint ทุกครั้งๆ เพื่อรอคำสั่งต่อไป ซึ่งในบางครั้งอาจจะกลายเป็นปัญหา ให้ลองพิจาณาโค้ดโปรแกรมสั้นๆ ต่อไปนี้

Program 20.3
main.m

จากโปรแกรมจะพบว่าค่า x นั้นจะเพิ่มขึ้นไปเรื่อยๆจนถึงจุดหนึ่งค่า x จะกลายเป็นติดลบ เพราะเกิดปัญหา integer overflow หรือค่า x มากเกินกว่าที่ integer จะรองรับได้ หากต้องการจะหาคำตอบว่าลูปทำงานไปกี่รอบค่า x ถึงจะกลายเป็นค่าติดลบ การหาคำตอบนี้คงไม่ได้ยากเย็นอะไรนัก แค่วาง breakpoint และสั่งให้ continue ให้โปรแกรมทำงานไปเรื่อยๆจนกระทั่งค่า x เกิดปัญหา overflow ดังเช่นในรูป ก็จะรู้ว่าลูปทำงานไปกี่รอบ

break_con

แต่อย่างไรก็ตามจากโปรแกรมดังกล่าว กว่าที่จะรู้ว่าเมื่อไหร่ค่า x จะกลายเป็นติดลบ ต้องสั่งให้ continue ถึง 29 ครั้งด้วยกัน คงไม่ใช่เรื่องสนุกที่ต้องนั่งกด continue ไปเรื่อยๆ จนกว่าจะเจอสิ่งที่ต้องการ โชคดีที่สามารถกำหนดให้ breakpoint ให้หยุดตามเงื่อนไขที่ต้องการได้หรือเรียกว่า breakpoint condition
การกำหนด condition ของ breakpoint ทำได้ด้วยการคลิกขวายัง breakpoint ที่ต้องการ แล้วเลือก Edit Breakpoint หลังจากนั้นก็จะพบกับช่องสำหรับกำหนด condition ของ breakpoint ดังรูป

set_break_condition

เมื่อเกิด integer overflow ค่า x จะกลายเป็นติดลบ ดังนั้นเงื่อนของ break point ที่ต้องการให้โปรแกรมหยุด ก็คือ x < 0 หากสั่งให้โปรแกรมทำงานอีกครั้งจะเห็นว่า โปรแกรมจะหยุดที่ i เป็น 29 โดยที่ไม่ต้องกด continue ให้เสียเวลา

set_condition

นอกจากการใช้เงื่อนไขเปรียบเทียบค่าโดยตรง ยังสามารถใช้เมธอดเป็นเงื่อนไขได้เช่นเดียวกัน โดยให้เพิ่ม return type นำหน้าเมธอด เช่น (bool)[myObject isEqualTo:otherObject]  หรือ (int)[array count] == 5

นอกจากการใช้ condition แล้วยังกำหนด action ให้กับ breakpoint ได้อีกด้วย เช่นการเรียกใช้ AppleScript , Log Message หรือสั่งให้เล่นเสียงต่างๆ

action

เราจะลองใช้ Action ง่ายๆ กันสักหนึ่งอย่างคือเรียกใช้ shell command เพื่อให้ Mac OSX พูดบอกเราว่า “Breakpoint has been reached” ด้วยคำสั่ง  say และกำหนด argument เป็น “A breakpoint has been reached”  ดังที่แสดงในรูป

say

เมื่อโปรแกรมหยุดที่ breakpoint ดังกล่าวก็จะได้ยินเสียงพูดตามประโยคที่ได้กำหนดไว้ นั่นเอง

Release and Debug

หลังจากที่ได้แก้ไขโปรแกรมจนแน่ใจว่า ไม่มีบั๊กแล้ว ก็ถึงเวลาที่จะนำโปรแกรมไปใช้งานจริง แต่ก่อนที่จะนำไปให้ผู้ใช้ได้ใช้งานจริงๆนั้น จำเป็นต้องปรับค่าของ compiler ให้คอมไพล์โปรแกรมให้มีประสิทธิภาพมากที่สุด ทำงานได้เร็วที่สุด และมีขนาดเล็กที่สุด นั่นก็คือ optimize โปรแกรมและนำ debug symbol หรือโค้ดส่วนที่ใช้ในการ debug ออกไป และเรียกว่า release นั่นเอง การปรับโปรแกรมให้เป็น release สามารถทำได้โดยการปรับ scheme ที่เมนู Product > Scheme > Edit Scheme จากนั้นก็จะปรากฎหน้าต่างการปรับแต่ง scheme

scheme

ตอนนี้ Scheme Run นั้นมี Build Configuration เป็น Debug เราอาจจะสร้าง Scheme ขึ้นมาใหม่และเลือก Build Configuration ให้เป็น release ก็ได้ หรือจะปรับโดยตรงที่ scheme ที่ต้องการก็ได้เช่นกัน นอกจากการปรับ scheme แล้วยังสามารถที่จะสร้าง Application แบบ release ได้ด้วยการเลือกเมนู Product > Archive จากนั้น XCode จะคอมไพล์โปรแกรมเป็น release และจะเก็บไฟล์ไว้ให้เรา

archive

จากนั้นก็เลือก Distribute เมื่อต้องการจะแจกจ่ายโปรแกรมให้ผู้ใช้งาน

 

Review หูฟัง – JBL – J22i

เนื่องจากหูฟัง iPhone ที่มาพร้อมกับเครื่องได้หายไป ผมเลยซื้อหูฟังมาใหม่ เลยนำมา review ซะเลย

JBL – J22i หูฟังยี่ห้อ JBL นี้เป็นหูฟังแบบ in-ear ถ้าเป็นหูฟังแบบที่แถมมากับ iPhone เรียกว่าแบบ ear-bud แต่ถ้าเป็นแบบที่ครอบหัวเรียกว่า head phone ซึ่งข้อดีของหูฟังแบบ in-ear ก็คือ หูฟังแบบนี้จะเสียงดีกว่าแบบ ear bud และลดเสียงรบกวนรอบด้านได้ดีกว่า ถึงแม้ไม่มีระบบ noise canceling ก็ตาม (ตัดเสียงรบกวน)  ส่วนข้อเสียก็คือราคาแพงกว่า ( ที่ผมซื้อมานี้ราคา 400 SEK คิดเป็นเงินไทยก็ประมาณ 2000 บาท )

_DSC0077

เหตุผลหลักที่ตัดสินใจซื้อหูฟังรุ่นนี้ก็คือ มีไมโครโฟนในตัว มี remote control และสามารถใช้ได้กับ iPhone , iPad , iPod ได้อย่างไม่มีปัญหา มีหูฟังรุ่นที่คล้ายกันอีกรุ่นก็คือ J22 ซึ่งจะไม่มีไมโครโฟนและ remote เหมือนอย่างรุ่นที่มี i ต่อท้าย ถ้าเป็นรุ่นสำหรับ android ก็จะมี a ต่อท้าย

_DSC0078

เมื่อแกะกล่องออกมาก็จะเห็น ซองหนังสำหรับใส่หูฟังแถมมาด้วย

_DSC0080

ซองหนัง ออกแบบมาได้พอดีสายของหูฟัง ในส่วนสายของหูฟังนั้นจะมีลักษณะแบน ไม่เป็นเส้นกลมๆ เหมือนหูฟังทั่วๆไป ข้อดีของสายแบบนี้คือเวลาเก็บสาย จะไม่ค่อยพันกัน เหมือนอย่างหูฟังที่แถมมากับ iPhone

_DSC0085

ตัวของ remote control มีด้วยกันทั้งหมด 3 ปุ่มคือ ลดเสียง เพิ่มเสียง และปุ่มสีแดง เป็นปุ่มกดไว้สำหรับกด ตอบรับเมื่อมีสายเรียกเข้า หรือสั่งให้หยุดเล่นเพลง

_DSC0092

สีของหูฟังมีสีดำ ส่วนบริเวณด้านในจะมีสีส้ม อย่างที่เห็นในรูป พร้อมกับแถม ยางครอบอีก 2 ขนาด เพื่อให้ได้เลือกเปลี่ยนใช้ตามชอบ

_DSC0088

 

เสียง

หลังจากแกะกล่องก็มาทดสอบเสียงกันเลย เท่าที่ผมฟังหูฟังนี้ มีเสียงเบสโอเค เสียงกลางชัดเจน แต่ผมรู้สึกเหมือนเสียงสูงจะยังออกมาไม่ดีนัก จากการฟังครั้งแรก ก็ต่อด้วยการ burn ไป 10 ชม. (การ burn คือให้เปิดเพลงให้หูฟังได้ทำงานไปสักระยะ เพื่อให้เสียงและชิ้นส่วนต่างๆได้เข้าที่เข้าทาง ) และลองฟังดูใหม่ พบว่า เสียงโทนกลาง และสูงชัดเจนมากขึ้น นอกจากนี้ผมก็ลองเปิดเสียงให้มันดังสุดๆ เพื่อทดสอบ หูฟังยังทำได้ดี เสียงไม่แตกเหมือนหูฟังราคาถูก และเสียงเบสก็ยังออกมาแน่นชัดเจนเช่นเดิม ไม่กระพือ

การโทรคุย

การใช้ไมโครโฟน ไม่มีปัญหาอะไร เท่าที่ถามจากเพื่อนที่ผมคุยด้วย ก็บอกว่าเสียงได้ยินชัดเจนดี แต่เนื่องจากเป็นหูฟังแบบ in ear ซึ่งมันก็จะมีข้อเสียอย่างหนึ่งคือ เวลาพูดคุยมันจะลักษณะเหมือนการเอามือปิดหูแล้วพูด นั่นคือจะได้ยินเสียงของตัวเองแบบอื้ออึงในหูหน่อยๆ

สรุป

เป็นหูฟังที่มีเสียงเบสพอดี เสียงกลางใช้ได้ และเสียงสูงโอเค  ถ้าชอบเพลงแนวสไตล์ pop ฟังเสียงกีต้าร์ หูฟังนี้เหมาะมาก แต่ถ้าฟังสาย jazz หรือชอบเสียงใสๆ หวานๆ ผมคิดว่า มันไม่ตอบโจทย์เท่าไหร่ จริงอยู่ว่าเสียงสูงและกลางมันชัด  แต่มันไม่หวานนุ่มนวล  ส่วนสายฟังแนว metal และอะไรหนักๆ ก็ใช้ได้ แต่มันก็ยังขาดในส่วนของเบสในโทนที่ลึกๆ เสียงเหยียบกระเดื่องอาจจะไม่สะใจขาโหด  และถ้าใครไม่ชอบหูฟังแบบ in ear ก็คงไม่เหมาะอีกนั่นเหละ ส่วนเรื่องฟังชั่นการใช้งานกับ iPhone ทำได้ดี ไม่มีปัญหาอะไรในการใช้งาน ส่วนงานประกอบและวัสดุต่างๆทำมาได้เยี่ยม

Objective-C Programming Chapter 19 (Part2)

NSOperation

NSOperation นี้เปรียบเสมือนหนึ่งหน่วยของการทำงาน แต่เนื่องจากคลาส NSOperation เป็น abstract class ดังนั้นจึงไม่สามารถใช้คลาสนี้ได้โดยตรง ต้องประกาศซับคลาสขึ้นมาใช้งาน หรืออาจจะใช้ซับคลาสของระบบ เช่น NSBlockOperation หรือ NSInvocationOperation ได้เช่นเดียวกัน จุดประสงค์หลักของคลาสนี้คือเตรียมฟังชั่นการทำงานบางอย่างที่จำเป็นของ task ไว้ให้ เช่น กำหนดลำดับความสำคัญ และบอกสถานะการทำงาน ซึ่งมีด้วยกันทั้งหมด 4 สถานะคือ ready, executing, finish และ cancel  อ็อบเจ็กของคลาสนี้มีลักษณะเป็นแบบ single-shot นั่นคือ ทำงานได้ครั้งเดียว เมื่อให้ทำงานไปแล้วโอเปอร์เรชันจะไม่สามารถนำกลับมาใช้ได้อีก

NSInvocationOperation

NSInvocationOperation เป็นซับคลาสของ NSOperation มีจุดประสงค์เพื่อใช้กับเมธอดของอ็อบเจ็ก ดังนั้นการประกาศอ็อบเจ็กของคลาสนี้ต้องกำหนด selector พร้อมกับอ็อบเจ็กที่ต้องการให้ทำงาน เช่นตัวอย่าง โปรแกรมต่อไปนี้

Program 19.5

NetworkController.h

NetworkController.m

จากโค้ดตัวอย่าง เมธอด downloadWithURL: นั้นทำหน้าที่เพียงแค่สร้าง operation object และได้กำหนด selector เป็น performDownload: ซึ่งเป็นเมธอดที่ใช้สำหรับดาวน์โหลดข้อมูล เมื่อโอเปอร์เรชันทำงานก็จะเรียกใช้เมธอดนี้นั่นเอง Continue reading Objective-C Programming Chapter 19 (Part2)

Objective-C Programming Chapter 19 (Part1)

Grand Central Dispatch

 

ทุกวันนี้เทคโนโลยีของ CPU ได้ก้าวหน้าไปมาก จากแต่เดิมที่มุ่งเน้นการเพิ่มความเร็วของสัญญานาฬิกา ได้เปลี่ยนมาเป็นเพิ่มจำนวนแกนของ CPU หรือที่เรียกว่า multicore แต่การที่จะใช้ซีพียูให้เกิดประสิทธิภาพสูงที่สุดนั้น โปรแกรมจำเป็นต้องสามารถทำงานได้หลายอย่างพร้อมกันได้ ในอดีตการที่โปรแกรมที่ใช้ซีพียูแบบ multiple core นั้นจะมีการสร้าง thread หลายๆตัวเพื่อให้แต่ละ thread ได้ใช้แกนของซีพียูได้หลายแกนพร้อมกัน แต่อย่างไรก็ตามปัญหาที่ตามมาก็คือการเพิ่มจำนวน thread นั้นในบางครั้ง ไม่ได้ช่วยให้เกิดประสิทธิภาพมากขึ้น เพราะเมื่อถึงจุดหนึ่งจำนวนของเทรดจะมีผลต่อประสิทธิภาพของโปรแกรม ดังนั้นโปรแกรมเมอร์จึงต้องหาจุดที่จำนวนเทรดเหมาะสมพอดีกับซีพียู ไม่มากไปหรือไม่น้อยไป และถึงแม้ว่าจะหาจำนวนของเทรดที่พอดีได้แล้วก็ได้ตาม ก็ยังเป็นเรื่องยากที่จะต้องจัดการแต่ละ thread ไม่ให้ขัดขวางการทำงานซึ่งกันและกัน เพื่อแก้ปัญหาต่างๆเหล่านี้ Apple จึงได้สร้างเทคโนโลยีใหม่ขึ้นมาที่เรียกว่า Grand Central Dispatch ( GCD ) ซึ่งเป็นเทคโนโลยีใหม่ ที่ได้นำมาใช้กับระบบปฎิบัติการ Mac OS X และ iOS เพื่อจัดการปัญหาเหล่านี้
เทคนิคการทำงานเบื้องหลังของ GDC คือ Asynchronous Function ที่ได้อธิบายไปแล้วในบทก่อนว่า เป็นการทำงานแบบอนุญาติให้ฟังชั่นอื่นเริ่มทำงานได้ทันที โดยต้องไม่ต้องรอให้ฟังชั่นที่กำลังทำงานในปัจจุบันเสร็จสิ้นเสียก่อน แม้ว่าเทคนิคการทำงานแบบนี้มีมานานแล้ว แต่โปรแกรมเมอร์ต้องเป็นคนเขียนโค้ด asynchronous function เองรวมไปถึงสร้าง thread ที่ใช้ทำงานร่วมกับฟังชั่นด้วยตัวเองทั้งหมด แต่หลังจากที่มีเทคโนโลยี GCD เข้ามา งานของโปรแกรมเมอร์ก็ลดน้อยลงเพราะไม่ต้องเขียนโครงสร้างการทำงานแบบ asynchronous function และโค้ดที่ใช้สร้างและจัดการ thread สิ่งที่โปรแกรมเมอร์ต้องทำจะเหลือแต่เพียงแค่การเขียนโค้ดของงานที่ต้องทำจริงๆ จากนั้นหลังจากนั้นก็ส่งต่อเข้าไปยัง dispatch queue ที่เหลือจะเป็นหน้าที่ของ GCD ที่จะจัดการสร้าง thread รวมไปถึงการบริหารเวลาการทำงานให้กับ CPU แต่ละ core เอง การใช้ GCD สามารถทำได้สองทางด้วยกันคือใช้ Dispatch Queues ซึ่งเป็น C API และทางที่สองใช้ NSOperationQueue และในบทนี้จะได้ทำความเข้าใจการใช้งานทั้งสองแบบ

Dispatch Queues

ดิสแพทช์คิวคือเครื่องมือใช้ในการจัดการงานหรือที่เรียกว่า task (ต่อไปนี้จะใช้คำว่า task) อะไรควรทำก่อนหรือหลัง เช่น โหลดข้อมูลจากเซิฟเวอร์ , อ่านข้อมูลจากฮาร์ดดิส , คำนวนค่าต่างๆ การประกาศ task สามารถเขียนด้วยฟังชั่น หรือจะใช้บล็อกก็ได้เช่นเดียวกัน การจัดลำดับการทำงานของ dispatch queues เป็นแบบ FIFO (First In – First Out) หาก task ใดเข้ามาในคิวก่อนก็จะได้รับสิทธิให้ทำงานก่อน ดิสแพทช์คิวได้แบ่งออกเป็นทั้งหมด 3 แบบด้วยกันคือ

Serial (private dispatch queue) เหมาะกับงานที่ต้องใช้ทรัพยากรร่วมกัน (shared resource) เนื่องจากดิสแพทช์คิวแบบนี้ อนุญาติให้ task ทำงานได้ทีละหนึ่ง task เท่านั้น ผู้ใช้สามารถสร้างคิวแบบนี้ได้ไม่จำกัด และดิสแพทช์คิวแต่ละคิวจะแยกการทำงานกันออกจากกัน เช่น โปรแกรมมีทั้งหมด 3 คิว โดยแต่ละคิวก็มี task เป็นของตัวเอง เมื่อให้โปรแกรมทำงาน คิวทั้ง 3 สามารถที่จะทำงานไปพร้อมกันได้ อย่างไรก็ตามคิวแต่ละคิวจะอนุญาติให้ task ได้ทำงานได้เพียงทีละ task เท่านั้น

serial

Concurrent (global dispatch queue) เป็นคิวที่อนุญาติให้ task ทำงานพร้อมกันได้มากกว่าหนึ่ง task และยังทำงานตามลำดับของ task ที่เข้ามาในดิสแพทช์คิว จำนวนของคิวที่สามารถสร้างได้นั้นขึ้นอยู่ทรัพยากรของระบบ เมื่อดิสแพทช์คิวทำงาน คิวจะสร้างเทรดขึ้นมาเพื่อรองรับ task และมีการบริหารจัดการโดยคิวเอง เช่นเดียวกับคิวแบบ serial

concurrent

Main dispatch queue อนุญาติให้ task ทำงานได้พร้อมกันเช่นเดียวกันกับ concurrent ดิสแพทช์คิวนี้จะไม่สร้างเทรดใหม่เหมือนกับสองคิวทีผ่านมา แต่จะให้ task ทำงานที่ Application Main Thread แทน คิวนี้มีการทำงานที่ main thread ดังนั้นผู้ใช้จะไม่ได้สร้างคิวขึ้นมาเอง แต่จะเรียกใช้ฟังชั่นเพื่อขอ main dispatch queue จากระบบ
เมื่อเข้าใจการทำงานของคิวทั้งสามแบบแล้ว ต่อไปก็ลงมือเขียนโปรแกรมกัน ซึ่งเป็นตัวอย่างการใช้งาน GCD อย่างง่ายๆ เช่น การดาวน์โหลด รูปภาพจากอินเทอร์เน็ต พร้อมกันหลายๆรูป Continue reading Objective-C Programming Chapter 19 (Part1)