属性关键字
- 读写权限
- readonly
- readwrite
- 原子性
- atomic
- nonatomic
- 引用计数
- retain/strong
- assign/unsafe_unretained
- weak
- copy
atomic是否是线程安全的?
atomic修饰的对象,系统会对它的set、get函数进行加锁。- 如果
atomic修饰一个数组,那么对数组赋值set和获取get,是可以保证线程安全的。 - 如果对数组进行
添加元素和删除元素操作,则不在atomic的操作范围内,是线程不安全的。 - atomic底层使用的是os_unfair_lock(性能最高)
assign和weak的区别?
- assign
- 修饰基本数据类型,如
int、bool等。 - 修饰对象类型时,不改变其引用计数。
- 会产生悬垂指针。
- 修饰基本数据类型,如
- weak
- 不改变背修饰对象的引用计数。
- 所指对象在被释放之后会自动置为
nil。
Q:MRC下如何重写retain修饰变量的setter方法?
浅拷贝和深拷贝
浅拷贝不会生成新对象,只是引用拷贝对象,指向的是同一块内存,两个对象任何一个发生变化都是互相影响。深拷贝新生成一个对象,把原来对象的内容复制过来了,两个对象毫不相关了。
load与initialize
load与initialize调用时机
+load在main函数之前被Runtime调用,+initialize 方法是在类或它的子类收到第一条消息之前被调用的,这里所指的消息包括实例方法和类方法的调用。
load方法调用顺序
父类->主类->分类
- 主类的 +load 方法会在它的所有父类的 +load 方法之后执行。如果主类没有实现 +load 方法,当它被runtime加载时 是不会去调用父类的 +load 方法的。
- 分类的 +load 方法会在它的主类的 +load 方法之后执行,当一个类和它的分类都实现了 +load 方法时,两个方法都会被调用。当有多个分类时,根据编译顺序(Build Phases->Complie Sources中的顺序)依次执行。
- 在类的+load方法调用的时候,可以调用category中声明的方法么? 可以调用,因为附加category到类的工作会先于+load方法的执行
initialize的调用顺序
+initialize 方法的调用与普通方法的调用是一样的,走的都是消息发送的流程。如果子类没有实现 +initialize 方法,那么继承自父类的实现会被调用;如果一个类的分类实现了 +initialize 方法,那么就会对这个类中的实现造成覆盖。
确保在load和initialize的调用只执行一次
由于initialize可能会调用多次,所以在这两个方法里面做的初始化操作需要保证只初始化一次,用dispatch_once来控制
分类
分类作用
- 声明私有方法
- 分解体积庞大的类文件
- 把Framework的私有方法公开
分类特点
- 运行时决议
- 可以为系统类增加分类
分类可以添加哪些内容?
- 实例方法
- 类方法
- 协议
- 属性
分类的实现原理
- category编译之后的底层结构是struct category_t,里面存储着分类的类名, 实例方法列表, 类方法列表, 协议列表和属性列表。
- 在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)
- 首先重新分配内存,可以足够放下类和Category中左右的方法数据。
- 接着将类中原有的方法地用到方法列表的最右边。
- 最后将Category中的方法copy到方法列表的最前面。
- 属性和协议也是同样的方法。
- 此时,Category的方法就放在了方法列表中的前面,而类中的原有方法则存也在于方法列表的最后面。
- 如果调用类的方法,则会从方法列表中从前往后查找,而如果Category中有相同的方法,那么会直接使用Category中的方法。
类别同名方法覆盖问题
- 如果类别和主类都有名叫funA的方法,那么在类别加载完成之后,类的方法列表里会有两个funA;
- 类别的方法被放到了新方法列表的前面,而主类的方法被放到了新方法列表的后面,这就造成了类别方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会停止查找;
- 如果多个类别定义了同名方法funA,具体调用哪个类别的实现由编译顺序决定(Build Phases->Complie Sources中的顺序),后编译的类别的实现将被调用。
- 在日常开发过程中,类别方法重名轻则造成调用不正确,重则造成crash,我们可以通过给类别方法名加前缀避免方法重名。
怎么调用被覆盖掉的方法
category其实并不是完全替换掉原来类的同名方法,只是category在方法列表的前面而已,所以我们只要顺着方法列表找到最后一个对应名字的方法,就可以调用原来类的方法。
扩展
扩展作用
- 声明私有属性
- 声明私有方法
- 声明私有成员变量
分类和扩展的区别
分类是运行时决议,扩展是编译时决议。- 分类可以有声明有实现。扩展只以声明的形式存在,多数情况下寄生于宿主类的.m中。
- 可以为系统类添加分类,不能为系统类添加扩展。
关联对象
能否给分类添加“成员变量”?
- 在类别中不能直接以@property的方式定义属性,OC不会主动给类别属性生成setter和getter方法;需要通过关联变量来实现。
关联对象原理
关联对象都由AssociationsManager管理。AssociationsManager里面是由一个静态AssociationsHashMap来存储所有的关联对象的。这相当于把所有对象的关联对象都存在一个全局map里面。而map的的key是这个对象的指针地址(任意两个不同对象的指针地址一定是不同的),而这个map的value又是另外一个AssociationsHashMap,里面保存了关联对象的kv对。 runtime的销毁对象函数objc_destructInstance里面会判断这个对象有没有关联对象,如果有,会调用_object_remove_assocations做关联对象的清理工作
代理
- 准确的说是一种软件
设计模式。 - iOS当中以
@protocol形式体现。 - 传递方式一对一。
如何规避循环引用?
通知
- 是使用
观察者模式来实现的用于跨层传递消息的机制。 - 传递方式为
一对多。
通知原理
- 全局字典
Notification_Map,以notificationName为key,以数组Observers_List为value。 Observers_List保存所有声明相同notificationName的对象。- 对象以
Observer的形式保存,其中包括执行的函数,对象本身等。
KVO
KVO即Key-Value Observing,翻译成键值观察。它是一种观察者模式的衍生。其基本思想是,对目标对象的某属性添加观察,当该属性发生变化时,通过触发观察者对象实现的KVO接口方法,来自动的通知观察者。
KVO实现原理
- 利用runtime动态生成一个子类,并且让instance对象的isa指向这个全新的子类。
- 子类命名为NSKVONotifying_xxx,重写set、class、superclass、dealloc方法,新增_isKVOA方法。
- class方法返回原instance对象的类对象。
- superclass方法指向原instance的类对象。
- isKVOA返回true。
- dealloc会增加一些监听的释放。
- set方法新增willChangeValueForKey和didChangeValueForKey,并且在didChangeValueForKey内部会出发监听器Observer的监听方法observeValueForKeyPath。
KVO动态生成的对象结构
NSKVONotifying_Person的类对象中, 一共有两个指针isa和superclass, 四个方法setAge:, class, dealloc和_isKVOA
手动KVO
通过调用willChangeValueForKey和didChangeValueForKey方法,可以手动调用kvo,两个方法必须同时出现。
KVO和线程
KVO是同步调用,调用线程跟属性值改变的线程是相同的。KVO 能保证所有age的观察者在 setter 方法返回前被通知到。
KVC
- KVC 是 Key-Value-Coding 的简称。
- KVC 是一种可以直接通过字符串的名字 key 来访问类属性的机制,而不需要调用setter、getter方法去访问。
- 我们可以通过在运行时动态的访问和修改对象的属性。KVC 是 iOS 开发中的黑魔法之一。
KVC原理
- setValue:forKey的原理?
- 按照setKey、_setkey顺序查找方法
- 没有找到方法,则查看accessInstanceVariablesDirectly方法返回值,默认为true。
- 若为true,按照_key、_isKey、key、isKey顺序查找成员变量,找到了直接赋值。
- 若为false或没有找到成员变量,调用valueForUndefineKey:并跑出异常NSUnknownKeyException
- valueForKey的原理?
- 按照getKey、key、isKey、_key顺序查找方法
- 没有找到方法,则查看accessInstanceVariablesDirectly方法返回值,默认为true。
- 若为true,按照_key、isKey、key、isKey顺序查找成员变量,找到了直接取值。
- 若为false或没有找到成员变量,调用valueForUndefineKey:并跑出异常NSUnknownKeyException
Q:KVC赋值时,会触发KVO吗?
- 使用KVO给属性或成员变量赋值时, 都会触发KVO, 系统会自动调用willChangeValueForKey:和didChangeValueForKey:两个方法
Q:如何利用KVC防崩溃
- 可以通过写一个NSObject分类来防崩溃重写valueForUndefinedKey:、setNilValueForKey:、setValue:(id)value forUndefinedKey:函数。