跳到主要内容

属性与修饰符

问题

@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/Settergetter= / setter=自定义方法名
Nullabilitynullable / nonnull / null_resettableSwift 桥接
类属性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 不保护方法调用间的线程安全

要保证线程安全,需要使用 @synchronizedNSLock、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 的规则
  • NSStringcopy 返回不可变字符串(如果本身是 NSString 则不会真正拷贝——浅拷贝优化)
  • NSMutableStringcopy 返回 NSString(不可变副本)
  • NSArray / NSDictionary 同理:用 copy 防止外部可变版本影响

深拷贝 vs 浅拷贝

源类型copymutableCopy
NSString浅拷贝(指针拷贝)深拷贝 → NSMutableString
NSMutableString深拷贝 → NSString深拷贝 → NSMutableString
NSArray浅拷贝(指针拷贝)深拷贝(容器)→ NSMutableArray
NSMutableArray深拷贝(容器)→ NSArray深拷贝(容器)→ NSMutableArray
"深拷贝容器"的含义

[mutableArray copy] 会创建新的容器(NSArray),但容器内的元素仍然是指针共享(即集合层面的浅拷贝)。要实现元素级别的深拷贝,需要 initWithArray:copyItems:YESNSKeyedArchiver

@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,如 NSMutableArrayNSMutableString
    • 因为 copy 会变为不可变类型,对可变对象调用 addObject: 等方法会崩溃
  • 属性类型是不可变类型时用 copy,防止外部传入可变子类修改内部数据

Q3: self.name 和 _name 的区别?

答案

  • self.name:调用 getter/setter 方法,会触发 KVO,遵循内存管理语义
  • _name:直接访问实例变量,不触发 KVO,不走内存管理(ARC 下仍有引用计数管理)

initdealloc 中推荐用 _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

相关链接