@property
@property修饰的变量,会通过编译器加入@synthesize自动合成为ivar+getter+setter(property_t),而且会生成成员变量,变量的名字就是_+属性名字,当然,不想用默认的带下划线的,你也可以通过@synthesize test = myTest指定实例变量名。@synthesize test = _test主要用于重写了setter方法、override父类、dynamic等不会自动合成ivar和存取器的情况时,做一下手动绑定。
在category中使用@property:不会添加ivar,不会有成员变量,只会生成getter/setter的方法声明,所以需要通过get/setAssociatedObject自己实现。
注1:成员变量 = 实例变量(id) + 基本类型(int)。
注2:使用@dynamic可以取消自动合成,自己实现setter/getter方法,没实现将会crash。
weak / strong / copy
weak原理
每一个有弱引用的对象,系统都维护一个表SideTable中的weak_table,记录它所有的弱引用指针地址而不增加引用计数,引用计数为0时找到这张表里所有的弱引用指针,置为nil。
对象对应的sidetable→weak_table→对象的地址作为key→weak_entries(weak_entry_t结构体中的inline数组)。
注:__weak修饰的变量会自动加入@auotreleasepool中延长生命周期至当前Runloop结束,否则创建即销毁。
weak实现的流程
声明变量默认属性是strong,weak需要用__weak标记。转汇编可知会调用objc_initWeak()初始化一个新的weak指针指向新的地址;然后内部调用storeWeak函数更新指针指向,真正实现了weak引用;释放时调用clearDeallocating遍历weak_table中,对象对应的weak_entry设为nil,然后删除entry,清理对象记录。
storeWeak函数实现
objc_storeWeak(&a, b) // b的内存地址作为key,weak修饰的属性变量a的内存地址&a作为value注册到weak表中,当b为nil时,则把&a从weak表中删除。
总结:获取新旧对象的两个弱引用计数表,lock,调用weak_unregister_no_lock把weak指针从旧表中删除,调用weak_register_no_lock把weak指针添加到新对象的新表,并把新对象设置为弱引用状态,把地址赋给weak指针。
注:weak_entry_t提供了增加和删除weak指针的核心方法。
注:bool objc_object::sidetable_isWeaklyReferenced()函数实现中,result = it->second & SIDE_TABLE_WEAKLY_REFERENCED; // weak修饰的独享引用计数会被设置为SIDE_TABLE_WEAKLY_REFERENCED,虽然和strong共用一个sidetable,但不参与计数表的计算。setWeaklyReferenced同理。
weak和assign
修饰对象:对象 / 基本数据类型(也可以修饰对象)
赋值方式:赋值引用(地址) / 赋值 值
引用计数:都不影响引用计数
对象销毁后:自动置nil / 不变(野指针)
给现有类添加weak属性(AssociatedObject):引用计数器不变(OBJC_ASSOCIATION_ASSIGN),对象销毁后自动置nil(捕获对象的销毁时机dealloc方法)。
strong
底层调用objc_storeStrong(*location, obj)函数,location是指针,obj是内存中的对象,内部实现:先判断id prev = *location,obj == *location retrun;,指针之前指向的地址不是当前对象→再执行retain引用计数+1→*location = obj,strong指针指向对象→objc_release(prev),释放prev指针对上一个对象的引用。
copy
copy关键字修饰的property属性的setter方法有个很重要的步骤:_copyProperty = [copyProperty copy];会做一次深拷贝,并把可变类型为不可变。
注:copy除了作为关键字,还有copy方法,不可变浅拷贝,可变深拷贝。而mutableCopy方法全是对对象进行深拷贝。
strong和copy的总结:修饰不可变类型时,都只做浅拷贝;修饰可变时,strong引用计数+1,copy深拷贝,指向不可变新对象。所以copy多用于修饰NSString、NSArray等有可变类型的不可变类型,避免赋值成可变类型,造成影响。
__unsafe_unretained
修饰非指针(修饰指针会造成野指针)的简单数据类型(如NSInteger,id,weak不行),等价assign,不需要weak的各种检查逻辑有一定的性能提升(对象释放时会查散列表删除weak指针)。可以理解在ARC时代,iOS系统向下兼容而存在的关键字。
AssociatedObject 关联对象
应用:1、可以在不改变源码的情况下为类添加实例变量(不属于类,存在一个全局表中);2、可以给分类添加成员变量(即实现category添加的属性的set/get方法):objc_setAssociatedObject和objc_getAssociatedObject;3、KVO中使用关联对象作为观察者可以避免循环引用。
底层原理:不是存在对象中的,而是通过AssociationsManager全局管理了一个AssociationsHashMap *_map,全局表中存储着所有对象的关联对象表ObjectAssociationMap,表内存着ObjectionAssociation关联对象的value和policy。通过运行时的方式添加和移除(=nil自动删除)。
关联对象的内存管理:policy枚举(assign,strong,copy,nonatomic,atomic),objc_setAssociatedObject时会指定policy,根据这个枚举对对象进行retain/copy或weak的方式进行内存管理。
相关问题
weak的实现原理,SideTable结构是怎样的
weak修饰的指针变量指向对象时不会增加对象的引用计数,而是把当前对象的所有weak指针维护在一个全局的hash表(SideTable)中,以对象为key,weak指针为value,对象释放时会遍历清除weak指针;storeWeak传入weak指针和对象时,会解除weak指针和旧对象的绑定,并完成与新的弱引用表的绑定。
全局有多个SideTable,每个SideTable内储存了许多对象的引用计数refcnts表和weak表,weak表中的weak_table_t结构体中的weak_entries,维护了对象所有的weak指针。
strong的实现
storeStrong函数:先判断指针之前指向的是否是当前对象,否:引用计数+1,*location = obj指针指向当前对象,objc_release(prev)释放指针之前指向的的对象。
@property
编译器会加入@synthesize自动合成为property_t = ivar+getter+setter,而且会自动生成成员变量ivar名字是property,而属性的名字就是_property。可以用@synthesize property = myProperty给变量自定义的属性名。
category中的@property:因为对象的内存布局在编译阶段已经确定,添加成员变量会破坏内存布局,所以分类中的@property只是添加到属性列表(properties),没有ivar和setter/getter。所以一般声明后,通过关联对象实现属性的setter/getter。
protocol中的@property:协议中其实也可以定义属性,需在.m中用@synthesize手动同步属性;或手动实现setter/getter方法。
@synthesize和@dynamic
@synthesize绑定ivar和存取器生成属性,一般编译器自动生成,可用于标记指定实例变量名;还可用于重写setter/getter,override父类属性和protocol中定义属性等,需要手动绑定存取器的情况。
@dynamic标记自己实现属性的setter/getter方法,而不使用自动合成
关联对象的原理和应用
AssociationsManager对象通过一张全局hash表维护,也是通过对象的指针地址作为key,value是所有的关联对象。多用于动态添加实例变量(分类中添加成员变量),其实变量不属于对象。
关联对象通过添加时的policy入参关键字决定内存管理方式(copy/strong/atomic等),设置成assign即是一个weak的关联对象了)
什么时候用__unsafe_unretained
__unsafe_unretained修饰的指针指向的对象在释放时,不需要weak的各种检查逻辑有一定的性能提升(对象释放时会查散列表删除weak指针),但要注意指针变量的作用范围,否则会造成野指针(适合局部临时使用如循环中)。可以理解为等价assign,iOS向下兼容的关键字。
网友评论