1. @property的作用
在类和扩展中
会自动设置@synthesize propertyName = _propertyName
来告诉编译器自动生成getter、setter、指定实例变量名
在分类中
不会自动生成getter、setter方法,不会生成实例变量
在协议中定义,并且类遵循该协议
@interface PropertyUsage : NSObject <PropertyTestProtocol>
@property (nonatomic, strong) NSObject *subTest;
@end
@interface PropertyUsage (TestCategory)
@property (nonatomic, strong) NSObject *categoryTest;
@end
默认不会自动生成getter、setter方法,不会指定实例变量名;编译器会提示需要去手动@synthesize
当手动@synthesize之后,就会自动生成getter、setter、及实例变量
在子类中定义同名的属性
@interface SubPropertyUsage : PropertyUsage
@property (nonatomic, strong) NSObject *subTest;
@end
此时编译器也会警告,说getter、setter方法会在父类中实现,同时也不会生成新的实例
加上@synthesize subTest = _subTest_;
这里不能用_subTest编译器会报错跟父类的冲突了Property 'subTest' attempting to use instance variable '_subTest' declared in super class 'PropertyUsage'
我们debug下打印子类的方法列表
(lldb) po [[SubPropertyUsage new] _shortMethodDescription]
<SubPropertyUsage: 0x6000009ae0d0>:
in SubPropertyUsage:
Properties:
@property (retain, nonatomic) NSObject* subTest; (@synthesize subTest = _subTest_;)
Instance Methods:
- (void) setSubTest:(id)arg1; (0x10a9824b0)
- (id) subTest; (0x10a982460)
- (void) .cxx_destruct; (0x10a982520)
in PropertyUsage:
Properties:
@property (retain, nonatomic) NSObject* subTest; (@synthesize subTest = _subTest;)
@property (readonly) unsigned long hash;
@property (readonly) Class superclass;
@property (readonly, copy) NSString* description;
@property (readonly, copy) NSString* debugDescription;
@property (copy, nonatomic) NSString* addressFormate; (@synthesize addressFormate = _addressFormate;)
Instance Methods:
- (void) setAddressFormate:(id)arg1; (0x10a9822d0)
- (void) setSubTest:(id)arg1; (0x10a982370)
- (id) addressFormate; (0x10a982280)
- (void) testPropertyUsage; (0x10a981fc0)
- (id) subTest; (0x10a982330)
- (void) .cxx_destruct; (0x10a9823d0)
(NSObject ...)
可以看到子类中也有一套getter、setter,并且生成了新的实例
结论:在子类中定义同名的属性,默认是不会生成新的getter、setter、实例;当使用@synthesize指定一个跟父类不一样的实例名的时候,会生成新的getter、setter、实例
2. 修饰符的作用
2.1. readwrite
可读写权限,也是属性的默认修饰符
2.2. readonly
只读权限,编译器会只生成getter方法不生成setter方法
2.3. getter=
指定属性的get方法
小测试以下代码打印tmpName会是什么结果
@interface PropertyUsage ()
@property (nonatomic, setter=_hcSetName:, getter=_hcGetName, copy) NSString *name;
@end
@implementation PropertyUsage
@synthesize name = _hcTestName;
@end
PropertyUsage *tmpPU = [PropertyUsage new];
tmpPU.name = @"HC";
NSString *tmpName = [tmpPU valueForKey:@"name"];
代码执行到tmpName会异常退出;
kvo的查找顺序:
- 先查询是否有
get<Key>
,<key>
,is<Key>
, or_<key>
方法,有的话就调用;- 看实例的accessInstanceVariablesDirectly方法,返回YES,就去查找实例变量按照这个顺序
_<key>``_is<Key>``<key>``is<Key>
,有就直接返回其值- 以上2部都没有匹配到,则会调用valueForUndefinedKey:,抛出一个异常
官方kvo文档
由于这里我们自定义了getter方法不是按照kvo查找规则的,并且实例变量的命名(name = _hcTestName)也不是按照kvo查找规则内的,所以找不到最终就异常了
2.4. setter=
指定属性的set方法
2.5. strong
强引用修饰变量,内部调用了objc_storeStrong
,先判断对象本身的值是否是跟传入的值一致,一致就返回,否则就retain新值,将新值赋给对象,释放旧值
void
objc_storeStrong(id *location, id obj)
{
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
objc_release(prev);
}
2.6. retain
手动管理内存是使用的强引用修饰符;内部会调用void objc_setProperty_nonatomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
赋值的方式跟strong一样
void objc_setProperty_nonatomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
reallySetProperty(self, _cmd, newValue, offset, false, false, false);
}
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
2.7. copy
拷贝;内部会调用objc_setProperty_nonatomic_copy
void objc_setProperty_nonatomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
reallySetProperty(self, _cmd, newValue, offset, false, true, false);
}
可以看到跟retain的差异就是调用reallySetProperty
的时候有个参数copy传了true;内部则对传入的newValue进行了copy操作newValue = [newValue copyWithZone:nil];
也就是说用copy修饰那么得对象实现了协议NSCopying,否则就会找不到copyWithZone方法导致崩溃
小测试:一个NSArray类型的属性使用copy修饰,当对属性赋值以不可变数组时会产生副本吗
@interface PropertyUsage : NSObject
@property (nonatomic, copy) NSArray *testCopyProperty;
@end
PropertyUsage *testPU = [PropertyUsage new];
NSArray *tmpArray = [NSArray array];
testPU.testCopyProperty = tmpArray;
答案是不会;对于不可变的集合,它的copy实现就是直接返回self;由于本身是不可变的所以没有必要再开辟一块空间去存储浪费空间,只需要指向同一快内存就可以了。
2.8. assign
直接赋值不产生强引用,一般是用于非OC的基础数据类型,如果是对OC对象使用assign,则对象会在被赋值之后就释放;但是指针还指向那块被释放的对象的空间;如果后续这块空间被分配给了其他的对象,那么就会产生野指针异常
2.9. unsafe_unretained
直接赋值不产生强引用,效果跟assign类似,用来修饰OC的对象
2.10. weak
弱引用;当赋值的时候内部会调用objc_storeWeak
,将变量的指针存储在以变量为key的weak表中,并将变量的isa的weakly_referenced
标记位置位1,当变量释放的时候发现标记为是1就去弱引用表中找到对象的entry将其清除,以达到自动置为nil
2.11. noatomic or atomic
非原子和原子,我们看到在属性赋值的函数reallySetProperty
以及获取属性值的函数objc_getProperty
中会根据是否是原子操作,而判断是否加上一个os_unfair_lock
,保证了多线程读写的操作是安全的,但是同时也因为加锁带来了额外的开销;这个也不能完全保证多线程的安全,比如数组属性申明原子操作,读写是安全的,那么我多线程去修改数组的元素了;所以我们一般都是用非原子操作,如果要线程安全,就自己对数据进行加锁处理保证读写改的安全
如果不显示指定,默认是atomic的
// reallySetProperty代码片段
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
if (offset == 0) {
return object_getClass(self);
}
// Retain release world
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;
// Atomic retain release world
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
id value = objc_retain(*slot);
slotlock.unlock();
// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
return objc_autoreleaseReturnValue(value);
}
spinlock_t名字虽然是自旋锁,但是内部使用的是os_unfair_lock
;由于自旋锁的优先级反转问题,苹果从iOS10开始底层的实现已经用os_unfair_lock
替换了OSSpinLock
不再安全的 OSSpinLock
StripedMap<spinlock_t> PropertyLocks;
using spinlock_t = mutex_tt<LOCKDEBUG>;
class mutex_tt : nocopy_t {
os_unfair_lock mLock;
// 省略其他代码
}