Objective-C学习小记

这一片博客是记录一部分学习Objective-C的时候我对一些问题思考的收获。

从什么是Objective-C说起

Objective-C这门语言已经在实际上被Archive了,但是学习这个语言有助于理解苹果IOS开发和后续转向更加现代的基于苹果的Swift开发。所以这里整理一下。

Apple Documentations是这样描述这个语言的:

Objective-C is the primary programming language you use when writing software for OS X and iOS. It’s a superset of the C programming language and provides object-oriented capabilities and a dynamic runtime. Objective-C inherits the syntax, primitive types, and flow control statements of C and adds syntax for defining classes and methods. It also adds language-level support for object graph management and object literals while providing dynamic typing and binding, deferring many responsibilities until runtime.

Reference: About Objective-C

​ 读下来,我们立马知道Objective-C是对C的一次扩展,他继承了C的大部分我们熟悉的原生类型,流控制等基础语法,但是添加了定义类和方法的功能。显然这就让C从一个面对过程的语言被扩展成了一个面对对象的语言。最重要的是实际上还完成了类似语言literal向对象功能的双向映射,所以达到了一种比C/C++还要强的Dynamic Typing,超出了C++的原生多态能力。

第一个Objective-C程序

笔者手头没有Mac电脑(比较穷),好在很多大佬有在线的编程网站可以进一步体验我们的Objective-C编程:在线运行Objective-C

#import <Foundation/Foundation.h>
 
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"Hello, World!");
    }
     return 0;
}

​ 看起来非常的简单,但是这就跟我们初步学习C一样,有大量的内容值得分析和研究。

#import <Foundation/Foundation.h>在做什么?

笔者当时看到这个示例程序的时候,脑子下意识的认为这是#include,但是实际上并不是,他更加像是C++20的#import,功能非常类似,为什么说类似,很简单:

  • #import 是 Objective-C 特有的头文件导入指令。
  • C++20的#import可以自动防止重复导入,不用写 #ifndef/#define 宏保护。这个的#import也是如此

<Foundation/Foundation.h> 是 Apple 提供的 Foundation 框架的入口头文件。也就是说,使用Apple Objc提供的不少上层建筑功能,就需要引入这个头文件,实际上就跟我们使用<stdlib.h>的含义是一样的,使用编译器内建的一些库。

@autoreleasepool在做什么?

@autoreleasepool,看名字就能猜出来。肯定是自动托管对象生命周期的东西,实际上的确大致如此。就这里,它实际上说的是在该作用域结束时,释放临时对象的内存,防止内存泄漏。

​ 这里可以牵扯的话题很多,笔者放到下面内存学习专门探讨和聊聊。

NSLog@“literal”

​ NSLog是一个经典的Foundation 提供的日志输出函数。他就是一个非常强大的std::cout,但是,他会比最原初的std::cout或者说是printf要多:

  • 自动换行。
  • 支持 NSString 格式化。
  • 会在控制台输出当前时间、进程号等信息。

比如说笔者的输出格式就有:

2025-10-23 01:34:58.916 a.out[3:3] Hello, World! # 显然网站使用的是UTC时间

@前缀在这里是用于创建 NSString 对象@"Hello, World!" 是一个 Objective-C 字符串对象,而不是 C 的 "Hello, World!"。那问题来了,这里的@"Hello, World!"@autoreleasepool的@是一回事嘛?

并不是,Objective-C 是在 C 语言基础上扩展的。所以它在语法上必须和 C 区分开。为了不与 C 的保留字冲突,Objective-C 所有的“新关键字”都加了 @。@autoreleasepool更加像是防止撞车。

而带有字面量或者是其他可以被转化成NS包装对象的字面量,则是为了方便的构造NS对象的语法糖。不用写又臭又长的构造罢了

出现位置作用对应类型举例备注
@autoreleasepoolObjective-C 关键字(语言结构)无(编译器语法)@autoreleasepool { ... }定义自动释放池
@"..."对象字面量语法NSString *@"Hello"创建 NSString 对象
@123, @[], @{}对象字面量语法NSNumber / NSArray / NSDictionary@[@1, @2]让对象构造更直观

到这里,我们就说完了一个最简单的Hello World程序。

快速入门Objective-C的类编程

​ Objective-C的类声明和实现被区分成两个部分。声明就像C++中的声明一样,告知成员有哪一些成员或者是方法。

@interface Person : NSObject {
    NSString *_name;
}
@property (nonatomic, copy) NSString *name;
- (void)sayHello;
@end

​ 实现上,就需要再单独的写一块内容:

@implementation Person

@synthesize name = _name; // 这里是告知将属性映射到成员上,这样我们就能默认的调用get/set了

- (void)sayHello {
    NSLog(@"Hello, I'm %@", _name);
}

- (void)dealloc {
    NSLog(@"%@ is being deallocated", _name);
}

@end

​ 我们把上面出现的内容列一个表格,就能得到这些内容(一顿搜索)

组成部分关键字作用
类声明@interface定义类名、父类、实例变量、属性、方法声明
类实现@implementation实现方法、合成属性
实例变量{ NSString *_name; }保存对象状态
属性@property + @synthesize自动生成 getter/setter
方法- (void)xxx定义类的行为
析构方法- (void)dealloc在对象销毁时释放资源
父类NSObject所有 OC 类的基类

Objective-C的内存管理机制

最急迫的问题:对象放哪了?

In C/C++, you can create objects as local (a.k.a stack-allocated) variables, where allocation and deallocation is handled for you automatically. You don’t have this luxury in Objective-C. In Objective-C all objects are allocated on the heap. It is the equivalent of always using new to create objects in C++. As a result, all object variables are pointers.

An In-depth Look At Manual Memory Management In Objective-C — Tom Dalling

​ 可以看到,对于Objc,他把所有的对象放在堆上了,这才是要点,我们要讨论Objective-C的内存管理机制的出发点在这里了。

经典基于引用计数的内存管理方式

​ 这里需要强调的是,大部分语言的自动内存管理都是利用Reference Count做的,Objective-C不例外。(kernel C都有kobject引用计数),所有基于引用计数对象创建的

  • 当你创建对象时,计数 = 1。
  • 当有其他对象保留(retain)它时,计数 +1。
  • 当不再需要时释放(release),计数 -1。
  • 当计数变为 0,系统调用 dealloc 销毁对象。

MRC和ARC

​ Objective-C算是历史遗产很丰富,他有两种内存管理的方式:

模式全称特点
MRCManual Reference Counting手动管理内存,程序员调用 retain / release
ARCAutomatic Reference Counting编译器自动插入 retain / release 调用。

​ 读了一下doc,我的理解是:ARC = “编译器帮你写 release”,MRC = “你自己写 release”。

Example

// Person.h
#import <Foundation/Foundation.h>

@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
- (void)sayHello;
@end

// Person.m
#import "Person.h"

@implementation Person
- (void)sayHello {
    NSLog(@"Hello, I'm %@", _name);
}
- (void)dealloc {
    NSLog(@"%@ is being deallocated", _name);
    // MRC下添加这个行
    // [super dealloc];
}
@end

MRC 模式(手动管理内存)

这种写法你要自己保证所有 retain/release 匹配,否则会内存泄漏或野指针。

// main.m
#import <Foundation/Foundation.h>
#import "Person.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init]; // ref = 1
        p.name = @"Charlie";
        [p sayHello];

        // 手动释放
        [p release]; // release, and cnt = 0, which will be delete later
    }
    return 0;
}
Hello, I'm Charlie
Charlie is being deallocated

ARC 模式(自动管理内存)

在 ARC 下,编译器自动帮咱们插入 retain/release。所以我们是 **不能手动写 [retain]/[release]/[autorelease]**的!

// main.m
#import <Foundation/Foundation.h>
#import "Person.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init]; // ARC 自动管理引用计数
        p.name = @"Charlie";
        [p sayHello];
        // 不需要写 [p release]; ARC 在作用域结束后会自动释放
    }
    return 0;
}

🧩 输出结果(相同):

Hello, I'm Charlie
Charlie is being deallocated

翻的一些doc