Objective-C เป็นภาษาโปรแกรมที่มีการจัดส่งข้อความแบบไดนามิก สร้างโดย Brad Cox ในช่วงทศวรรษ 1980 Apple เลือก Objective-C เป็นภาษาหลักสำหรับ NeXTSTEP และต่อมาสำหรับ iOS SDK Programming With Objective-C — บทนำเกี่ยวกับไวยากรณ์ข้อความและการจัดการหน่วยความจำ
ประเด็นสำคัญ
Objective-C เป็นภาษาโปรแกรมที่ขยาย C ด้วยความสามารถของการเขียนโปรแกรมเชิงวัตถุในสไตล์ Smalltalk โค้ด Objective-C ถูกคอมไพล์ผ่าน LLVM หรือ GCC เป็นโค้ดเครื่องดั้งเดิม โดยคงความเข้ากันได้ย้อนหลังอย่างสมบูรณ์กับ C — โค้ด C ใดๆ ก็ใช้ได้ใน Objective-C
Apple ซื้อ NeXT (พร้อมกับ Objective-C) ในปี 1997 ภาษาได้กลายเป็นรากฐานของ Cocoa และ Cocoa Touch — เฟรมเวิร์กสำหรับ macOS และ iOS ตั้งแต่ปี 2014 Apple ได้ส่งเสริม Swift ให้เป็นภาษาทดแทน แต่ Objective-C ยังคงมีความสำคัญอย่างยิ่งสำหรับโปรเจกต์เดิมและเฟรมเวิร์กระบบบางส่วน ตามข้อมูลของ Apple (WWDC 2024) ประมาณ 35% ของแอปใน App Store ยังคงมีโค้ด Objective-C
คุณสมบัติหลักคือ รันไทม์แบบไดนามิก แตกต่างจาก Swift ที่การเรียกเมธอดถูกแก้ไขในเวลาคอมไพล์ Objective-C ส่งข้อความในเวลารันไทม์ผ่านฟังก์ชัน objc_msgSend ซึ่งช่วยให้สามารถแทนที่เมธอดได้ทันที (method swizzling) เพิ่มคลาสแบบไดนามิก และใช้ forward invocation สำหรับ selector ที่ไม่มีอยู่
โค้ด C ใดๆ ก็ใช้ได้ใน Objective-C ส่วนขยายเชิงวัตถุเพิ่มคลาส (@interface/@implementation) หมวดหมู่ โปรโตคอล (@protocol) และการกำหนดชนิดแบบไดนามิก (id) ไฟล์มีนามสกุล .m (การนำไปใช้) และ .h (ส่วนหัว)
ไวยากรณ์ข้อความ คือความแตกต่างหลักระหว่าง Objective-C และภาษาแบบ C แทนที่จะใช้ object.method(argument) จะใช้ [object method:argument] ข้อความแต่ละข้อความผ่าน objc_msgSend ซึ่งค้นหาการนำไปใช้ของเมธอดในรันไทม์แบบไดนามิก
// ไวยากรณ์ข้อความพร้อมพารามิเตอร์ที่มีชื่อ
NSString *greeting = [NSString stringWithFormat:@"Hello, %@", name];
// ข้อความซ้อน
NSArray *sortedArray = [[array sortedArrayUsingSelector:@selector(compare:)] copy];
// การตรวจสอบชนิดผ่าน introspection
if ([object isKindOfClass:[UIView class]]) {
UIView *view = (UIView *)object;
view.backgroundColor = [UIColor redColor];
}การจัดส่งแบบไดนามิก ช่วยให้สามารถแทนที่เมธอดในรันไทม์ (method swizzling) — เทคนิคที่ทรงพลังแต่เสี่ยง ตัวอย่างเช่น เฟรมเวิร์กอย่าง AFNetworking และ Aspects ใช้ swizzling เพื่อสกัดกั้นการเรียก URLSession Apple เตือน: swizzling อาจทำให้เฟรมเวิร์กระบบเสียหายหากใช้ไม่ถูกต้อง
หมวดหมู่ คือคุณสมบัติพิเศษของ Objective-C ที่ช่วยให้เพิ่มเมธอดให้กับคลาสที่มีอยู่ (รวมถึงคลาสระบบอย่าง NSString และ UIView) โดยไม่ต้องสืบทอดและไม่ต้องเข้าถึงซอร์สโค้ด หมวดหมู่ประกาศผ่าน @interface ClassName (CategoryName)
// ไฟล์ UIColor+Hex.h — หมวดหมู่สำหรับ UIColor
@interface UIColor (Hex)
+ (instancetype)colorWithHex:NSUIntegerhex;
@end
// ไฟล์ UIColor+Hex.m — การนำไปใช้
@implementation UIColor (Hex)
+ (instancetype)colorWithHex:NSUIntegerhex {
CGFloat r = ((hex >> 16) & 0xFF) / 255.0;
CGFloat g = ((hex >> 8) & 0xFF) / 255.0;
CGFloat b = (hex & 0xFF) / 255.0;
return [self colorWithRed:r green:g blue:b alpha:1.0];
}
@endส่วนขยาย (Class Extension) เป็นหมวดหมู่ชนิดพิเศษที่ประกาศในไฟล์ .m โดยไม่มีชื่อ: @interface ClassName () แตกต่างจากหมวดหมู่ ส่วนขยายสามารถเพิ่มไม่เพียงเมธอด แต่ยังรวมถึง ivar (ตัวแปรอินสแตนซ์) และคุณสมบัติ ส่วนขยายใช้เพื่อซ่อน API ภายในจากโมดูลภายนอก
บล็อก คือฟังก์ชันนิรนามใน Objective-C ที่จับตัวแปรจากขอบเขตภายนอก ไวยากรณ์บล็อก: ^(int x) { return x * 2; } บล็อกใช้ใน UIKit สำหรับตัวจัดการ callback ใน Grand Central Dispatch สำหรับงานแบบอะซิงโครนัส และในคอลเลกชัน NSArray/NSDictionary สำหรับการดำเนินการเชิงฟังก์ชัน
// การประกาศชนิดบล็อก
typedef void (^CompletionBlock)(BOOL success, NSError *error);
// บล็อกเป็นพารามิเตอร์
- (void)fetchDataWithCompletion:(CompletionBlock)completion {
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
BOOL result = [weakSelf processData];
if (completion) {
completion(result, nil);
}
});
}การจัดการหน่วยความจำ ในบล็อกมีความสำคัญอย่างยิ่ง บล็อกจับ self ด้วย reference ที่แข็งแกร่ง ทำให้เกิด retain cycle เมื่อใช้โดยตรง วิธีแก้คือ __weak typeof(self) weakSelf = self ตามด้วยการตรวจสอบภายในบล็อก ปัญหานี้แก้ไขได้อย่างสมบูรณ์ใน Swift ผ่าน capture lists [weak self]
Objective-C พัฒนาจาก MRC (Manual Reference Counting) ไปเป็น ARC (Automatic Reference Counting) ใน MRC นักพัฒนาเรียกด้วยตนเอง retain (เพิ่มตัวนับ) release (ลด) และ autorelease (ปล่อยแบบรอเลื่อน) ข้อผิดพลาดในการเรียกเหล่านี้นำไปสู่การรั่วไหลของหน่วยความจำหรือการค้างเนื่องจากพอยน์เตอร์ที่angling
| การดำเนินการ | MRC | ARC |
|---|---|---|
| การสร้างออบเจ็กต์ | [[Object alloc] init] | [[Object alloc] init] |
| การถือ reference | [object retain] | อัตโนมัติ |
| การปล่อย | [object release] | อัตโนมัติ |
| การปล่อยอัตโนมัติ | [object autorelease] | อัตโนมัติ (ไม่จำเป็น) |
| Reference แบบอ่อน | __weak ไม่มี | __weak (nil อัตโนมัติ) |
ARC ถูกนำมาใช้ใน Xcode 4.2 และ LLVM 3.0 (2011) คอมไพเลอร์แทรก retain/release โดยอัตโนมัติในเวลาคอมไพล์โดยวิเคราะห์อายุของออบเจ็กต์ ARC ไม่ใช่ garbage collection — เป็นการวิเคราะห์แบบสถิตพร้อมการแทรกอัตโนมัติ ARC ของ Objective-C เข้ากันได้กับ ARC ของ Swift: ทั้งสองใช้ระบบนับ reference เดียวกันในระดับรันไทม์
การใช้ Objective-C และ Swift ร่วมกันในโปรเจกต์เดียวเป็นแนวปฏิบัติทั่วไปสำหรับโปรเจกต์เดิม Apple จัดเตรียม Bridging Header — ไฟล์ที่สร้างโดยอัตโนมัติซึ่ง Swift มองเห็นคลาส Objective-C และ Objective-C มองเห็นคลาส Swift ที่สืบทอดจาก NSObject
// ProjectName-Bridging-Header.h
// Swift เห็นส่วนหัวเหล่านี้โดยอัตโนมัติ
#import "LegacyManager.h"
#import "NetworkClient.h"
#import "DataStore.h"การย้ายจาก Objective-C ไปยัง Swift เป็นกระบวนการแบบค่อยเป็นค่อยไป ไฟล์ใหม่เขียนด้วย Swift ไฟล์เก่าถูกปรับโครงสร้างทีละน้อย Bridging Header ถูกสร้างโดยอัตโนมัติเมื่อเพิ่มไฟล์ Swift แรกในโปรเจกต์ Objective-C สำหรับการมองเห็นย้อนกลับ (ObjC → Swift) Xcode สร้างไฟล์ <ProjectName>-Swift.h ที่มีประกาศ @interface สำหรับคลาส Swift ที่ทำเครื่องหมายด้วย @objc
// คลาส Swift ที่มองเห็นได้จาก Objective-C
@objc class SwiftRouter: NSObject {
@objc func navigateToProfile(userId: Int) {
// การนำไปใช้
}
}ข้อจำกัด: ชนิดค่า (value type) ของ Swift (struct, enum) ไม่สามารถมองเห็นได้โดยตรงจาก Objective-C — ต้องห่อในคลาสที่มี @objc Generics ของ Swift สามารถเข้าถึงได้อย่างจำกัดจาก Objective-C แนวทางที่แนะนำคือเขียนโค้ดใหม่ใน Swift และปรับโครงสร้างโค้ด Objective-C ที่มีอยู่เมื่อเปลี่ยนฟังก์ชันการทำงานที่เกี่ยวข้องเท่านั้น
ข้อได้เปรียบหลักของ Objective-C เหนือ Swift คือ การเข้าถึงรันไทม์อย่างเต็มที่ Method swizzling การแทนที่การนำไปใช้ของเมธอดในรันไทม์ ช่วยให้แทนที่เมธอดของระบบโดยไม่ต้องสืบทอด ไลบรารีอย่าง Aspects และ JRSwizzle ใช้ความสามารถนี้สำหรับ AOP (การเขียนโปรแกรมเชิงด้าน) — การตรวจสอบ การวิเคราะห์ และการบันทึก
Forward invocation เป็นคุณสมบัติรันไทม์อีกอย่าง: หากออบเจ็กต์ไม่ตอบสนองต่อ selector ระบบจะเรียก forwardInvocation: ซึ่งช่วยให้ส่งต่อข้อความไปยังออบเจ็กต์อื่น นี่คือพื้นฐานของรูปแบบ Proxy ใน Objective-C (NSProxy) ซึ่งใช้สำหรับการเริ่มต้นแบบขี้เกียจและออบเจ็กต์แบบกระจาย
Apple แนะนำให้ลดการใช้เทคนิครันไทม์ในโค้ดใหม่ โดยเลือกใช้การกำหนดชนิดแบบสถิตของ Swift อย่างไรก็ตาม ในโปรเจกต์เดิม ความรู้เกี่ยวกับ objc_msgSend, method_exchangeImplementations และ objc_getAssociatedObject เป็นสิ่งจำเป็นสำหรับการบำรุงรักษาฐานโค้ดที่มีอยู่
Objective-C รองรับ คุณสมบัติ ในรูปแบบน้ำตาลเชิงไวยากรณ์เหนือ ivar พร้อม getter/setter ตัวปรับแต่ง atomic/nonatomic, strong/weak/copy, readonly/readwrite และ assign/retain กำหนดพฤติกรรมหน่วยความจำและความปลอดภัยของเธรด Key-Value Observing (KVO) เป็นกลไกการสังเกตการเปลี่ยนแปลงคุณสมบัติที่สร้างในรันไทม์: ออบเจ็กต์ใดๆ สามารถสมัครรับการเปลี่ยนแปลงคุณสมบัติของออบเจ็กต์อื่นผ่าน addObserver
คำถามที่พบบ่อย
การเรียกเมธอดมีลักษณะเป็น [object selector:argument] ข้อความผ่าน objc_msgSend ซึ่งค้นหาการนำไปใช้ในรันไทม์แบบไดนามิก Selector คือชื่อเมธอด (@selector(methodName)) ไม่ใช่พอยน์เตอร์ฟังก์ชัน ซึ่งช่วยให้ swizzling และ forward invocation
หมวดหมู่ (@interface ClassName (Name)) เพิ่มเมธอดให้กับคลาสใดๆ (รวมถึงคลาสระบบ) โดยไม่ต้องสืบทอด ส่วนขยาย (@interface ClassName ()) ประกาศในไฟล์ .m และสามารถเพิ่มคุณสมบัติและ ivars หมวดหมู่ไม่สามารถเพิ่ม ivars ได้ แต่ส่วนขยายสามารถเพิ่มได้
บล็อกคือฟังก์ชันนิรนามที่มีไวยากรณ์ ^(พารามิเตอร์) { เนื้อหา } จับตัวแปรจากบริบท คล้ายกับ lambda ใน C++ และ closure ใน Swift ต้องใช้ __weak เพื่อป้องกัน retain cycles เมื่อจับ self ใช้ใน UIKit, GCD และ Foundation
MRC คือการนับ reference ด้วยตนเอง: โปรแกรมเมอร์เรียก retain, release, autorelease ARC คืออัตโนมัติ: คอมไพเลอร์แทรก retain/release ตามการวิเคราะห์แบบสถิต ARC ไม่ใช่ GC — ออบเจ็กต์ถูกปล่อยทันทีเมื่อตัวนับถึงศูนย์ ARC รองรับตัวปรับแต่ง __weak และ __strong
ได้ ผ่าน Bridging Header Swift มองเห็น Objective-C ผ่านส่วนหัวนี้ Objective-C มองเห็น Swift ผ่าน <ProjectName>-Swift.h คลาส Swift ต้องสืบทอดจาก NSObject และทำเครื่องหมายด้วย @objc struct และ enum ของ Swift ไม่สามารถมองเห็นได้โดยตรงโดย Objective-C
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ