assign 和 weak 的区别?
在 iOS 开发(Objective-C)中,assign 和 weak 都是用于修饰属性(Property)的关键字,它们的主要区别在于对象释放后的行为以及适用的数据类型。
以下是核心区别的详细总结:
1. 核心区别总结
| 特性 | weak | assign |
|---|---|---|
| 对象释放后 | 自动置为 nil (安全) | 保持原地址不变,变成悬垂指针 (不安全) |
| 适用类型 | 仅限 Objective-C 对象 (id, UIView, etc.) | 基本数据类型 (int, float, BOOL) + C数据结构 |
| 引用计数 | 不增加引用计数 | 不增加引用计数 |
| 底层实现 | 依赖 Runtime 的哈希表维护 | 简单的指针地址赋值 |
2. 详细原理解析
assign
- 定义:
assign是简单的赋值操作。 - 内存管理:它不会改变对象的引用计数。
- 适用场景:主要用于修饰基本数据类型(如
NSInteger,CGFloat,BOOL)和 C 数据结构(如struct,enum)。 - 用于对象的危险性(悬垂指针/野指针):
- 如果在 ARC 下用
assign修饰一个对象,当该对象被释放(dealloc)后,指针变量依然指向那块内存地址。 - 如果你再次访问这个属性,就会访问一块已经回收的内存,导致 Crash (EXC_BAD_ACCESS)。
- 比喻:你记住了某人的家庭住址。房子拆迁了,你手里还拿着地址。如果你按地址找过去,可能会掉进废墟坑里(崩溃)。
- 如果在 ARC 下用
weak
- 定义:
weak表示一种“非拥有”关系。 - 内存管理:它不会改变对象的引用计数。
- 适用场景:仅用于 Objective-C 对象。常用于解决循环引用(Retain Cycle)问题,例如
delegate代理属性或 Block 中的self。 - 安全性(自动置 nil):
- 当
weak指向的对象被销毁时,Runtime 会自动将这个指针变量赋值为nil。 - 在 OC 中,向
nil发送消息是安全的(什么都不会发生),所以不会导致 Crash。 - 比喻:你记住了某人的家庭住址,但这个地址写在一张魔法纸上。如果房子拆迁了,魔法纸上的字会自动消失(变成空白/nil)。你再看纸,上面什么都没有,你就知道不用去了。
- 当
3. 底层实现原理(面试加分项)
assign 的实现:
非常简单,就是内存地址的拷贝。weak 的实现:
Runtime 维护了一张全局的 Weak 表(Hash Table)。- Key:被引用对象的内存地址。
- Value:一个数组,存储了所有指向该对象的
weak指针的地址。 - 当对象调用
dealloc释放时,Runtime 会去查这张表,找到所有指向它的weak指针,将它们统一置为nil,然后从表中删除该记录。
4. 常见代码示例
正确用法:
plaintext
// 基本数据类型用 assign
@property (nonatomic, assign) NSInteger age;
// 代理通常用 weak 避免循环引用
@property (nonatomic, weak) id<MyDelegate> delegate;
// UI控件(如果是 XIB/Storyboard 拖拽的)通常用 weak,因为 view hierarchy 已经强引用了它
@property (nonatomic, weak) IBOutlet UILabel *titleLabel;
错误用法(会导致 Crash):
plaintext
// 错误:用 assign 修饰对象
@property (nonatomic, assign) NSString *name;
// 场景:
self.name = [NSString stringWithFormat:@"Hello"]; // name 指向一个对象
// ... 稍后该字符串对象引用计数归零被释放 ...
NSLog(@"%@", self.name); // 崩溃!因为 self.name 还是指向原来的地址,但内存已回收
一句话总结
assign 适用于基本数据类型,对象释放后变野指针;weak 适用于对象,对象释放后自动变 nil。