属性与修饰符
问题
@property 都有哪些修饰符?atomic 和 nonatomic 的区别?copy 修饰 NSString 的原因?
答案
@property 自动合成
// 声明属性
@property (nonatomic, copy) NSString *name;
// 编译器自动生成:
// 1. 实例变量 _name
// 2. getter 方法 - (NSString *)name
// 3. setter 方法 - (void)setName:(NSString *)name
修饰符分类
| 分类 | 修饰符 | 说明 |
|---|---|---|
| 原子性 | atomic / nonatomic | 线程安全(默认 atomic) |
| 读写权限 | readwrite / readonly | 是否生成 setter(默认 readwrite) |
| 内存管理 | strong / weak / copy / assign / unsafe_unretained | 引用关系 |
| Getter/Setter | getter= / setter= | 自定义方法名 |
| Nullability | nullable / nonnull / null_resettable | Swift 桥接 |
| 类属性 | class | 类属性(非实例属性) |
atomic vs nonatomic
// atomic(默认):getter/setter 内部加锁
// 保证读写的完整性,但不保证线程安全
@property (atomic, strong) NSArray *list;
// nonatomic:不加锁,性能好
// iOS 开发几乎都用 nonatomic
@property (nonatomic, strong) NSArray *list;
atomic ≠ 线程安全
atomic 只保证 getter/setter 的原子性(读写不会被撕裂),但不保证多线程操作的线程安全。例如:
// 即使用 atomic,以下代码也不是线程安全的
// 线程 A:self.array = @[@1, @2];
// 线程 B:self.array = @[@3, @4];
// 线程 C:NSLog(@"%@", self.array); // 可能是 A 或 B 的结果
// 更危险的场景
// 线程 A:[self.mutableArray addObject:@1];
// 线程 B:[self.mutableArray removeLastObject];
// → 可能崩溃,atomic 不保护方法调用间的线程安全
要保证线程安全,需要使用 @synchronized、NSLock、GCD 串行队列等。
copy 修饰 NSString
@property (nonatomic, copy) NSString *name;
// 为什么用 copy?
NSMutableString *mstr = [NSMutableString stringWithString:@"Alice"];
// 如果用 strong:
person.name = mstr;
[mstr appendString:@" Bob"];
NSLog(@"%@", person.name); // "Alice Bob" ← name 被意外修改!
// 如果用 copy:
person.name = mstr;
[mstr appendString:@" Bob"];
NSLog(@"%@", person.name); // "Alice" ← 安全,copy 了一份不可变副本
copy 的规则
NSString→copy返回不可变字符串(如果本身是 NSString 则不会真正拷贝——浅拷贝优化)NSMutableString→copy返回 NSString(不可变副本)NSArray/NSDictionary同理:用copy防止外部可变版本影响
深拷贝 vs 浅拷贝
| 源类型 | copy | mutableCopy |
|---|---|---|
| NSString | 浅拷贝(指针拷贝) | 深拷贝 → NSMutableString |
| NSMutableString | 深拷贝 → NSString | 深拷贝 → NSMutableString |
| NSArray | 浅拷贝(指针拷贝) | 深拷贝(容器)→ NSMutableArray |
| NSMutableArray | 深拷贝(容器)→ NSArray | 深拷贝(容器)→ NSMutableArray |
"深拷贝容器"的含义
[mutableArray copy] 会创建新的容器(NSArray),但容器内的元素仍然是指针共享(即集合层面的浅拷贝)。要实现元素级别的深拷贝,需要 initWithArray:copyItems:YES 或 NSKeyedArchiver。
@synthesize 与 @dynamic
// @synthesize:自动生成 getter/setter 和实例变量
@synthesize name = _name; // 现在默认自动生成,很少手写
// @dynamic:告诉编译器不自动生成,由开发者自行实现
@dynamic name;
// 常见于 CoreData 的 NSManagedObject 子类
// getter/setter 在运行时由 CoreData 动态提供
常见面试问题
Q1: @property 的本质是什么?
答案:@property = ivar(实例变量) + getter + setter。编译器自动合成实例变量 _propertyName,并根据修饰符生成对应语义的 getter 和 setter 方法。
Q2: 什么情况下需要用 strong 而不是 copy?
答案:
- 属性类型本身是可变类型时用
strong,如NSMutableArray、NSMutableString- 因为
copy会变为不可变类型,对可变对象调用addObject:等方法会崩溃
- 因为
- 属性类型是不可变类型时用
copy,防止外部传入可变子类修改内部数据
Q3: self.name 和 _name 的区别?
答案:
self.name:调用 getter/setter 方法,会触发 KVO,遵循内存管理语义_name:直接访问实例变量,不触发 KVO,不走内存管理(ARC 下仍有引用计数管理)
在 init 和 dealloc 中推荐用 _name 直接访问,避免子类重写 setter 的副作用。
Q4: 如何让一个属性在 .h 中 readonly,在 .m 中 readwrite?
答案:
// .h 对外只读
@interface Person : NSObject
@property (nonatomic, readonly, copy) NSString *name;
@end
// .m Extension 中重新声明为 readwrite
@interface Person ()
@property (nonatomic, readwrite, copy) NSString *name;
@end