Objective-C Programming Chapter 7 (Part 2)

Holding other objects

ที่ผ่านมาเราได้เขียนคลาสที่มีตัวแปร ต่างๆมากมาย แต่ก็เป็นเพียงแค่ตัวแปรแบบธรรมดาทั่วๆไปเช่น ทศนิยม จำนวนเต็ม แต่ในความเป็นจริงแล้ว คลาสต่างๆมักจะมีสามาชิกเป็นออบเจ็กต์ด้วย เราจะสร้างโปรเจคขึ้นมาใหม่โดยใช้คลาสจากโปรแกรม 7.3 ที่ผ่านมาและเพิ่มคลาส harddisk เข้าไปในโปรแกรมของเรา โดยมีโค้ดดังต่อไปนี้

Program 7.4

Harddisk.h

Harddisk.m

และให้แก้ไขโค้ดของคลาส computer ดังนี้

Computer.h

สิ่งที่ได้แก้ไขไปก็คือเพิ่มสมาชิกใหม่ชื่อ externalHarddisk ซึ่งเป็นคลาส Harddisk การประกาศ externalHarddisk จะเห็นว่าเป็นตัวแปรแบบ Harddisk* หรือ pointer นั่นเอง นอกจากนี้เพิ่มเมธอด setExternalHarddisk โดยรับพารามิเตอร์ pointer เข้ามาเช่นกัน

Computer.m

เมื่อพิจาณาจากโค้ด

ตัวแปร externalHarddisk นั้นเป็นเพียงแค่ pointer ที่ชี้ไปยัง ExternalHarddisk เท่านั้นเอง ดูภาพประกอบเพื่อความเข้าใจ

chapter7_5

สุดท้ายคือแก้ไขตัวโปรแกรมหลักดังนี้
main.m

เมนโปรแกรมประกาศ computer และ extHDD จากนั้นก็กำหนดค่าความจุของ extHDD เท่ากับ 240 พร้อมกับกำหนดราคาและความเร็วให้กับ computer จากนั้นในบรรทัดที่ 17 เรียกเมธอด setExternalHarddisk โดยส่งพารามิเตอร์ extHDD ไปด้วย ดังนั้นแล้วตัวแปร externalHarddisk ของ computer ก็คือ exterHDD นั่นเอง เมื่อให้โปรแกรมทำงานก็จะได้ผลลัพธ์ดังนี้

Program 7.4 Output
Computer
price 12000.0
speed 450 mHz
capacity 240 GB

ถึง แม้โปรแกรมจะทำงานได้ไม่มีปัญหาใดๆ แต่เมื่อไหร่ก็ตามที่ออบเจ็กต์ extHDD ถูกทำลายปัญหาก็จะเกิดขึ้นทันที เพราะว่าตัวแปร externalHarddisk ในคลาสของ Computer นั้นไม่มีทางรู้ได้เลยว่าออบเจ็กต์นั้นได้ถูกทำลายลงไปแล้ว  เช่นสมมติว่าเราแก้ไขเปลี่ยนลำดับโค้ดให้ extHDD คืนหน่วยความจำ หลังจากนั้นให้ computer เรียกเมธอด printDetails ดังตัวอย่าง

 

เมื่อคอมไพล์และรันโปรแกรมก็จะเกิดความผิดพลาด Bad Acesss ทันที เพราะเราได้ทำลาย extHDD โดยการคืนหน่วยความจำไปแล้ว แต่ในเมธอด printDetains ของ computer ยังเรียกใช้อ็อบเจ็กต์นั้นอยู่ จึงเกิด error ขึ้นดังรูป

chapter7_1

แล้ว เราจะแก้ปัญหาได้อย่างไร คำตอบง่ายๆก็คือให้คลาส computer บอกกับ extHDD ว่าใช้งานอยู่ โดยใช้ retain นั่นเอง เราจะแก้ไขเมธอด setExternalHarddisk ให้เป็นดังนี้

เท่านี้เราก็สามารถป้องกันปัญหาได้แล้ว นอกจากนี้เราได้เพิ่มโค้ดไว้ตรวจสอบด้วยกว่าออบเจ็กต์นั้นไม่เป็น nil งานของเรายังไม่จบเพียงเท่านี้ เพราะอย่างที่ได้เคยอธิบายเรื่องของหน่วยความจำว่าการ retain เป็นการเพิ่มค่า counter ดังนั้นเมื่อเลิกใช้งานเราก็ต้อง release เพื่อลด counter ฉะนั้นแล้วเราก็จะเพิ่มเมธอดเพื่อ release หลังจากเลิกใช้งานแล้ว

จากนั้นเราก็แก้ไขโปรแกรมใหม่ ก็จะมีโค้ดดังนี้

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

Owning objects

สมมติว่าแก้ไขเมธอด setExternalHarddisk โดยเพิ่มปริมาณความจุของ externalHarddisk ของคลาส Computer ให้เป็นสองเท่า ดังนี้

และส่วนโปรแกรมหลักก็เปลี่ยนโค้ดให้เป็นดังนี้

Program 7.4 ( Modified )

Program 7.4 (Modified) Output
Computer
price 12000.0
speed 450 mHz
capacity 480 GB

External Hard disk
capacity 480 GB

เมื่อ พิจารณาจากโปรแกรม เราได้เปลี่ยนความจุของ externalHarddisk ในเมธอด setExternalHarddisk เท่านั้น แต่ผลลัพธ์ของโปรแกรมกลับกลายเป็นว่า capacity นั้นได้เปลี่ยนเป็น 480 ทั้ง computer และ extHDD ? คำตอบก็คือ เพราะว่า externalHarddisk นั้นเป็นเพียงแค่ pointer ที่ชี้ไปยังออบเจ็กต์อื่น ดังนั้นเมื่อเราอ้างถึง externalHarddisk ก็เหมือนกับอ้างไปยังออบเจ็กต์ตัวนั้นตรงๆ การเปลี่ยนแปลงค่าก็เท่ากับเปลี่ยนค่าของสิ่งนั้นโดยตรง ถ้าหากเราจะเลี่ยงปัญหานี้ก็สามารถทำได้โดยการ สร้างออบเจ็กต์เป็นของตัวเองเลยดีกว่า ดังเช่นโค้ดตัวอย่าง

จากตัวอย่างเราได้เพิ่มเมธอด initHarddisk เพื่อใช้ในการสร้างออบเจ็กต์ externalHarddisk ที่เหลือก็คือแก้ไขส่วนของโปรแกรมหลักโดยเรียก initHarddisk หลังจากการประกาศออบเจ็กต์ computer

หลังจากนั้นเมื่อคอมไพล์และรันโปรแกรมที่ได้แก้ไขไปโปรแกรมก็จะมีผลลัพธ์ดังนี้

Program 7.4 (Fix) Output
Computer
price 12000.0
speed 450 mHz
capacity 480 GB

External Hard disk
capacity 240 GB

Overriding Methods

ในหัวข้อนี้เราจะได้เรียนรู้เกี่ยวกับ คุณสมบัติอย่างหนึ่งของ Object Oriented Programming ที่สำคัญมากๆ ก็คือ Overriding Methods จากที่เราได้ทราบกันแล้วว่าถ้าหากเราสร้างคลาสใหม่โดยการ sub class คลาสนั้นจะไม่สามารถลบเมธอดที่ติดมากับ parent ออกไปได้ อย่างไรก็ตามเราสามารถเปลี่ยนแปลงแก้ไขการทำงานของเมธอดนั้นได้ โดยการเขียนทับหรือมีศัพท์ทางเทคนิคว่า overriding methods ลองพิจารณาโปรแกรมตัวอย่างง่ายต่อไปนี้

Program 7.5

Vehicle.h

Vehicle.m

จากนั้นสร้างคลาส Car โดยการซับคลาส Vehicle

Car.h

Car.m

และส่วนสุดท้ายเขียนส่วนการทำงานของโปรแกรมง่ายๆดังนี้
main.m

Program 7.5 Output
speed 140.0
speed 140.0
Car double Speed
speed 280.0

ก็ อย่างที่ได้กล่าวไปว่า child class นั้นจะได้รับทุกๆอย่างมาเหมือนกับ parent class ถ้าเราอยากจะแก้ไขให้คลาส Car มีค่า speed เริ่มต้นเป็น 200 จะทำอย่างไร ? แน่นอนว่าอย่างแรกที่เราทำได้ก็คือการเขียน method ขึ้นมาใหม่ แต่เราก็ต้องแก้ไขโปรแกรมให้เรียกใช้เมธอดใหม่ด้วย ลองจินตนาการว่าถ้าหากโปรแกรมหลักของเรา เรียกใช้เมธอดนี้หลายๆส่วน ปัญหาย่อมเกิดขึ้นแน่นอน เพราะเราต้องตามไปแก้ไขหลายๆส่วนของโปรแกรม ดังนั้นเราจะใช้การ Overiding Methods เพื่อเขียนทับเมธอดของเก่าที่ได้มาจาก parent วิธีการก็ไม่ได้ยากอะไร ชื่อก็บอกอยู่แล้วว่าเขียนทับ ดังนั้นก็เขียนใหม่ได้เลย

ส่วนโปรแกรมหลักก็ไม่ได้แก้ไขอะไร เมื่อคอมไพล์และรันโปรแกรม ก็จะแสดงผลดังนี้

Program 7.5Output (Fixed)
speed 140.0
speed 500.0
Car Double Speed
speed 1000.0

ลองดูอีกสักตัวอย่างเพื่อความเข้าใจ เราจะแก้ไขการทำงานโปรแกรม 7.5 โดยการเรียกเมธอด doubleSpeed ดังตัวอย่าง

 

เมื่อเราคอมไพล์โปรแกรมที่ได้แก้ไขไป XCode จะแจ้งเตือนด้วยข้อความดังนี้

vec_warn

โปรแกรม แจ้งเราว่า ‘Vehicle’ may not respond to ‘doubleSpeed’ ก็หมายความว่า Vehicle อาจจะไม่ตอบสนองต่อการเรียกใช้เมธอด doubleSpeed นั่นก็เพราะว่าคลาส Vehicle นั้นไม่มีเมธอดนี้นั่นเอง เนื่องจากคุณสมบัติ inheritance นั้นจะเป็นลักษณะการสืบทอดจาก parent ไปยัง child อย่างเดียวเท่านั้น การแก้ไขส่วนต่างๆของ child จะไม่ได้เกี่ยวข้อง parent แต่อย่างใด ในกรณีนี้่คลาส Car เป็น child ส่วนคลาส Vehicle เป็น parent การเพิ่มเมธอด doubleSpeed ให้กับ Car ก็ไม่ได้เกี่ยวข้องอะไรกับ Vechicle นั่นเอง

 

Init , Dealloc and Super keyword

ย้อนกลับ ไปยังโปรแกรมที่ 7.4 โปรแกรมนี้ก็ทำงานได้สมบูรณ์ดีแต่ก็ยังไม่ใช่แนวทางที่ถูกต้องนัก เพราะถ้าจะพิจารณาดีๆแล้วก็จะเห็นว่า หลังจาก init แล้วเราต้องเรียก initHarddisk เพื่อเตรียมความพร้อมของออบเจ็กต์ต่างๆภายในคลาสอย่างเช่น  externalHarddisk และเมื่อใช้เสร็จก็ต้องคืนหน่วยความจำของออบเจ็กต์ที่เราได้จองไปด้วยคำสั่ง releaseExternalHarddisk และหลังจากนั้นถึงจะเรียก release อีกที ซึ่งค่อนข้างจะยุ่งยากอยู่พอสมควร และจากหัวข้อที่ผ่านมาเราได้ใช้ Overinding Method กันไปแล้ว ก็จะเห็นได้ว่าเราสามารถ overidding เมธอดใดๆของ parent ก็ได้ เมื่อเราทราบเช่นนี้แล้วเราจะอาศัย overriding เพื่อเปลี่ยนการทำงานเมธอด init ของ NSObject ให้มีความสามารถในการจองหน่วยความจำของตัวแปรในคลาสของเรา และ overriding เมธอด dealloc ➊ เพื่อใช้ในการคืนหน่วยความจำให้กับออบเจ็กต์ที่คลาสได้สร้างไว้ เราจะเขียนโปรแกรมใหม่โดยมีโค้ดดังนี้
Program 7.6

SolidStateDisk.h

SolidStateDisk.m

MacBook.h

MacBook.m

จากโค้ดตัวอย่างของคลาส MacBook จะเห็นว่าเราได้ overridding เมธอด init เพื่อเขียนการทำงานใหม่ การทำงานใหม่นี้ในขึ้นตอนแรกเราจะเรียก

สิ่ง ที่เพิ่มเข้ามาในบรรทัดนี้คือคีย์เวิดร์ super ซึ่งเป็นคำสั่งเอาไว้อ้างอิงถึง parent ของคลาสที่ได้สืบทอดมา และการที่เราต้องให้ค่า self (ตัวเอง) มีค่าเท่ากับ [super init] ก็เพราะว่า [super init] เปรียบเสมือนการเรียกเมธอด init ของ NSObject

super_ch7

ซึ่ง จะเตรียมค่าพื้นฐานต่างๆของภายใน NSObject หลังจากนั้นจะส่งค่าตำแหน่งหน่วยความจำที่ได้จองไปกลับมาให้ เราจึงต้องให้ self เท่ากับตำแหน่งหน่วยความจำที่ NSObject ได้จองไว้ให้เรานั่นเอง

ในกรณีที่จองหน่วยความจำไม่ได้ก็จะได้ค่า nil กลับมา ดังนั้นเราจึงต้องตรวจสอบค่าด้วยคำสั่ง

เมื่อทุกอย่างเรียบร้อย เราก็จะจองหน่วยความจำให้กับตัวแปรในคลาสของเราต่อ นั่นก็คือ

และสุดท้ายเราก็จะส่ง self หรือตำแหน่งความจำของออบเจ็กต์นี้กลับไปนั่นเอง

และอีกหนึ่งเมธอดที่เราได้ Overriding ไปนั่นก็คือ dealloc ซึ่งมีโค้ดดังนี้

เมื่อดูการทำงานจากโค้ดแล้วก็จะเห็นว่า เราได้คืนหน่วยความจำให้ในส่วนของออบเจ็กต์ต่างๆภายในคลาสก่อน หลังจากนั้น ถึงจะเรียก

เพื่อเป็นการคืนหน่วยความจำให้กับออบเจ็กต์ต่างๆของ parent ที่ได้จองไว้นั่นเอง
ในส่วนของโปรแกรมหลักของเราก็จะมีหน้าตาแบบนี้

main.m

Program 7.6 Output
speed 2.700000 GHz
ssd 500.0 GB

เห็น ได้ว่าคลาส MacBook นั้นได้ลดจำนวนเมธอดในการจองหน่วยความจำและคืนหน่วยความจำสำหรับตัวแปรของ คลาส และประโยชน์อีกอย่างคือถ้าหากเราเขียนเมธอดแยกอย่าง initHarddisk บางครั้งเราอาจจะลืมเรียกใช้งาน แต่ถ้าเรา Overiding Method อย่าง init ก็ไม่ต้องกังวลาปัญหานี้

More init

ถึงแม้ว่าเราจะ overriding method อย่าง init ได้ แต่ในบางครั้งเราอาจจะจำเป็นที่จะต้องความยืดหยุ่น เช่น สมมติว่าถ้าหากเราต้องการจะประกาศออบเจ็กต์ MacBook แบบกำหนดขนาดของฮาร์ดดิสเริ่มต้นได้เอง การใช้ init อาจจะไม่ครอบคลุม ดังนั้นแล้วก็อาจจะจำเป็นต้องเขียนเมธอด init แบบพิเศษเพิ่มเติมเพื่อช่วยอำนวยความสะดวก ดังเช่นตัวอย่าง

ถ้าจะสังเกตจะเห็นว่า เราเรียก

ไม่ ได้เรียก super แต่อย่างใด นั่นก็เพราะว่าเราก็ยังมีเมธอด init ของเก่าที่เราได้เขียนไป ฉะนั้นเราก็สามารถใช้ของเดิมได้ ซึ่งช่วยลดการเขียนโค้ดลง

macbook_dia

Program 7.7

Program 7.7 Output
MacBook A
speed 2.700000 GHz
ssd 500 GB.

MacBook B
speed 1.50000 GHz
ssd 300 GB.

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

โหลด PDF ไปอ่านได้ครับ ส่วน Source code ก็โหลดได้ที่ github

One thought on “Objective-C Programming Chapter 7 (Part 2)”

Leave a Reply