iOS 基础知识回顾——关于 property

目录:

  • 本质
  • 修饰符
  • synthesize, dynamic
  • KVO 与 Property

@property 本质:

概述:

@property = ivar + getter + setter

@property 其实是属性的声明标识符,提供成员变量的访问方法的声明、控制成员变量的访问权限、控制多线程时成员变量的访问环境
“属性” (property)作为 Objective-C 的一项特性,主要的作用就在于封装对象中的数据。 Objective-C 对象通常会把其所需要的数据保存为各种实例变量。实例变量一般通过“存取方法”(access method)来访问。其中,“获取方法” (getter)用于读取变量值,而“设置方法” (setter)用于写入变量值。这个概念已经定型,并且经由“属性”这一特性而成为 Objective-C 2.0 的一部分。 而在正规的 Objective-C 编码风格中,存取方法有着严格的命名规范。 正因为有了这种严格的命名规范,所以 Objective-C 这门语言才能根据名称自动创建出存取方法。

ivar,getter,setter 是什么?

  • ivar:作为变量存放的载体,是真正存放变量的(不完全正确,可能是指针),和其他语言的类下的变量含义一样
    • 关于 ivar 更多信息见 sunnyxx 大神 <a href="http://blog.sunnyxx.com/2015/09/13/class-ivar-layout/">Objective-C Class Ivar Layout 探索</a>
  • getter:变量访问方法。变量的访问方法都可以称之为 getter。@property 自动的生成的 getter 是根据变量名有关的。
  • setter:变量的修改/设置方法。变量的修改设置方法都可以称之为 setter。@property 自动的生成的 getter 是根据变量名有关的。

代码 @property NSString *firstName 做了什么?

前面提到 @perperty 是 ivar,getter,setter 的集合,他们的命名是有规则的。

  • 命名规则/规范: ivar,getter,setter
  • ivar 名: _firstName
  • getter 名:firstName
  • setter 名: setFirstName
  • 其他默认行为:
    其他的默认行为和修饰符相关的,见修饰符的影响

修饰符的影响:

  • atomic:setter,getter 中添加 spinlock 来确定操作的原子性。atomaic 这个是默认行为。nonatomic 则不会添加 spinlock 相关代码
  • readwrite:同时生成 getter,setter 方法,
  • readonly:仅仅生成 getter
  • strong,weak,assign:控制编译器自动添加 arc 相关代码。细节见修饰符
  • retain, copy, assign:控制 setter 对于入参的处理。细节见修饰符

property 与 protocol,category:

  1. 是否可以使用?
    答:在 protocol 和 category 都可以使用

  2. @perperty 究竟做了什么?
    答:在 protocol 中只是声明了 setter,getter 方法;在 category 也只是声明了 setter 和 getter 方法,没有添加 ivar。

  3. 如何在 category 中添加 变量?
    答:通过 objc_setAssociatedObject,objc_getAssociatedObject 实现添加关联变量。

修饰符:

修饰符处理的对象其实是 getter,setter,定义了编译器对于 getter,setter 的合成相关操作

atomic与nonatomic

  • atomic:默认是有该属性的,这个属性是为了保证程序在多线程情况,编译器会自动生成一些互斥加锁代码,避免该变量的读写不同步问题。

  • nonatomic:如果该对象无需考虑多线程的情况,请加入这个属性,这样会让编译器少生成一些互斥加锁代码,可以提高效率。

  • 注意:

  • 针对的是指 getter,setter,而不是说 ivar

  • 是通过自旋锁 SpinLock 来实现原子的。iOS 上默认是 atomic。

readwrite与readonly

  • readwrite:这个属性是默认的情况,会自动为你生成存取器。
  • readonly:只生成getter不会有setter方法。
  • readwrite、readonly这两个属性通常是用来控制成员变量的访问权限。

strong与weak:

  • strong:强引用,也是我们通常说的引用,其存亡直接决定了所指向对象的存亡。如果不存在指向一个对象的引用,并且此对象不再显示在列表中,则此对象会被从内存中释放。对于引用计数而言,引用计数会增加

  • weak:弱引用,不决定对象的存亡。即使一个对象被持有无数个弱引用,只要没有强引用指向它,那么还是会被清除。引用计数不会改变,但是会把指针添加到对象的 weak table 中,为 nil 的时候将本指针也设置为 nil。

    • weak runtime 实现:属性 ivar 的具体设置函数,我们假定为objc_storeWeak(&a, b) 函数
      objc_storeWeak 函数把第二个参数--赋值对象(b)的内存地址作为键值key,将第一个参数--weak修饰的属性变量(a)的内存地址(&a)作为value,注册到 weak 表中。如果第二个参数(b)为0(nil),那么把变量(a)的内存地址(&a)从weak表中删除,
      你可以把objc_storeWeak(&a, b)理解为:objc_storeWeak(value, key),并且当key变nil,将value置nil。
      在b非nil时,a和b指向同一个内存地址,在b变nil时,a变nil。此时向a发送消息不会崩溃:在Objective-C中向nil发送消息是安全的。
  • strong与retain功能相似;weak与assign相似,只是当对象消失后weak会自动把指针变为nil;

assign、copy、retain:

  • assign:对于基本类型,默认是该修饰符(非基本类型默认是 strong)。setter方法直接赋值,不进行任何retain操作,不改变引用计数。一般用来处理基本数据类型。

  • retain:释放旧的对象(release),将旧对象的值赋给新对象,再令新对象引用计数为1。我理解为指针的拷贝,拷贝一份原来的指针,释放原来指针指向的对象的内容,再令指针指向新的对象内容。

  • copy:与retain处理流程一样,先对旧值release,再copy出新的对象,retainCount为1.为了减少对上下文的依赖而引入的机 制。我理解为内容的拷贝,向内存申请一块空间,把原来的对象内容赋给它,令其引用计数为1。对copy属性要特别注意:被定义有copy属性的对象必须要 符合NSCopying协议,必须实现- (id)copyWithZone:(NSZone *)zone方法。

  • 拷贝有浅拷贝,深拷贝:oc 中主要表现为的 copy,mutableCopy。关于 copy 更多信息见<a href="https://www.zybuluo.com/MicroCai/note/50592">iOS 集合的深复制与浅复制</a>


    copy 总结.png
  • 直接使用:

  • 使用assign: 对基础数据类型 (NSInteger,CGFloat)和C数据类型(int, float, double, char, 等等)

  • 使用copy: 对NSString

  • 使用retain: 对其他NSObject和其子类

getter setter:

  • getter:是用来指定get方法的方法名
  • setter:是用来指定set访求的方法名

在@property的属性中,如果这个属性是一个BOOL值,通常我们可以用getter来定义一个自己喜欢的名字,例如:

  • @property (nonatomic, assign, getter=isValue) boolean value;
  • @property (nonatomic, assign, setter=setIsValue) boolean value;

retain, copy, assign区别

在 OC 中的内存管理的本质其实是通过引用计数来完成的,每个对象都有一个记录当前对象引用的次数,当引用次数为0的时候,才会去释放对象。 下面假设有新产生一个对象,a 作为指向这个对象的变量(指针),现将 a 使用不同的方式复制给 变量 b,

  • assign: a 和 b指向同一块内存。assign 指示引用计数不增加,当前对象的引用计数依然为 1。而当 a 设置为 nil 的时候,由于引用计数变为 0,对象已经被释放,而 b 完全不知道,已经指向原来对象的地址,而原来的对象已经被是释放了,b 成为野指针。那么b在使用这块内存的时候会引起程序crash掉。
  • retain: 在将 a 赋值给 b 的时候,a 和 b 都是指向同一个对象,该对象引用计数会 +1,变为 2。所以即使在 a = nil 的时候,对象的引用计数为1,所以依然不会释放,所以访问不会出现问题。在 b = nil 的时候,引用计数才会 0,才会被释放。
  • copy:和上面 assign 和 retain 完全不同。assign 和 retain都是指向同一个对象,也就是共享一块内存。而 copy 指示,在复制的时候,并不是简单的赋值,而是拷贝原来的对象,然后将 b,指向这个拷贝生成的对象。由于 a 和 b 是两个不同的对象,仅仅是在值/内容相等。a = nil,对于 b 完全没有影响。

nonnull,nullablenull_resettable,_Null_unspecified:

  • nonnull:不能为空
  • nullable:表示可以为空
  • null_resettable:
  • get:不能返回空
  • set可以为空
  • 注意:如果使用null_resettable,必须 重写get方法或者set方法,处理传递的值为空的情况
  • _Null_unspecified:不确定是否为空

assign 与 weak 比较:

  1. 修饰变量类型的区别
  • weak 只可以修饰对象。如果修饰基本数据类型,编译器会报错-“Property with ‘weak’ attribute must be of object type”。
  • assign 可修饰对象,和基本数据类型。当需要修饰对象类型时,MRC时代使用unsafe_unretained。当然,unsafe_unretained也可能产生野指针,所以它名字是”unsafe_”
  1. 是否产生野指针的区别
  • weak 不会产生野指针问题。因为weak修饰的对象释放后(引用计数器值为0),指针会自动被置nil,之后再向该对象发消息也不会崩溃。 weak是安全的。
  • assign 如果修饰对象,会产生野指针问题;如果修饰基本数据类型则是安全的。修饰的对象释放后,指针不会自动被置空,此时向对象发消息会崩溃。

@synthesize和@dynamic

property 是有 ivar ,getter,setter 构成。而这两个关键字处理的的是这三者间的关系的。编译器在编译期间完成。

@synthesize:自动合成

  • 自动合成 getter, setter:注意名字编译器会自动检查,如果没有手动实现,会自动添加 getter,setter,如果实现了,则不做处理
  • 具体合成那些其实还和 ready write 相关修饰符相关的,默认为 readwrite
  • 指定变量名字:@synthesize firstName = _myFirstName;
  • 合成规则:假设声明为 @property NSString *firstName;
  • 变量 ivar 名字ivar 变量名称为 _firstName
  • getter, setter:getter 变量名称为 firstName, setter 为 setFirstName

那些情况下自动合成会失效

  • 同时重写了 setter 和 getter 时
  • 重写了只读属性的 getter 时
  • 使用了 @dynamic 时
  • 在 @protocol 中定义的所有属性
  • 在 category 中定义的所有属性
  • 重载的属性

@dynamic:禁止自动合成

  • 禁止自动合成getter, setter:告诉编译器属性的 setter 与 getter 方法由用户自己实现,不自动生成。(当然对于 readonly 的属性只需提供 getter 即可)

  • 注意:属性被声明为 @dynamic var,然后你没有提供 @setter方法和 @getter 方法,编译的时候没问题,但是当程序运行到 instance.var = someVar,由于缺 setter 方法会导致程序崩溃; 或者当运行到 someVar = var 时,由于缺 getter 方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定

  • 使用场景:
    在CoreData的NSManagedObject类使用的某些。如果你想这些情况下,声明和使用属性,但要避免缺少方法在编译时的警告,你可以使用@dynamic动态指令,而不是@synthesize合成指令。

KVO:

什么是KVO?

KVO(Key Value Observing, 键值观察)是Objective-C对观察者模式的实现,每次当被观察对象的某个属性值发生改变时,注册的观察者便能获得通知。
使用:三个基本步骤:

  1. 注册观察者,指定被观察对象的属性:
[obj addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];

其中,person即为被观察对象,它的name属性即为被观察的属性。

  1. 在观察者中实现以下回调方法:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
  1. 移除观察者

KVO 触发:

概述:触发依赖与以下三个函数:执行顺序如下

  1. willChangeValueForKey: 在一个被观察属性发生改变之前调用,记录旧的值
  2. observeValueForKeyPath:ofObject:change:context: 当改变发生后,调用该函数
  3. didChangeValueForKey: 在 2 完成后执行

触发方式分类:

  • 自动触发:注册KVO,在 value 发生变化的时候自动触发。property 相关属性属性默认都是自动触发的
  • 手动触发: value 的变化,通过代码控制触发。

手动触发 KVO:

  • 手动触发 KVO,就是通知 value 变化。主要有两种情况
    property 变量:默认情况下 property 的是自动触发,手动触发方法与步骤:

    • 重载 + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key: key 为手动触发的 key 的返回 NO,其他调用父方法
    • 重写 setter 方法:在修改值前后分别调用,
      willChangeValueForKey: 和 didChangeValueForKey:
  • 注意:

    • 上面步骤缺一不可
    • 如果步骤 1 缺失, 会导致 KVO 代码执行两次
  • 非 property 变量:对于 property 变量,编译器会自动合成 getter 和 setter
    ,而KVO 是运行期间的,依赖与 runtime。所以KVO 对于名字有严格的要求(setter)

  • 方法:只需要在自己的 setter 中调用 willChangeValueForKey: 和 didChangeValueForKey: 即可。

手动触发 KVO 使用场景:

  • 自定义 setter 实现,setter 使用其他名字

  • category 分类的添加的属性

  • 使用:objc_setAssociatedObject,objc_getAssociatedObject 添加关联变量

  • 关于 category 详情见 <a href="http://blog.sunnyxx.com/2014/03/05/objc_category_secret/">objc category的秘密</a>

KVO 实现原理(Apple的实现)

核心:isa + setter

当你观察一个对象时,一个新的类会被动态创建。这个类继承自该对象的原本的类,并重写了被观察属性的 setter 方法。重写的 setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象:值的更改。最后通过 isa 混写(isa-swizzling) 把这个对象的 isa 指针 ( isa 指针告诉 Runtime 系统这个对象的类是什么 ) 指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例


官方文档说明:见文档
Automatic key-value observing is implemented using a technique called isa-swizzling.
The isa pointer, as the name suggests, points to the object's class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data.
When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance.

参考内容:

  • <a href="https://github.com/ChenYilong/iOSInterviewQuestions">《招聘一个靠谱的 iOS》—参考答案</a>
  • <a href="https://www.lvtao.net/ios/504.html">iOS开发中@property的属性weak nonatomic strong readonly等介绍</a>
  • <a href="https://opensource.apple.com/tarballs/objc4/">runtime 源码</a>
  • <a href="https://www.zybuluo.com/MicroCai/note/50592">iOS 集合的深复制与浅复制</a>
  • <a href="http://blog.sunnyxx.com/2015/09/13/class-ivar-layout/">Objective-C Class Ivar Layout 探索</a>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容

  • 历时约三个月,总算是把长达50万字的《白鹿原》看完了,书里主要人物不少,一身硬气的白嘉轩,仙风道骨的朱先生,浪子回...
    詹宇seven阅读 19,179评论 11 14
  • 一个人的风险意识影响了他的决策。很多企业家在决策的时候,只想到项目会给他带来的收益,完全不考虑风险,直接导致了项目...
    __xxx__阅读 224评论 0 0
  • 昨晚某大V发出了一个视频,各大营销号不久后开始疯狂式转载,今早起来稳稳抢了热搜榜第一,点开视频看完了,和大V心情一...
    裤个大叉阅读 387评论 0 1