Objective-C关键字@property使用原理探究
@property
@property是OC开发中常用到的关键字,今天这篇文章就为它做一个较为系统全面的总结
主要包含内容
接下来我会分别解析
存取器方法
一般访问存取器方法只需要使用.propertyName即可,需要特别指定存取器方法时可通过getter=getterName与setter=setterName,具体示例如下:
// 指定getter访问名为isOpen
@property (nonatomic, assign, getter=isOpen) BOOL open;
// 指定setter方法名为setNickName:
@property (nonatomic, copy, setter=setNickName:) NSString *name;
读写权限
- readwrite:表示自动生成对应的
getter
和setter
方法,即可读可写权限,readwrite
是编译器的默认选项。 - readonly:表示只生成
getter
,不需要生成setter
,即只可读,不可以修改。
内存管理
- strong:指定与目标对象存在强(拥有)的关系,修饰对象的引用计数会+1,通常用来修饰对象类型,可变集合及可变字符串类型。当对象引用计数为0,即不被任何对象持有,对象就会从内存中释放
- assign:不改变修饰对象的引用计数,通常用来修饰基本数据类型(
NSInteger
,NSNumber
,CGRect
,CGFloat
等),也是默认属性。需要特别注意的一点是当修饰的对象的引用计数为0对象被销毁的时候,对象指针不会自动清空成为野指针,后续再次访问会产生野指针错误:EXC_BAD_ACCESS
- copy:对象会在内存中拷贝一个副本,副本引用计数为1。一般用于不可变对象的集合类型,这是为了保证进行copy操作的时候生成的都是不可变类型。 copy分深拷贝与浅拷贝,对可变与不可变对象进行copy操作结果如下:
源对象类型 | 拷贝方式 | 目标对象类型 | 拷贝类型(深|浅) |
---|---|---|---|
mutable对象 | copy | 不可变 | 深拷贝 |
mutable对象 | mutableCopy | 可变 | 深拷贝 |
immutable对象 | copy | 不可变 | 浅拷贝 |
immutable对象 | mutableCopy | 可变 | 深拷贝 |
可以总结以下两点:
对mutable对象的拷贝都是深拷贝
所有对象的copy结果都是不可变
weak:弱引用关系,修饰对象的引用计数不会增加,当修饰对象被销毁的时候,对象指针会自动置为 nil
,主要可以用于避免循环引用;weak
只能用来修饰对象类型,且是在 ARC
下新引入的修饰词,只能修饰对象,MRC
下相当于使用 assign
。
数据结构
struct SideTable {
spinlock_t slock;// 用于给原子性操作加锁
RefcountMap refcnts;// 引用计数hash表
weak_table_t weak_table;// weak对象指针hash表
}
struct weak_table_t {
weak_entry_t *weak_entries;// 存储 weak 对象信息的 hash 数组
size_t num_entries;// 数组中元素的个数,数组初始化的时候默认4个,占用达到3/4会翻倍扩容
uintptr_t mask;// 计数辅助量
uintptr_t max_hash_displacement;// hash 元素最大偏移值
};
清除weak
对象dealloc
的时候,会调用weak_clear_no_lock
函数将指向该对象的弱引用指针置为nil
,具体实现如下
// objc-weak.mm
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
// 获得 weak 指向的地址,即对象内存地址
objc_object *referent = (objc_object *)referent_id;
// 找到管理 referent 的 entry 容器
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
// 如果 entry == nil,表示没有弱引用需要置为 nil,直接返回
if (entry == nil) {
/// XXX shouldn't happen, but does with mismatched CF/objc
//printf("XXX no entry for clear deallocating %p\n", referent);
return;
}
// zero out references
weak_referrer_t *referrers;
size_t count;
if (entry->out_of_line()) {
// referrers 是一个数组,存储所有指向 referent_id 的弱引用
referrers = entry->referrers;
// 弱引用数组长度
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
// 遍历弱引用数组,将所有指向 referent_id 的弱引用全部置为 nil
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
if (*referrer == referent) {
*referrer = nil;
}
else if (*referrer) {
_objc_inform("__weak variable at %p holds %p instead of %p. "
"This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
referrer, (void*)*referrer, (void*)referent);
objc_weak_error();
}
}
}
// 从 weak_table 中移除对应的弱引用的管理容器
weak_entry_remove(weak_table, entry);
}
总结:
当一个对象被销毁时,在dealloc
方法内部经过一系列的函数调用栈,通过两次哈希查找,第一次根据对象的地址找到它所在的Sidetable
,第二次根据对象的地址在Sidetable
的weak_table
中找到它的弱引用表。弱引用表中存储的是对象的地址(作为key
)和weak
指针地址的数组(作为value
)的映射。weak_clear_no_lock
函数中遍历弱引用数组,将指向对象的地址的weak
变量全都置为nil
。
添加weak
一个被声明为__weak
的指针,在经过编译之后。通过objc_initWeak
函数初始化附有__weak
修饰符的变量,在变量作用域结束时通过objc_destroyWeak
函数销毁该变量。
id obj = [[NSObject alloc] init];
id __weak obj1 = obj;
id obj1;
objc_initWeak(&obj1,obj);
objc_destroyWeak(&obj1);
objc_initWeak
函数调用栈如下:
// NSObject.mm
1. objc_initWeak
2. storeWeak
// objc-weak.mm
3. weak_register_no_lock
4. weak_unregister_no_lock
总结:
一个被标记为__weak
的指针,在经过编译之后会调用objc_initWeak
函数,objc_initWeak
函数中初始化weak
变量后调用storeWeak
。添加weak
的过程如下:
经过一系列的函数调用栈,最终在weak_register_no_lock()
函数当中,进行弱引用变量的添加,具体添加的位置是通过哈希算法来查找的。如果对应位置已经存在当前对象的弱引用表(数组),那就把弱引用变量添加进去;如果不存在的话,就创建一个弱引用表,然后将弱引用变量添加进去。(weak相关实现较为复杂后续的文章会做专门解析)
retain:MRC
下使用,ARC
下使用strong
,用来修饰对象类型,强引用对象,其修饰对象的引用计数会 +1,不会对对象分配新的内存空间。
unsafe_unretained:同weak
类似,不会对对象的引用计数 +1,只能用来修饰对象类型,修饰的对象在被销毁时,其指针不会自动清空,指向的仍然是已销毁的对象,这时再调用该指针会产生野指针EXC_BAD_ACCESS
错误。
原子性
atomic
原子性:系统会自动给生成的 getter/setter
方法进行加锁操作; nonatomic
非原子性:系统不会给自动生成的 getter/setter
方法进行加锁操作; 设置属性函数 reallySetProperty(...)
的原子性非原子性实现如下:
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
获取属性函数 objc_getProperty(...)
的内部实现如下:
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);
总结
由上面代码可见atomic
只能对存取器方法加锁,并不能保障多线程下对对象的其他操作安全。
以上就是Objective-C关键字@property使用原理探究的详细内容,更多关于Objective-C关键字@property的资料请关注编程网其它相关文章!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341