cv.cm

編碼 · 程式語言

Objective-C 入門教學

閱讀約 10 分鐘

簡答

Objective-C 是在 C 之上加入 Smalltalk 風格物件導向訊息傳遞的語言。你透過方括號傳送訊息來呼叫方法,[object doSomething:arg],在 @interface 與 @implementation 區塊中定義類別,並使用 Apple 的 Foundation 框架處理字串、陣列與字典。在 Swift 出現之前,它是 macOS 與 iOS App 的主要語言,至今仍用於許多既有的 Apple 程式碼。在 Mac 上,用 clang -fobjc-arc -framework Foundation hello.m 編譯。

Objective-C 是什麼、誰在使用

Objective-C 由 Brad Cox 與 Tom Love 於 1980 年代初創造,後來被 NeXT 採用於其 NeXTSTEP 系統,再由 Apple 承接,NeXTSTEP 也成為 macOS 與 iOS 的基礎。它是 C 的嚴格超集:任何 C 程式碼都是合法的 Objective-C,兩者可以自由混用。

如今 Apple 建議新的 App 使用 Swift,但 Objective-C 仍然受到完整支援。你會在較舊的 iOS 與 macOS App、許多系統框架與第三方函式庫,以及混用 C 或 C++ 與 Apple API 的專案中看到它。Swift 與 Objective-C 可以在同一個專案中互通,所以兩者都懂,對 Apple 平台的開發者很有用。

安裝並執行 Hello, world

在 macOS 上,安裝 Xcode 或僅安裝命令列工具(xcode-select --install),其中包含 clang 編譯器與 Foundation 框架。原始碼檔案使用 .m 副檔名;標頭檔使用 .h。

// hello.m
#import <Foundation/Foundation.h>

int main(void) {
    @autoreleasepool {
        NSLog(@"Hello, world!");
    }
    return 0;
}
clang -fobjc-arc -framework Foundation hello.m -o hello
./hello
# 2026-10-31 10:00:00.000 hello[1234:5678] Hello, world!

NSLog 會寫到標準錯誤輸出,並加上時間戳記與行程名稱。@"..." 建立的是 NSString 物件,而不是 C 字串。-fobjc-arc 會開啟自動參照計數(Automatic Reference Counting),這是標準的記憶體管理方式。在 Linux 上,GNUstep 專案提供相容的 Foundation 函式庫,但建置指令不同。

型別、物件與訊息

因為 Objective-C 就是 C,所有 C 型別都能使用。物件一律透過指標使用(NSString *name)。呼叫方法稱為傳送訊息;引數會穿插在方法名稱的各個部分之間。

型別範例說明
C 數字型別int, double, NSInteger在 64 位元平台上,NSInteger 是 64 位元
BOOLBOOL ok = YES;YES / NO
NSString *@"hello"不可變的字串物件
NSNumber *@42, @3.14, @(x + 1)包裝數字,讓集合能存放它
idid thing = @"x";指向任何物件的指標,沒有靜態型別
nilnil空的物件指標
NSString *name = @"Ana";
NSInteger age = 30;
NSString *line = [NSString stringWithFormat:@"%@ is %ld", name, (long)age];
NSLog(@"%@", line);                          // Ana is 30
NSLog(@"%lu", (unsigned long)[name length]); // 3
NSString *upper = [name uppercaseString];    // @"ANA"
BOOL same = [name isEqualToString:@"Ana"];   // YES
NSString *nothing = nil;
NSLog(@"%lu", (unsigned long)[nothing length]); // 0: messages to nil return 0

在格式字串中,%@ 會印出一個物件(它會呼叫該物件的 description 方法)。對 nil 傳送訊息是被允許的,並會回傳 nil 或 0,這避免了許多當機,但也可能掩蓋錯誤。

流程控制

流程控制與 C 完全相同:if、else、switch、for、while 與 do ... while。Objective-C 另外加入了集合專用的 for ... in 快速列舉。

NSInteger score = 85;
if (score >= 90) {
    NSLog(@"A");
} else if (score >= 80) {
    NSLog(@"B");
} else {
    NSLog(@"C");
}

for (int i = 1; i <= 3; i++) {
    NSLog(@"%d squared is %d", i, i * i);
}

for (NSString *fruit in @[@"apple", @"pear"]) {
    NSLog(@"%@", fruit);
}

類別、屬性與方法

類別有公開的介面(通常放在 .h 檔案中)與實作(放在 .m 檔案中)。實例方法以 - 開頭,類別方法以 + 開頭。@property 宣告屬性,並產生它的 getter、setter 與以底線命名的後備變數。

@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic) NSInteger age;
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age;
- (NSString *)greeting;
@end

@implementation Person
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age {
    self = [super init];
    if (self) {
        _name = [name copy];
        _age = age;
    }
    return self;
}

- (NSString *)greeting {
    return [NSString stringWithFormat:@"Hi, I am %@, %ld", self.name, (long)self.age];
}
@end

// Usage inside main:
Person *ana = [[Person alloc] initWithName:@"Ana" age:30];
NSLog(@"%@", [ana greeting]);   // Hi, I am Ana, 30
ana.age = 31;                   // dot syntax calls the setter

物件的建立分兩步:alloc 保留記憶體,再由 init... 方法初始化。上面的方法名為 initWithName:age:(冒號是名稱的一部分)。協定(@protocol)扮演介面的角色,而 category 讓你能為既有的類別新增方法,甚至包括 NSString。

Foundation 集合與 block

Foundation 框架提供不可變的集合與可變的子類別。字面值讓它們寫起來很簡短。集合只能存放物件,所以數字要包裝成 NSNumber,而且不能存放 nil(請改用 [NSNull null])。

類別字面值或建立方式可變版本
NSArray@[@"Ana", @"Ben"]NSMutableArray
NSDictionary@{@"Ana": @30}NSMutableDictionary
NSSet[NSSet setWithArray:arr]NSMutableSet
NSString@"text"NSMutableString
NSArray<NSString *> *names = @[@"Ana", @"Ben"];
NSMutableArray<NSString *> *more = [names mutableCopy];
[more addObject:@"Cleo"];
NSDictionary<NSString *, NSNumber *> *ages = @{@"Ana": @30, @"Ben": @17};
NSLog(@"%lu names, Ana is %@", (unsigned long)more.count, ages[@"Ana"]);

NSInteger (^square)(NSInteger) = ^(NSInteger x) { return x * x; };
NSLog(@"%ld", (long)square(7));    // 49

NSArray<NSNumber *> *sorted = [@[@3, @1, @2]
    sortedArrayUsingComparator:^NSComparisonResult(NSNumber *a, NSNumber *b) {
        return [a compare:b];
    }];
NSLog(@"%@", [sorted componentsJoinedByString:@", "]);   // 1, 2, 3

以 ^ 撰寫的 block 是閉包:捕捉周圍作用域變數的匿名函式。Apple API 用它們來做回呼、排序,以及搭配 Grand Central Dispatch 的非同步工作。<NSString *> 的部分是輕量的泛型,有助於編譯器與 Swift,是選擇性的。

記憶體管理與錯誤處理

使用 ARC 時,編譯器會替你插入參照計數的呼叫:當沒有任何強參照指向某個物件時,它就會被釋放。你只需要打破循環參照,通常是把其中一方標記為 weak(例如 delegate 屬性)。

可復原的錯誤使用 NSError 模式:方法失敗時回傳 nil 或 NO,並填入以參考方式傳入的 NSError 物件。例外(@try、@catch、NSException)雖然存在,但在 Cocoa 中它們代表的是索引超出範圍這類程式設計師的錯誤,而不是一般的失敗。

NSError *error = nil;
NSString *text = [NSString stringWithContentsOfFile:@"/no/such/file"
                                           encoding:NSUTF8StringEncoding
                                              error:&error];
if (text == nil) {
    NSLog(@"Failed: %@", error.localizedDescription);
}

完整的小程式:字詞頻率

#import <Foundation/Foundation.h>

int main(void) {
    @autoreleasepool {
        NSString *text = @"the cat and the hat and the bat";
        NSMutableDictionary<NSString *, NSNumber *> *counts = [NSMutableDictionary dictionary];
        for (NSString *word in [text componentsSeparatedByString:@" "]) {
            counts[word] = @(counts[word].integerValue + 1);
        }
        NSArray<NSString *> *sorted = [counts keysSortedByValueUsingComparator:
            ^NSComparisonResult(NSNumber *a, NSNumber *b) { return [b compare:a]; }];
        for (NSString *word in [sorted subarrayWithRange:NSMakeRange(0, 2)]) {
            printf("%s %ld\n", word.UTF8String, (long)counts[word].integerValue);
        }
    }
    return 0;
}
// the 3
// and 2

對於新字詞,counts[word] 是 nil,而 nil.integerValue 回傳 0,所以第一次計數會變成 1。比較函式把順序反轉,以便依次數由高到低排序,printf 則在不帶 NSLog 前綴的情況下列印。

接下來可以學什麼

  • Apple 文件封存庫中的「Programming with Objective-C」指南,介紹語言基礎。
  • Apple 開發者文件中的 Foundation 框架參考。
  • Clang 文件中關於自動參照計數的部分,提供確切的記憶體規則。
  • 如果你維護混合語言的程式碼,請學習 Objective-C 與 Swift 如何一起運作。

常見問題

Objective-C 現在還有人用嗎?

有,主要用於既有的 iOS 與 macOS App 及函式庫。Apple 建議新的程式碼使用 Swift,但 Xcode 與 Apple SDK 仍然支援 Objective-C。

該先學 Swift 還是 Objective-C?

對於新的 Apple 開發,請先學 Swift。當你需要閱讀或維護較舊的程式碼、緊密使用 C 或 C++ 函式庫,或想了解 Apple 框架底層如何運作時,再學 Objective-C。

Objective-C 的方括號是什麼意思?

它們用來傳送訊息:[receiver message] 會對物件 receiver 呼叫方法 message。引數接在冒號之後,如 [dict setObject:value forKey:key]。

Objective-C 有垃圾回收嗎?

現代的 Objective-C 使用自動參照計數(ARC),由編譯器加入 retain 與 release 呼叫。Apple 早期用於 macOS 的垃圾回收器已被棄用,且不再提供。

全部編碼教學