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 asupersetof the C programming language and providesobject-oriented capabilitiesand 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对象的语法糖。不用写又臭又长的构造罢了
| 出现位置 | 作用 | 对应类型 | 举例 | 备注 |
|---|---|---|---|---|
@autoreleasepool | Objective-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 usingnewto 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算是历史遗产很丰富,他有两种内存管理的方式:
| 模式 | 全称 | 特点 |
|---|---|---|
| MRC | Manual Reference Counting | 手动管理内存,程序员调用 retain / release。 |
| ARC | Automatic 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