在iOS开发中,我们可以通过KVO机制来监听某个对象的某个属性的变化。
用过KVO的同学都应该知道,KVO的回调是以代理的形式实现的:在给某个对象添加观察以后,需要在另外一个地方实现回调代理方法。这种设计给人感觉比较分散,因此突然想试试用Block来实现KVO,将添加观察的代码和回调处理的代码写在一起。在学习了ImplementKVO的实现以后,自己也写了一个:SJKVOController
SJKVOController的用法
只需要引入NSObject+SJKVOController.h
头文件就可以使用SJKVOController。
先看一下它的头文件:
1 |
|
从上面的API可以看出,这个小轮子:
- 支持一次观察同一对象的多个属性。
- 可以一次只观察一个对象的一个属性。
- 可以移除对某个对象对多个属性的观察。
- 可以移除对某个对象对某个属性的观察。
- 可以移除某个观察自己的对象。
- 可以移除所有观察自己的对象。
- 打印出所有观察自己的对象的信息,包括对象本身,观察的属性,setter方法。
下面来结合Demo讲解一下如何使用这个小轮子:
在点击上面两个按钮中的任意一个,增加观察:
一次性添加:
1 | - (IBAction)addObserversTogether:(UIButton *)sender { |
分两次添加:
1 | - (IBAction)addObserverSeparatedly:(UIButton *)sender { |
添加以后,点击最下面的按钮来显示所有的观察信息:
1 | - (IBAction)showAllObservingItems:(UIButton *)sender { |
输出:
1 | SJKVOController[80499:4242749] SJKVOLog:==================== Start Listing All Observers: ==================== |
在这里我重写了description方法,打印出了每个观察的对象和key,以及setter方法。
现在点击更新按钮,则会更新model的number和color属性,从而触发KVO:
1 | - (IBAction)updateNumber:(UIButton *)sender { |
我们可以看到中间的Label上面显示的数字和背景色都在变化,成功实现了KVO:
现在我们移除观察,点击remove按钮
1 | - (IBAction)removeAllObservingItems:(UIButton *)sender { |
在移除了所有的观察者以后,则会打印出:
1 | SJKVOController[80499:4242749] SJKVOLog:Removed all obserbing objects of object:<Model: 0x60000003b700> |
而且如果在这个时候打印观察者list,则会输出:
1 | SJKVOController[80499:4242749] SJKVOLog:There is no observers obserbing object:<Model: 0x60000003b700> |
需要注意的是,这里的移除可以有多种选择:可以移某个对象的某个key,也可以移除某个对象的几个keys,为了验证,我们可以结合list方法来验证一下移除是否成功:
验证1:在添加number和color的观察后,移除nunber的观察:
1 | - (IBAction)removeAllObservingItems:(UIButton *)sender { |
在移除以后,我们调用list方法,输出:
1 | SJKVOController[80850:4278383] SJKVOLog:==================== Start Listing All Observers: ==================== |
现在只有color属性被观察了。看一下实际的效果:
我们可以看到,现在只有color在变,而数字没有变化了,验证此移除方法正确。
验证2:在添加number和color的观察后,移除nunber和color的观察:
1 | - (IBAction)removeAllObservingItems:(UIButton *)sender { |
在移除以后,我们调用list方法,输出:
1 | SJKVOController[80901:4283311] SJKVOLog:There is no observers obserbing object:<Model: 0x600000220fa0> |
现在color和number属性都不被观察了。看一下实际的效果:
我们可以看到,现在color和number都不变了,验证此移除方法正确。
OK,现在知道了怎么用SJKVOController,我下面给大家看一下代码:
SJKVOController代码解析
先大致讲解一下SJKVOController的实现思路:
- 为了减少侵入性,SJKVOController被设计为NSObject的一个分类。
- SJKVOController仿照了KVO的实现思路,在添加观察以后在运行时动态生成当前类的子类,给这个子类添加被观察的属性的set方法并使用isa swizzle的方式将当前对象转换为当前类的子类的实现。
- 同时,这个子类还使用了关联对象来保存一个“观察项”的set,每一个观察项封装了一次观察的行为(有去重机制):包括观察自己的对象,自己被观察的属性,以及传进来的block。
- 在当前类,也就是子类的set方法被调用的时候做三件事情:
- 第一件事情是使用KVC来找出当前属性的旧值。
- 第二件事情是调用父类(原来的类)的set方法(设新值)。
- 第三件事是根据当前的观察对象和key,在观察项set里面找出对应的block并调用。
再来看一下这个小轮子的几个类:
- SJKVOController:实现KVO主要功能的类。
- SJKVOObserverItem:封装观察项的类。
- SJKVOTool:setter和getter的相互转换和相关运行时查询方法等。
- SJKVOError:封装错误类型。
- SJKVOHeader:引用了运行时的头文件。
下面开始一个一个来讲解每个类的源码:
SJKVOController
再看一下头文件:
1 |
|
每个方法的意思相信读者已经能看懂了,现在讲一下具体的实现。从sj_addObserver:forKey withBlock:
开始:
sj_addObserver:forKey withBlock:方法:
除去一些错误的判断,该方法作了下面几件事情:
1.判断当前被观察的类是否存在与传入key对应的setter方法:
1 | SEL setterSelector = NSSelectorFromString([SJKVOTool setterFromGetter:key]); |
2. 如果有,判断当前被观察到类是否已经是KVO类(在KVO机制中,如果某个对象一旦被观察,则这个对象就变成了带有包含KVO前缀的类的实例)。如果已经是KVO类,则将当前实例的isa指针指向其父类(最开始被观察的类):
1 | //get original class(current class,may be KVO class) |
3. 如果不是KVO类(说明当前实例没有被观察),则创建一个带有KVO前缀的类,并将当前实例的isa指针指向这个新建的类:
1 | //create a KVO class |
看一下如何新建一个新的类:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22- (Class)createKVOClassFromOriginalClassName:(NSString *)originalClassName
{
NSString *kvoClassName = [SJKVOClassPrefix stringByAppendingString:originalClassName];
Class KVOClass = NSClassFromString(kvoClassName);
// KVO class already exists
if (KVOClass) {
return KVOClass;
}
// if there is no KVO class, then create one
KVOClass = objc_allocateClassPair(OriginalClass, kvoClassName.UTF8String, 0);//OriginalClass is super class
// pretending to be the original class:return the super class in class method
Method clazzMethod = class_getInstanceMethod(OriginalClass, @selector(class));
class_addMethod(KVOClass, @selector(class), (IMP)return_original_class, method_getTypeEncoding(clazzMethod));
// finally, register this new KVO class
objc_registerClassPair(KVOClass);
return KVOClass;
}
4. 查看观察项set,如果这个set里面有已经保存的观察项,则需要新建一个空的观察项set,将已经保存的观察项放入这个新建的set里面:
1 | //if we already have some history observer items, we should add them into new KVO class |
看一下如何保存观察项的:1
2
3
4
5
6
7
8
9
10- (void)KVOConfigurationWithObserver:(NSObject *)observer key:(NSString *)key block:(SJKVOBlock)block kvoClass:(Class)kvoClass setterSelector:(SEL)setterSelector setterMethod:(Method)setterMethod
{
//add setter method in KVO Class
if(![SJKVOTool detectClass:OriginalClass hasSelector:setterSelector]){
class_addMethod(kvoClass, setterSelector, (IMP)kvo_setter_implementation, method_getTypeEncoding(setterMethod));
}
//add item of this observer&&key pair
[self addObserverItem:observer key:key setterSelector:setterSelector setterMethod:setterMethod block:block];
}
这里首先给KVO类增加了setter方法:
1 | //implementation of KVO setter method |
然后实例化对应的观察项:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20- (void)addObserverItem:(NSObject *)observer
key:(NSString *)key
setterSelector:(SEL)setterSelector
setterMethod:(Method)setterMethod
block:(SJKVOBlock)block
{
NSMutableSet *observers = objc_getAssociatedObject(self, &SJKVOObservers);
if (!observers) {
observers = [[NSMutableSet alloc] initWithCapacity:10];
objc_setAssociatedObject(self, &SJKVOObservers, observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
SJKVOObserverItem *item = [[SJKVOObserverItem alloc] initWithObserver:observer Key:key setterSelector:setterSelector setterMethod:setterMethod block:block];
if (item) {
[observers addObject:item];
}
}
5. 判断新的观察是否会与已经保存的观察项重复(当观察对象和key一致的时候),如果重复,则不添加新的观察:
1 | / /ignore same observer and key:if the observer and key are same with saved observerItem,we should not add them one more time |
而一次性添加多个key的方法,也只是调用多次一次性添加单个key的方法罢了:
1 | - (void)sj_addObserver:(NSObject *)observer |
关于移除观察的实现,只是在观察项set里面找出封装了对应的观察对象和key的观察项就可以了:
1 | - (void)sj_removeObserver:(NSObject *)observer |
再看一下移除所有观察者:
1 | - (void)sj_removeAllObservers |
SJKVOObserverItem
这个类负责封装每一个观察项的信息,包括:
- 观察者对象。
- 被观察的key。
- setter方法名(SEL)
- setter方法(Method)
- 回调的block
需要注意的是:
在这个小轮子里,对于同一个对象可以观察不同的key的情况,是将这两个key区分开来的,是属于不同的观察项。所以应该用不同的SJKVOObserverItem
实例来封装。
1 |
|
SJKVOTool
这个类负责setter方法与getter方法相互转换,以及和运行时相关的操作,服务于SJKVOController
。看一下它的头文件:
1 |
|
##SJKVOError
这个小轮子仿照了JSONModel的错误管理方式,用单独的一个类SJKVOError
来返回各种错误:
1 |
|
OK,这样就介绍完了,希望各位同学可以积极指正~
————————————————- 2018年7月17日更新 ————————————————-
注意注意!!!
笔者在近期开通了个人公众号,主要分享编程,读书笔记,思考类的文章。
- 编程类文章:包括笔者以前发布的精选技术文章,以及后续发布的技术文章(以原创为主),并且逐渐脱离 iOS 的内容,将侧重点会转移到提高编程能力的方向上。
- 读书笔记类文章:分享编程类,思考类,心理类,职场类书籍的读书笔记。
- 思考类文章:分享笔者平时在技术上,生活上的思考。
因为公众号每天发布的消息数有限制,所以到目前为止还没有将所有过去的精选文章都发布在公众号上,后续会逐步发布的。
而且因为各大博客平台的各种限制,后面还会在公众号上发布一些短小精干,以小见大的干货文章哦~
扫下方的公众号二维码并点击关注,期待与您的共同成长~