cv.cm

编码 · 编程语言

Objective-C 入门教程

阅读约 9 分钟

简短回答

Objective-C 是在 C 之上加入 Smalltalk 风格面向对象消息机制的语言。你通过方括号发送消息来调用方法,如 [object doSomething:arg],在 @interface 和 @implementation 块中定义类,并使用 Apple 的 Foundation 框架处理字符串、数组和字典。在 Swift 出现之前,它是 macOS 和 iOS 应用的主要语言,至今仍用于许多现有的 Apple 代码库。在 Mac 上,用 clang -fobjc-arc -framework Foundation hello.m 编译。

Objective-C 是什么,谁在用

Objective-C 由 Brad Cox 和 Tom Love 在 20 世纪 80 年代初创建,被 NeXT 用于其 NeXTSTEP 系统,后来由 Apple 继承,NeXTSTEP 成了 macOS 和 iOS 的基础。它是 C 的严格超集:任何 C 代码都是合法的 Objective-C,两者可以自由混用。

如今 Apple 推荐新应用使用 Swift,但 Objective-C 仍然受到完全支持。它存在于较老的 iOS 和 macOS 应用、许多系统框架和第三方库中,以及把 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 启用自动引用计数(ARC),这是标准的内存管理方式。在 Linux 上,GNUstep 项目提供兼容的 Foundation 库,构建命令有所不同。

类型、对象与消息

因为 Objective-C 就是 C,所以所有 C 类型都可用。对象总是通过指针使用(NSString *name)。调用方法称为发送消息;参数与方法名的各个部分交错排列。

类型示例说明
C 数值类型int, double, NSIntegerNSInteger 在 64 位平台上是 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,这避免了许多崩溃,但也可能掩盖 bug。

控制流

控制流与 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 集合与块

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

用 ^ 书写的块是闭包:能捕获外围作用域变量的匿名函数。Apple 的 API 把它们用于回调、排序以及配合 Grand Central Dispatch 的异步工作。<NSString *> 这类部分是轻量级泛型,对编译器和 Swift 有帮助;它们是可选的。

内存管理与错误处理

使用 ARC 时,编译器会替你插入引用计数调用:当没有强引用指向对象时,对象就会被释放。你只需要打破循环引用,通常是把其中一方标记为 weak(例如委托属性)。

可恢复的错误使用 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 应用与库中。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 提供的旧垃圾回收器已被弃用,不再可用。

全部编码教程