Masonry链式编程思想的基本思路以及KVO底层的响应式编程

Masonry基本使用

[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的参数就是需要内部对象操作的值

1.通过链式来实现简单的计算器

从后面往前面推,先来看看我们最终需要的调用链式代码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的返回值就是对象,循环进行再次调用,最终内部传参的对象把结果存储在属性字段中,可以根据函数返回值返回出来,打印最终需要的结果

2.通过KVO研究下响应式编程思路

链式编程思想就是,不考虑调用顺序,只在乎结果,事件分发出去,只要有人监听,就能进行下一步操作,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

Masonry链式编程思想的基本思路以及KVO底层的响应式编程_第1张图片


Masonry链式编程思想的基本思路以及KVO底层的响应式编程_第2张图片


注意看:在给对象添加观察者之前,对象的class、runtime真实class和isa指针都是CalculateManager 对象,这很正常,但是增加观察者之后呢,注意看,class还是CalculateManager ,但是isa指针和runtime的getclass暴露了一切,已经变了一个子类NSKVONotifying_CalculateManager 这里好像在哪看到过,叫做门面设计模式,对外隐藏了一切类信息,就连直接打印class都被重写了,还是返回原本的父类,但是内在确确实实被动态改变了,当我们监听的属性改变的时候,其实就是监听到set方法的调用,重写动态创建那个子类的set方法,然后由子类通知观察者,我已经被改变,呵呵,你该干嘛干嘛
Masonry链式编程思想的基本思路以及KVO底层的响应式编程_第3张图片


既然监听的是属性的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);
}

3.模仿下KVO自己实现下响应式编程思路

回顾下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啊,这个时候,如果你是作者,你就能淡定的抛出:略懂,略懂!!!
Masonry链式编程思想的基本思路以及KVO底层的响应式编程_第4张图片

你可能感兴趣的:(基础知识)