OC语言特性
分类
用分类做了哪些事情
- 声明私有方法
- 分解体积庞大的类文件
- 把framework的私有方法公开化
特点
- 运行时决议:编好分类文件,并没有分类中添加的内容附加到相应的宿主类,而是在运行时通过RunTime真实的添加到相应的宿主类
- 可以为系统类添加分类
分类中都可以添加哪些内容?
- 实例方法
- 类方法
- 协议
- 属性(分类中定义属性,实际上只是生成了对应的getter、setter方法)
Runtime的源码分类的结构体:struct _category_t
分类的调用栈(Category)
- _objc_int -> map_2_images -> map_images_nolock -> _read_images -> remethodizeClass
- images是镜像
- _read_images 加载一些可执行文件到内存当中进行处理
- remethodizeClass
如果两个分类添加同一个函数名称的函数,哪一个函数最终生效?
取决于编译顺序,最后生成分类的函数会生效,前面的将被覆盖掉。
总结:
-
分类添加的方法可以”覆盖”原有方法
① 实际上是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#>)
① 设定一个value值,通过key值建立一个映射关系,然后通过policy策略,关联到object上
② 根据指定的key到object中获取关联的值
③ 根据对象,移除所有相关它的对象
我们用关联对象技术来实现为分类添加”成员变量”,这个成员变量会被添加到哪里?
① 我们为分类所添加的成员变量肯定不是在宿主类中的。
② 关联对象由AssociationsManager管理并在AssociationHashMap存储。
③ 我们创建的每一个对象的关联对象实际上都存储在了AssociationHashMap这样的容器中。
④ 所有对象的关联内容都在同一个全局容器中
⑤ 不同对象的关联对象的值都会放到全局容器中
⑥ 假如想消除一个关联对象时,可以传入value为nil操作来实现
关联对象
关联对象本质
关联对象由AssociationsManager管理并在AssociationHashMap存储。
我们创建的每一个对象的关联对象实际上都存储在了AssociationHashMap这样的容器中。
所有对象的关联内容都在同一个全局容器中
利用关联对象添加成员变量
1. - (void)setName:(NSString *)name { objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
2. - (NSString *)name
{
// 隐式参数
// _cmd == @selector(name)
return objc_getAssociatedObject(self, _cmd);
}
(void)removeAssociatedObjects{
// 移除所有关联对象
objc_removeAssociatedObjects(self);
}
关联对象的本质.png
关联对象的Json形式.png
扩展
类扩展一般都在宿主类的.m文件中。
一般用扩展做什么?
- 声明私有属性
- 声明私有方法(没有多大作用,方便阅读)
- 声明私有成员变量
声明私有属性、声明私有成员变量是有区别的
特点
- 分类是运行时决议、扩展是编译时决议
- 分类可以有声明和实现,而扩展只有声明的形式,实现是直接在宿主类的
- 可以为系统类添加分类,但是不能为系统类添加扩展
代理
- 准确的说是一种软件设计模式
- iOS 当中以@protocol形式体现
- 传递方式一对一
代理的工作流程
三个角色:
- 委托方
① 要求代理方需要实现的接口,也就是协议
② 调用代理方遵从协议的方法
③ 可能返回一个处理结果 - 协议
① 可以定义方法(必须实现和可选类型)
② 可以定义属性 -
代理方
按照协议实现方法
代理的实现流程.png
代理方和委托方以什么关系存在?
一般声明为weak来规避循环引用
代理循环引用.png
通知实现机制
- 使用观察者模式,来实现用于跨层传递消息的机制
- 传递方式一对多
如何实现通知的机制?假如说自己实现这种机制如何实现?
- 实现一个实现一个全局Map
- key为notificationName
- value为一个Serrvers(因为一对多), servers中的每个元素应该能明确哪一个对象,哪一个方法
通知传递消息一对多的流程
通知一对多的流程.png通知和代理的区别
- 代理使用代理模式,而通知是由观察者模式实现的
- 代理是一对一,而通知是一对多
KVO
什么是KVO?
- KVO是key-value observing的缩写
- KVO是Objective-C对观察者设计模式的又一实现
- KVO是Objective-C对观察者设计模式的又一实现
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;
观测方法的options
- NSKeyValueObservingOptionOld 把更改之前的值提供给处理方法
- NSKeyValueObservingOptionNew 把更改之后的值提供给处理方法
- NSKeyValueObservingOptionInitial 把初始化的值提供给处理方法,一旦注册,立马就会调用一次。通常它会带有新值,而不会带有旧值。
- NSKeyValueObservingOptionPrior 分2次调用。在值改变之前和值改变之后。
总结
- 使用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是否会违背于面向对象的编程思想
KVC的key是没有限制的,如果我们知道这个对象内部的私有变量名称的话,我们可以在外界修改、访问它,从这个角度来考虑的话是会破坏面向对象的思想的
valueForKey的实现流程
KVCGetter方法的流程.pngAccessor Method方法的定义
- instance var
- _key
- _isKey
- key
- isKey
setValue:forKey的实现流程
KVCsetter方法的流程.pngAccessor Method方法的定义
- set:
- _set:
- setIs:
instance var
- _key
- _isKey
- key
- isKey
属性关键字
属性关键字分为哪几类
- 读写权限
① readonly
② readwrite iOS默认 - 原子性
① atomic iOS默认
-修饰的是一个数组,赋值和获取是线程安全的,
-修改这个数组中的数据不是线程安全的 (例如在atomic修饰的NSMutableArray数组中,进行增删元素操作)
② nonatomic - 引用技计数
① 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
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这个数组的时候就会出现崩溃问题
MST
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指针指向这个子类
4、能否为分类添加实例变量?
- 实例变量(成员变量),即它只能添加属性,setter/getter的声明,但是没有实现,不能使用其父类的一些成员方法
- 属性是系统自动为我们生成的一个添加下划线的实例变量,系统帮我们实现了setter/getter方法
- 不使用属性创建的实例变量,需要最后在@implementation中用synthesize生成set方法
@synthesize name;(创建实例变量的setter、getter方法),@dynamic告诉编译器,不自动生成getter/setter方法, - 为分类添加实例变量
① 使用关联对象技术为分类添加“实例变量”(只是达到一个成员变量的效果,实际上并不是真正的添加了实例变量)
② 代码实例
.h文件
@interface CustomView (dd)
- (NSString *)name;
- (void)setName:(NSString *)name;
@end
.m
@implementation CustomView (dd)
/*
* 使用关联对象模拟实例变量
* 使用objc_getAssociatedObject、objc_setAssociatedObject模拟『属性』的存取方法
*/
- (NSString *)name{
return objc_getAssociatedObject(self, _cmd);
}
- (void)setName:(NSString *)name{
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end