iOS Objective-C 语言基础1 —— @property必知必会
这篇文章讲了@property相关的面试题,有些结论我已经用实际运行过的代码验证了,可放心食用。(不放心也可以自己拿去跑一跑)。面试题来源为 sunnyxxx的博客招聘一个靠谱的 iOS
@property 后面可以有哪些修饰符?
原子性
atomic:
@property使我们可以创建一个属性,系统会生成对于这个属性的 getter 和 setter函数。对这个属性的读写操作则是通过 setter 和 getter完成的。假如设置了atomic 属性,那就会对 setter 和 getter 进行加锁,在执行的时候保证只有当前函数能执行,也就是「互斥性」。这也保证了「原子性」,也就是其他线程不会读取到中间值。
但是加锁有性能上的损耗,尽量不使用,它的使用场景是多线程环境下、对一个属性进行读取和写入操作,使用atomic 能够确保同一时间只有一个线程能够修改和访问。
但是不能保证绝对的线程安全。比如在并发环境下,对一个属性进行+1 操作。A线程读取得到「100」,然后 B线程也读到「100」,A 线程写入「101」,B 线程也写入「101」。最后得到的值自然就小于 30000.
而如果使用 NSLock 进行加锁,那
self.intSource = self.intSource + 1;这一行代码的执行就能保证互斥性。也就保证了最后数据的正确性(属性是不是 atomic 的已经无影响了了,因为此时锁的作用范围变大了)
- (void)testAutomicProperty {
self.intSource = 0;
NSLock *myLock = [[NSLock alloc] init];
dispatch_queue_t queue1 = dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// dispatch_queue_t queue2 = dispatch_queue_create("555", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
// 异步的,都加到 group 中
dispatch_group_async(group, queue1, ^{
for (int i = 0; i < 10000; i++) {
[myLock lock];
self.intSource = self.intSource + 1;
[myLock unlock];
}
});
dispatch_group_async(group, queue1, ^{
for (int i = 0; i < 10000; i++) {
[myLock lock];
self.intSource = self.intSource + 1;
[myLock unlock];
}
});
dispatch_group_async(group, queue1, ^{
for (int i = 0; i < 10000; i++) {
[myLock lock];
self.intSource = self.intSource + 1;
[myLock unlock];
}
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 打印最后的结果
NSLog(@"最后的结果是:%d", self.intSource);
});
}
// 不加锁:最后的结果 <= 30000
// 加锁:结果 == 30000nonatomic:setter 和 getter 不加锁。
内存管理
strong:强引用
copy:copy 修饰的属性,在给它赋值的时候,不是直接把指针赋值,而是深拷贝,把内容赋值给它。这样做,这个属性的值就有了独属于自己的一个值(一个对象),赋值给它的对象修改了,也不影响该属性的值。
copy 常用于不可变对象(NSString、NSArray)。
既然是深拷贝,那就跟原来的对象没关系,于是对原来对象的引用计数不会有影响。
对于可变对象,比如 NSMutableString类型的对象,不能使用 copy 修饰 ta。因为 copy 出来的只能是不可变对象。如果我们给「可变对象」用了 copy,那实际上「可变对象指针」指向的是一个「不可变对象」,如果用它调用修改的方法,如appendString,就会报错,
Attempt to mutate immutable object with xxxassign:不执行 retain 操作,引用计数不会添加。用于修饰对象时,当引用对象被销毁后,如果再向 ta 发送消息,会报野指针错误。
weak:弱引用,引用对象的引用计数不会增加。当引用的对象被销毁时,weak 指针会自动置 nil,后续对 ta 发送消息,不会报野指针错误(跟 assign 属性的作用形成了明显的对比)。
一个常见的面试问题是:Runtime是怎么实现weak 属性的自动置 nil 的?
Runtime 维护了一个表格SideTables:
struct SideTable { spinlock_t slock; // 确保原子性操作的锁,虽然名字还叫 spinlock_t ,其实本质已经是 mutex_t,具体可见 objc-os 源码( `using spinlock_t = mutex_tt<LOCKDEBUG>;` ) RefcountMap refcnts; // 用来存储对象引用计数的 hash 表 weak_table_t weak_table // 存储对象 weak 引用指针的 hash 表。自动置 nil 就靠这个属性 //... };其中weak_table_t是一个哈希数组,创建 weak 指针引用的对象a 时,把 a 的内存地址作为 key,把 weak 指针作为 value 存入表中。所有 weak 指针都存进来。当销毁 a 对象时,会拿到 a的内存地址,去 weak_table_t 中找到对应 entry,把 value 逐一置 nil,然后将这个 entry 从表格中抹去。
retain:MRC环境下的 strong。修饰对象的引用计数+1。
unsafe_unretained:跟 weak 类似,但是不会自动置 nil,引用计数不 +1.
函数名字(setter 和 getter)
该修饰符可以指定 setter 和 getter 的名字。
这个修饰符的使用场景是:比如我们有一个 BOOL 类型的属性,叫 safe, 那么它默认的 getter 名字就是 safe,而用 getter 修饰符就能把 getter 的名字变成 isSafe,更加符合命名规范。
读写权限
- readwrite
- readonly
什么情况使用 weak 关键字,相比 assign 有什么不同?
- 修饰对象,用 weak。因为 weak 可以用自动置 nil
- 一般数据类型就用 assign。比如 NSInteger,NSUInteger,
怎么用 copy 关键字?
对于不可变对象,就用 copy 关键字,保证了该属性指向的值不会因为赋值对象的改变而改变。
这个写法会出什么问题: @property (copy) NSMutableArray *array;
如果向 array 对象发送修改的消息,比如addObject: 时,就会报错说Attempt to mutate immutable object with appendString:',因为 copy 存的只能是 不可变对象,也就是 NSArray.
如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter?
——有啥用呢?
能保证自己的类的对象是线程安全的。
需要实现NSCopying协议,该协议只有一个方法
- (id)copyWithZone:(NSZone *)zone;
举个例子
@interface MyObject : NSObject <NSCopying>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSMutableArray *mutableArray;
@end
@implementation MyObject
- (id)copyWithZone:(NSZone *)zone {
MyObject *copy = [[[self class] allocWithZone:zone] init];
copy.name = [self.name copyWithZone:zone]; // 对字符串属性执行深拷贝
copy.mutableArray = [self.mutableArray mutableCopyWithZone:zone]; // 对可变数组属性执行深拷贝
return copy;
}
@end@property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的 ?
本质是 ivar、getter、setter 。
ivar:Instance Variable,实例变量。
是这样添加的:自动合成auto synthesis。不仅创建 setter 和 getter,同时生成下划线开头的变量。
@protocol 和 category 中如何使用 @property ?
- @protocol 定义的@property 只能是指示如何处理属性,而不包括 setter 和 getter 的实现。
- category 中使用@property只能创建 setter 和 getter 的声明,具体的函数实现要自己借助关联对象实现,以下是一个例子
#import "NSObject+AssociatedObject.h"
@implementation NSObject (AssociatedObject)
// 使用关联对象实现属性的 getter 方法
- (NSString *)associatedProperty {
return objc_getAssociatedObject(self, @selector(associatedProperty));
}
// 使用关联对象实现属性的 setter 方法
- (void)setAssociatedProperty:(NSString *)newValue {
objc_setAssociatedObject(self, @selector(associatedProperty), newValue, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end参考资料
- 苹果文档Property Declaration Attributes: 介绍了所有修饰符
- iOS 探究 | 第一篇 属性 @property 详细探究:掘金文章,写得还蛮清楚的