[self.enterButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.moviePlayer.view);
make.centerY.equalTo(self.moviePlayer.view);
make.height.mas_equalTo(@(50));
make.width.mas_equalTo(@(0));
}];
我们平时已经用习惯了面向对象的编程思想,基本上理解为万物皆对象的概念,那么如果用Autolayout布局的话,Masonry就肯定会用过,每个优秀的框架都有一套自己的编程思想,这就是经典的链式编程思想,既然都会用了,那么学习下人家的编程思想也是非常好的提高。
上述代码思想是通过.符号把多行代码链接成一句代码,增强了可读性,例如a(1).b(2).c(3)一系列操作,那么在OC中要实现a(参数)这种调用方式,那么必然就是Block了。没错,链式编程的方法调用返回的是Block,而且调用完Block之后必然会有返回值(该返回值就是操作对象本身),这样才能链式调用,而且Block的参数就是需要内部对象操作的值
从后面往前面推,先来看看我们最终需要的调用链式代码manager.add(10).add(20).add(30).add(40)
,只要这句代码,我们就能完成加法运算,一步一步来分析,首先调用的是manager.add(10)
,这是第一个加法运算区块,这里还能分两个调用,第一个是manager.add
,后面紧跟着是(10),这种调用方式必然是Block,因此add方法返回值就是一个Block,该block的参数就是括号里面的值,那么假定没有返回值,调用完第一波区块的时候,就不能再调用add
了,因此,该Block既有参数,又有返回值,返回值就是调用的对象
代码如下
1.创建CalculateManager对象和方法
#define 加法
#define 减法
#define 乘法
#define 除法
@interface CalculateManager : NSObject
// 内部用来保存计算的结果
@property (nonatomic,assign) int result;
加法
- (CalculateManager *(^)(int num))add;
乘法
- (CalculateManager *(^)(int num))multiply;
减法
- (CalculateManager *(^)(int num))minus;
除法
- (CalculateManager *(^)(int num))div;
2.实现暴露出来的方法
- (CalculateManager *(^)(int num))add
{
// 把block返回出去,在外面进行数据处理,调用完之后返回对象
return ^CalculateManager *(int num){
self.result += num;
return self;
};
}
- (CalculateManager *(^)(int))minus
{
return ^CalculateManager *(int num){
self.result -= num;
return self;
};
}
- (CalculateManager * (^)(int num))div{
return ^CalculateManager *(int num){
self.result /= num;
return self;
};
}
- (CalculateManager * (^)(int num))multiply{
return ^CalculateManager *(int num){
self.result *= num;
return self;
};
}
3.最基础的调用
CalculateManager *manager = [[CalculateManager alloc] init];
int result = [manager.add(10).add(20).add(30).add(40) result];
NSLog(@"result = %d",result);
可以看到上面的基本调用已经完成了,虽然也是链式的,结果也有,但是总感觉和Masonry不一样,OK,在来一层
@interface NSObject (Calculate)
+ (int)mkj_makeCalculates:(void(^)(CalculateManager *))block;
@end
+ (int)mkj_makeCalculates:(void(^)(CalculateManager *))block {
CalculateManager *manager = [[CalculateManager alloc] init];
block(manager);
return manager.result;
}
4.封装后的调用,现在看起来和Masonry有点像了
int result = [CalculateManager mkj_makeCalculates:^(CalculateManager *manager) {
manager.add(10).add(10).add(10);
manager.add(30).add(40);
manager.div(2);
manager.multiply(4).minus(12);
}];
NSLog(@"result111 = %d",result);
稍微概括下,首先调用Category的类方法,参数是Block,该Block根据实际调用地方的参数进行方法链式调用,Category里面先创建一个计算对象,然后调用Block,把对象当做参数进行Block回调,在外部进行一套链式调用,链式的思想是,对象的方法调用没有参数返回值必须是Block,然后调用Block,Block的返回值就是对象,循环进行再次调用,最终内部传参的对象把结果存储在属性字段中,可以根据函数返回值返回出来,打印最终需要的结果
链式编程思想就是,不考虑调用顺序,只在乎结果,事件分发出去,只要有人监听,就能进行下一步操作,KVO就是这样,稍微分析下
简单的KVO如下
// KVO内部原理
CalculateManager *manager1 = [[CalculateManager alloc] init];
NSLog(@"打印类%@,通过runtime获取类信息%@",manager1,object_getClass(manager1));
[manager1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
NSLog(@"打印类%@,通过runtime获取类信息%@",manager1,object_getClass(manager1));
manager1.name = @"mikejing";
NSLog(@"打印类%@,通过runtime获取类信息%@",manager1,object_getClass(manager1));
[manager1 removeObserver:self forKeyPath:@"name"];
NSLog(@"打印类%@,通过runtime获取类信息%@",manager1,object_getClass(manager1));
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionaryid> *)change context:(void *)context
{
NSLog(@"%@",change);
}
打印结果如下
2016-12-31 11:21:11.182 链式编程[43681:1390993] 打印类<CalculateManager: 0x60000002e800>,通过runtime获取类信息CalculateManager
2016-12-31 11:21:11.183 链式编程[43681:1390993] 打印类<CalculateManager: 0x60000002e800>,通过runtime获取类信息NSKVONotifying_CalculateManager
2016-12-31 11:21:11.183 链式编程[43681:1390993] {
kind = 1;
new = mikejing;
}
2016-12-31 11:21:11.184 链式编程[43681:1390993] 打印类<CalculateManager: 0x60000002e800>,通过runtime获取类信息NSKVONotifying_CalculateManager
2016-12-31 11:21:11.184 链式编程[43681:1390993] 打印类<CalculateManager: 0x60000002e800>,通过runtime获取类信息CalculateManager
注意看:在给对象添加观察者之前,对象的class、runtime真实class和isa指针都是CalculateManager
对象,这很正常,但是增加观察者之后呢,注意看,class还是CalculateManager
,但是isa指针和runtime的getclass暴露了一切,已经变了一个子类NSKVONotifying_CalculateManager
这里好像在哪看到过,叫做门面设计模式,对外隐藏了一切类信息,就连直接打印class都被重写了,还是返回原本的父类,但是内在确确实实被动态改变了,当我们监听的属性改变的时候,其实就是监听到set方法的调用,重写动态创建那个子类的set方法,然后由子类通知观察者,我已经被改变,呵呵,你该干嘛干嘛
既然监听的是属性的set方法,那来验证下
@interface CalculateManager : NSObject
// 暴露一个外部变量直接访问
{
@public
NSString *_name;
}
// 然后把属性的变化换个方法,直接访问
// manager1.name = @"mikejing";
manager1->_name = @"mikejing";
// 然后这个观察者就不会被调用了
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionaryid> *)change context:(void *)context
{
// 不再被打印,说明没监听到,有兴趣可以试试
NSLog(@"%@",change);
}
回顾下KVO实现的思路
1.动态创建NSKVONotifying_类名,该类是原来那个类的子类,用来最终同时观察者
2. 修改当前对象的isa指针,当调用set方法的时候先去创建的子类找,而且子类重写了class方法,返回原来父类,来掩盖犯罪证据
3.子类重写set,当外部调用set,就会进来子类的方法
4.重写set方法里面先调用super set:
方法,然后通知观察者
第一步,category一个方法
@interface NSObject (Calculate)
- (void)mkj_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
第二步动态修改类的isa指针,并且保存观察者对象和调用者绑定
- (void)mkj_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
{
// 修改调用者的isa指针为动态创建的子类,能让set方法改变的时候,去子类进行操作
object_setClass(self, [MKJKVONotifying_CalculateManager class]);
// 由于observr没有任何存储,子类set方法改变之后如何调用observe实现的方法?runtime
// 动态给对象关联一个属性
objc_setAssociatedObject(self, "hehe", observer, OBJC_ASSOCIATION_RETAIN);
}
第三步重写class,隐藏子类,通过runtime拿出对应的观察者,通知观察者改变
- (Class)class
{
return [CalculateManager class];
}
- (void)setName:(NSString *)name
{
// 调用父类
[super setName:name];
// 根据关联字段拿出关联的观察者
id obj = objc_getAssociatedObject(self, "hehe");
// 子类最终通知调用观察者的observer方法
[obj observeValueForKeyPath:@"name" ofObject:self change:@{@"change":name} context:nil];
}
第四步外部调用自己实现的类似的KVO
// 自己实现KVO
CalculateManager *manager2 = [[CalculateManager alloc] init];
NSLog(@"打印类%@,通过runtime获取类信息%@",manager2,object_getClass(manager2));
[manager2 mkj_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
NSLog(@"打印类%@,通过runtime获取类信息%@",manager2,object_getClass(manager2));
manager2.name = @"jiaojiao";
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(@"%@",change);
}
// 打印信息如下
2016-12-31 12:10:02.409 链式编程[44785:1429519] 打印类0x60800003eba0>,通过runtime获取类信息CalculateManager
2016-12-31 12:10:02.410 链式编程[44785:1429519] 打印类0x60800003eba0>,通过runtime获取类信息MKJKVONotifying_CalculateManager
2016-12-31 12:10:02.410 链式编程[44785:1429519] {
change = jiaojiao;
}
通过上述打印可以看出,实际的调用是在子类进行的,而且,外部调用还只是能看到父类的信息
上面的只是一些简单的思路而已,记录一下,内部实现其实还需要进一步学习,毕竟i love study,it make me happy,哈哈哈,其实思路这种东西,研究久了,只是提供了很多解决问题的方案和设计思路,项目中就能自然的想到解决方案,毕竟好的编程可读性非常高,而且能做到牛B的高聚合,低耦合,让读代码的人深深感叹,大哥你好牛B啊,就像读SDWebImage,Masonry等优质框架,你肯定会觉得,卧槽,大哥,你好牛b啊,这个时候,如果你是作者,你就能淡定的抛出:略懂,略懂!!!