链式编程
在实际开发过程中,基本上链式开发思想很常见。第三方框架Masonry
和ReactiveCocoa
都大量用了这种思想。
链式编程
其实就是将多个操作(多行代码)通过点号.
链接在一起成为一句代码
block
block
在链式开发中扮演这很重要的角色。通常我们调用一个block
Person * p = [[Person alloc] init];
[p result:^{
}]
然而我现在想将他改进一下用.
语法将其调用出来,写法如下
-(void(^)(int))result{
return ^(int num){
NSLog(@"num:%d",num);
};
}
此时调用的时候
Person * p = [[Person alloc] init];
p.result(3);
打印:
2017-09-23 12:15:08.650 链式编程[26528:1560398] num:3
接下来我们做一些细微的修改,就变成了链式编程
了
-(Person *(^)(int))result{
static int sum = 0;
return ^(int num){
sum += num;
NSLog(@"sum:%d",sum);
return self;
};
}
此时调用
p.result(3).result(4).result(5);
打印:
2017-09-23 12:20:09.210 链式编程[26883:1581658] sum:3
2017-09-23 12:20:09.210 链式编程[26883:1581658] sum:7
2017-09-23 12:20:09.210 链式编程[26883:1581658] sum:12
细心的你应该早已发现Masorny里面的
make.left.top.equalTo(@10);
和RAC中的
[[signal filter:^BOOL(NSNumber * _Nullable value) {
return value > 2;
}]subscribeNext:^(id _Nullable x) {
}];
这些都是链式思想
响应式编程
响应式编程(反应式编程)
是一种面向数据流和变化传播的编程范式。
相信看完解释后的同学应该第一时间就想到了KVO。
KVO
首先创建一个Person
对象,添加name
属性,然后给name添加KVO
[p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
然后实现下面的函数
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
当p.name
属性发生变化时候,程序就回到了这个代理方法,监听到了name
属性变化。
不知道此时大家有没有疑惑,KVO到底能不能监听到成员变量呢。带着疑惑我做一个实验,将name属性改成成员变量,代码如下:
//@property (nonatomic,strong) NSString * name;
NSString * _name;
这个时候,我们发现当改变p->name
的时候,KVO根本不回调。相信大家都知道,属性是成员变量+setter方法
和getter方法
组成。这个时候得出以下结论:KVO监听的是属性的seter方法
。
修改isa
接下来,在添加KVO的属性时候加一个两个NSLog
NSLog(@"%@",object_getClass(p));
[p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:0];
NSLog(@"%@",object_getClass(p));
打印
2017-09-25 09:27:40.098 链式编程[79382:12699417] Person
2017-09-25 09:27:40.098 链式编程[79382:12699417] NSKVONotifying_Person
从打印结果,我惊奇发现p变成了NSKVONotifying_Person
。原来苹果在添加KVO的时候,动态创建了Person
一个子类,实现该属性的setter
方法,修改原来Person
的isa
指针指向NSKVONotifying_Person
。当属性被修改,isa
指向的中间类NSKVONotifying_Person
的setter
方法会被调用,接下来就是进行下面一系列的操作了。
手动实现KVO
既然明白了KVO的底层实现原理,那我们也尝试实现一个自己的KVO。分析下来,我们需要做的事情有:
- 在添加KVO的时候动态创建一个子类
- 在子类里面实现属性的
setter
属性 - 将
isa
指针指向子类 - 在子类
setter
调用父类方法修改属性 - 通知observe收到属性更新的动作
有经验的童鞋应该已经发现,修改isa
指针苹果内部可以做,但是没有提供方法给开发者用。查阅资料发现可以用object_setClass
进行代替。
接下来创建一个类别:NSObject+KVO
,然后实现以下代码
- (void)lq_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{
//动态生成一个类
//获取类名
NSString * className = NSStringFromClass(self.class);
NSString * newClassName = [NSString stringWithFormat:@"LQKVO_%@",className];
const char * name = [newClassName UTF8String];
//动态创建一个类
Class myClass = objc_allocateClassPair([self class], name, 0);
//注册类
objc_registerClassPair(myClass);
//修改isa
object_setClass(self, myClass);
//添加方法
class_addMethod(myClass, @selector(setName:), (IMP)setName, "");
void setName(NSString * newName){
NSLog(@"我收到消息了%@",newName);
}
在VC中实现
[p lq_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:0];
Command + R
运行,控制台打印
2017-09-24 12:12:58.730 链式编程[69869:4654336] 我收到消息了<LQKVO_Person: 0x604000029500>
看到这个打印结果,我懵逼了,尼玛我传的不是name
吗,为什么会打印我动态创建的这个类呢?难道是系统出问题了,接连运行几次结果还是一样。
去苹果的文档中心查找class_addMethod
方法,从他的示例代码中我发现了下面的代码
void myMethodIMP(id self, SEL _cmd)
{
// implementation ....
}
这时候我瞬间明白了,原来这个方法存在两个隐藏参数,于是将代码改成
void setName(id self, SEL _cmd, NSString * newName)
NSLog(@"我收到消息了%@",newName);
}
Command + R
运行,控制台打印
2017-09-24 12:24:02.532 链式编程[70499:4698280] 我收到消息了lq
完美,终于搞定了,继续完成下面的内容。
void setName(id self, SEL _cmd, NSString * newName){
NSLog(@"我收到消息了%@",newName);
//保存class
id class = [self class];
//改变类型[super setName:];
object_setClass(self, class_getSuperclass(class));
objc_msgSend();
}
当我拿到父类准备赋值的时候,准备调用父类的setName
方法。
但是这里objc_msgSend
却怎么都敲不出参数,Command
点击进去看到以下内容。
#if !OBJC_OLD_DISPATCH_PROTOTYPES
OBJC_EXPORT void
objc_msgSend(void /* id self, SEL op, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
OBJC_EXPORT void
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
#else
由于自己经验水平一般,真的没看懂那个宏具体是要干嘛,但是我肯定是苹果在某种特定的情况下将它禁止了。去网上查阅资料得知,苹果不推荐使用objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
该方法,可是我们一定要拿来玩玩,苹果也是不会禁止的。build Setting
搜索objc_msgSend
改成NO
即可
经过自己一步一步的踩坑,终于完成以下代码
- (void)lq_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{
//动态生成一个类
//获取类名
NSString * className = NSStringFromClass(self.class);
NSString * newClassName = [NSString stringWithFormat:@"LQKVO_%@",className];
const char * name = [newClassName UTF8String];
//动态创建一个类
Class myClass = objc_allocateClassPair([self class], name, 0);
//注册类
objc_registerClassPair(myClass);
//修改isa
object_setClass(self, myClass);
//添加方法
class_addMethod(myClass, @selector(setName:), (IMP)setName, "v@:@");
//将观察者保存到当前对象中
objc_setAssociatedObject(self, @"key", observer, OBJC_ASSOCIATION_ASSIGN);
}
void setName(id self, SEL _cmd, NSString * newName){
NSLog(@"我收到消息了%@",newName);
//保存class
id class = [self class];
//改变类型[super setName:];
object_setClass(self, class_getSuperclass(class));
objc_msgSend(self, @selector(setName:),newName);
//回到子类
object_setClass(self, class);
//拿到当前属性
id observer = objc_getAssociatedObject(self, @"key");
objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:),@"name",self,@{@"name":newName},nil);
}
至此,自定义KVO已经完成了,赶快给自己点个赞吧!😁😁😁