分类
用分类干了什么事情?
- 声明私有方法
- 分解体积庞大类文件
- 把Framework的私有方法公开
特点
- 运行时决议:编好分类文件,并没有分类中添加的内容附加到相应的宿主类,而是在运行时通过RunTime真实的添加到相应的宿主类
- 可以为系统类添加分类
分类中都可以添加哪些内容?
- 实例方法
- 类方法
- 协议
- 属性(分类中定义属性,实际上只是生成了对应的getter、setter方法)
Runtime的源码分类的结构体:struct _category_t
分类的调用栈(Category)
- _objc_int - map_2_images - map_images_nolock - _read_images - remethodizeClass
- images是镜像
- _read_images 加载一些可执行文件到内存当中进行处理
- remethodizeClass
分类_objc_init调用栈.png
如果两个分类添加同一个函数名称的函数,哪一个函数最终生效?
取决于编译顺序,最后生成分类的函数会生效,前面的将被覆盖掉。
总结:
-
分类添加的方法可以"覆盖"原有方法
- 实际上是runtime在查找这个类的方法时,会查找分类一个宿主类的所有方法所在数组
- 而这个数组的分类方法是在宿主类前面的,所以说分类方法的调用有优先宿主类
- 而并不是覆盖宿主类的方法。
-
同名分类方法谁能生效取决于编译顺序
- 最后被编译的分类,会优先生效
-
名字相同的分类会引起编译报错
- 生成具体分类,会把添加的分类名称,以下划线的形式拼接到宿主类中,这样在添加过程中,如果名称一样会报错。
能否给分类添加"成员变量"?
- 不能在分类的声明和定义实现的时候直接为分类添加成员变量,但是可以通过关联对象的技术来为分类添加"成员变量",来达到分类可以添加"成员变量的效果"。
关联对象的3个API:
objc_setAssociatedObject(<#id _Nonnull object#>, <#const void * _Nonnull key#>, <#id _Nullable value#>, <#objc_AssociationPolicy policy#>)
objc_getAssociatedObject(<#id _Nonnull object#>, <#const void * _Nonnull key#>)
objc_removeAssociatedObjects(<#id _Nonnull object#>)
我们用关联对象技术来实现为分类添加"成员变量",这个成员变量会被添加到哪里?
- 我们为分类所添加的成员变量肯定不是在宿主类中的。
- 关联对象由
AssociationsManager
管理并在AssociationHashMap
存储。 - 我们创建的每一个对象的关联对象实际上都存储在了
AssociationHashMap
这样的容器中。 - 所有对象的关联内容都在同一个全局容器中
- 不同对象的关联对象的值都会放到全局容器中
关联对象
关联对象的本质
关联对象由AssociationsManager
管理并在AssociationHashMap
存储。
我们创建的每一个对象的关联对象实际上都存储在了AssociationHashMap
这样的容器中。
所有对象的关联内容都在同一个全局容器中
关联对象的本质.png
关联对象的Json形式.png
扩展(Extension)
类扩展一般都在宿主类的.m文件中。
- 一般用扩展做什么?
- 声明私有属性
- 声明私有方法(没有多大作用,方便阅读)
- 声明私有成员变量
声明私有属性、声明私有成员变量是有区别的
特点
- 编译时决议
- 只义声明的形式存在,多数情况下寄生于宿主类的.m中
- 不能为系统类添加扩展。
分类和区别在哪里
- 分类是运行时决议、扩展是编译时决议
- 分类可以有声明和实现,而扩展只有声明的形式,实现是直接在宿主类的
- 可以为系统类添加分类,但是不能为系统类添加扩展
代理
- 准确的说是一种软件设计模式
- iOS 当中以@protocol形式体现
- 传递方式一对一
代理的工作流程
- 三个角色:
- 委托方
- 要求代理方需要实现的接口,也就是协议
- 调用代理方遵从协议的方法
- 可能返回一个处理结果
- 协议
- 可以定义方法(必须实现和可选类型)
- 可以定义属性
- 代理方
- 按照协议实现方法
- 委托方
代理的实现流程.png
代理方和委托方以什么关系存在?
- 一般声明为weak来规避循环引用
代理循环引用.png
通知实现机制
- 使用观察者模式,来实现用于跨层传递消息的机制
- 传递方式一对多
如何实现通知的机制?假如说自己实现这种机制如何实现?
- 实现一个实现一个全局Map
- key为notificationName
- value为一个Serrvers(因为一对多)
- servers中的每个元素应该能明确哪一个对象,哪一个方法
通知传递消息一对多的流程
通知一对多的流程.png
通知和代理的区别
- 代理使用代理模式,而通知是由观察者模式实现的
- 代理是一对一,而通知是一对多
KVO
什么是KVO?
- KVO是key-value observing的缩写
- KVO是Objective-C对观察者设计模式的又一实现
- Apple使用了isa混写(isa-swizzling)来实现KVO
KVO的实现机制
KVOisa混写.png
- 系统调用
addObserver
添加KVO的时候 - 会想被观察者对象的isa指向其名为
NSKVONotifying_<ObjectClass>
子类 -
NSKVONotifying_<ObjectClass>
中重写了setter方法 - 重写的
setter
方法负责通知观察者- 重写的‘setter’代码块两头添加了下面两个方法
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;
通过KVC设置value能够生效?
- 通过KVC设置value,KVO的通知会生效
- KVC设置value会调用setter方法
通过成员变量直接赋值value能否生效
- 不会触发系统的KVO的
- 不会调用setter方法
- 可以在变量赋值的代码块前后添加下面两个API来触发KVO
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;
总结
- 使用setter方法改变值KVO才会生效
- 使用setValue:forkey:改变KVO才会生效
- 成员变量直接修改需手动添加KVO才会生效
KVC
什么是KVC?
- KVC是Key-value coding的缩写
- KVC的两个API
- (void)setValue:(nullable id)value forKey:(NSString *)key
- (nullable id)valueForKey:(NSString *)key
- KVC的两个API
使用KVC是否会违背于面向对象的编程思想
- KVC的key是没有限制的,如果我们知道这个对象内部的私有变量名称的话,我们可以在外界修改、访问它,从这个角度来考虑的话是会破坏面向对象的思想的
valueForKey的实现流程
KVCGetter方法的流程.png
Accessor Method方法的定义
- <getKey>
- <key>
- <isKey>
instance var
- _key
- _isKey
- key
- isKey
setValue:forKey的实现流程
KVCsetter方法的流程.png
Accessor Method方法的定义
- set<Key>:
- _set<Key>:
- setIs<Key>:
instance var
- _key
- _isKey
- key
- isKey
属性关键字
属性关键字分为哪几类
- 读写权限
- readonly
- readwrite iOS默认
- 原子性
- atomic iOS默认
- 修饰的是一个数组,赋值和获取是线程安全的,
- 修改这个数组中的数据不是线程安全的
- nonatomic
- atomic iOS默认
- 引用技计数
- retain mrc
- strong arc
- assign 既可以修饰对象,也可以修饰值类型数据
- unsafe_unretained MRC使用的比较多,ARC基本上不怎么用
- weak
- copy
assign的特点
- 修饰基本数据类型,如果int、bool等
- 修饰对象类型时,不改变引用计数
- 在对象释放以后,assign修饰的对象指针还是会指向这个对象地址,当使用这个对象的时候,会出现野指针。
weak 特点
- 不改变对象引用计数
- 所指对象在释放之后会自动置为nil
assign和weak的区别有哪些?
- 区别:
- 1、weak只可以修饰对象、而assign可以修饰基本数据类型和对象
- 2、assign修饰的对象的时候,当对象被释放掉以后,assign指针还是会指向对象的地址,而weak修饰的对象,在对象释放后,weak的指针会被置nil
weak指针对象为什么会被值为nil?
Copy
源对象类型 | 拷贝方式 | 目标对象类型 | 拷贝类型 |
---|---|---|---|
mutable对象 | copy | 不可变 | 深拷贝 |
mutable对象 | mutableCopy | 可变 | 深拷贝 |
immutable对象 | copy | 不可变 | 浅拷贝 |
immutable对象 | mutableCopy | 可变 | 深拷贝 |
- 可变对象的copy和mutablecopy都是深拷贝
- 不可变对象的copy是浅拷贝,mutablecopy是深拷贝
- copy方法返回的都是不可变对象
浅拷贝
- 浅拷贝会增加被拷贝对象的引用计数
- 并没有发生新的内存分配
浅拷贝就是对内存地址的复制,让目标对象指针和源对象指向同一片内存空间。
浅拷贝.png
深拷贝
- 不会增加被拷贝对象的引用计数
- 产生了内存分配
深拷贝让目标对象指针和源对象指针指向两片内容相同的内存空间
深拷贝.png
深拷贝VS浅拷贝?
- 是否开辟新的内存空间
- 是否影响引用计数
@property(copy)NSMutableArray *array;会出现什么问题?
最终结果是一个不可变的对象,那么你在修改这个数组的时候会崩溃
如果赋值过来的是NSMutableArray,copy之后虽然是深拷贝,但是返回的是NSArray
如果赋值过来的是NSArray,copy之后是NSArray
当修改array这个数组的时候就会出现崩溃问题
面试题
1、MRC下如何重写retain修饰变量的setter方法?
@property(nonatomic,retain)id obj;
- (void)setObj:(id)obj{
//对象不等判断
//如果传递过来的obj对象是原来的obj对象
//实际上也是在传递过来的obj对象release的操作,有可能会直接释放掉了传递过来的对象,
//这时候如果还去访问被释放的对象就会崩溃
if(_obj != obj){
[_obj release];
_obj = [obj retain]
}
}
2、 分类的实现原理
- 是由运行时来决议的
- 不同分类,同名函数名称,谁最后生效,取决于,谁最后参与编译的同名分类方法会生效
- 如果分类方法的名字和宿主的方法名称一样,那么分类的方法会覆盖宿主类的方法,这里的覆盖是runtime优先处理数组靠前的方法,如果找到就调用。宿主类的同名方法还是存在的
3、KVO实现原理是怎么样的?
- KVO是系统基于观察模式的一个实现
- KVO通过isa的混写技术来动态运行时去为某一个类添加子类,并重写setter方法,同时把原有类的isa指针指向这个子类