鏈?zhǔn)骄幊膛c響應(yīng)式編程

鏈?zhǔn)骄幊?/h3>

在實(shí)際開發(fā)過(guò)程中,基本上鏈?zhǔn)介_發(fā)思想很常見(jiàn)。第三方框架MasonryReactiveCocoa都大量用了這種思想。

鏈?zhǔn)骄幊?/code>其實(shí)就是將多個(gè)操作(多行代碼)通過(guò)點(diǎn)號(hào).鏈接在一起成為一句代碼

block

block在鏈?zhǔn)介_發(fā)中扮演這很重要的角色。通常我們調(diào)用一個(gè)block

Person * p = [[Person alloc] init];
[p result:^{
        
}]

然而我現(xiàn)在想將他改進(jìn)一下用.語(yǔ)法將其調(diào)用出來(lái),寫法如下

-(void(^)(int))result{
    return ^(int num){
        NSLog(@"num:%d",num);
    };
}

此時(shí)調(diào)用的時(shí)候

Person * p = [[Person alloc] init];
p.result(3);

打印:

2017-09-23 12:15:08.650 鏈?zhǔn)骄幊蘙26528:1560398] num:3

接下來(lái)我們做一些細(xì)微的修改,就變成了鏈?zhǔn)骄幊?/code>了

-(Person *(^)(int))result{
    static int sum = 0;
    return ^(int num){
        sum += num;
        NSLog(@"sum:%d",sum);
        return self;
    };
}

此時(shí)調(diào)用

 p.result(3).result(4).result(5);

打印:

2017-09-23 12:20:09.210 鏈?zhǔn)骄幊蘙26883:1581658] sum:3
2017-09-23 12:20:09.210 鏈?zhǔn)骄幊蘙26883:1581658] sum:7
2017-09-23 12:20:09.210 鏈?zhǔn)骄幊蘙26883:1581658] sum:12

細(xì)心的你應(yīng)該早已發(fā)現(xiàn)Masorny里面的

make.left.top.equalTo(@10);

和RAC中的

 [[signal filter:^BOOL(NSNumber *  _Nullable value) {
        return value > 2;
    }]subscribeNext:^(id  _Nullable x) {
        
    }];

這些都是鏈?zhǔn)剿枷?/p>

響應(yīng)式編程

響應(yīng)式編程(反應(yīng)式編程)是一種面向數(shù)據(jù)流和變化傳播的編程范式。
相信看完解釋后的同學(xué)應(yīng)該第一時(shí)間就想到了KVO。

KVO

首先創(chuàng)建一個(gè)Person對(duì)象,添加name屬性,然后給name添加KVO

[p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];

然后實(shí)現(xiàn)下面的函數(shù)

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context

當(dāng)p.name屬性發(fā)生變化時(shí)候,程序就回到了這個(gè)代理方法,監(jiān)聽到了name屬性變化。
不知道此時(shí)大家有沒(méi)有疑惑,KVO到底能不能監(jiān)聽到成員變量呢。帶著疑惑我做一個(gè)實(shí)驗(yàn),將name屬性改成成員變量,代碼如下:

//@property (nonatomic,strong) NSString * name;


NSString * _name;

這個(gè)時(shí)候,我們發(fā)現(xiàn)當(dāng)改變p->name的時(shí)候,KVO根本不回調(diào)。相信大家都知道,屬性是成員變量+setter方法getter方法組成。這個(gè)時(shí)候得出以下結(jié)論:KVO監(jiān)聽的是屬性的seter方法

修改isa

接下來(lái),在添加KVO的屬性時(shí)候加一個(gè)兩個(gè)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 鏈?zhǔn)骄幊蘙79382:12699417] Person
2017-09-25 09:27:40.098 鏈?zhǔn)骄幊蘙79382:12699417] NSKVONotifying_Person

從打印結(jié)果,我驚奇發(fā)現(xiàn)p變成了NSKVONotifying_Person。原來(lái)蘋果在添加KVO的時(shí)候,動(dòng)態(tài)創(chuàng)建了Person一個(gè)子類,實(shí)現(xiàn)該屬性的setter方法,修改原來(lái)Personisa指針指向NSKVONotifying_Person。當(dāng)屬性被修改,isa指向的中間類NSKVONotifying_Personsetter方法會(huì)被調(diào)用,接下來(lái)就是進(jìn)行下面一系列的操作了。

手動(dòng)實(shí)現(xiàn)KVO

既然明白了KVO的底層實(shí)現(xiàn)原理,那我們也嘗試實(shí)現(xiàn)一個(gè)自己的KVO。分析下來(lái),我們需要做的事情有:

  1. 在添加KVO的時(shí)候動(dòng)態(tài)創(chuàng)建一個(gè)子類
  2. 在子類里面實(shí)現(xiàn)屬性的setter屬性
  3. isa指針指向子類
  4. 在子類setter調(diào)用父類方法修改屬性
  5. 通知observe收到屬性更新的動(dòng)作

有經(jīng)驗(yàn)的童鞋應(yīng)該已經(jīng)發(fā)現(xiàn),修改isa指針蘋果內(nèi)部可以做,但是沒(méi)有提供方法給開發(fā)者用。查閱資料發(fā)現(xiàn)可以用object_setClass進(jìn)行代替。

接下來(lái)創(chuàng)建一個(gè)類別:NSObject+KVO,然后實(shí)現(xiàn)以下代碼

- (void)lq_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{
    
    //動(dòng)態(tài)生成一個(gè)類
    //獲取類名
    NSString * className = NSStringFromClass(self.class);
    NSString * newClassName = [NSString stringWithFormat:@"LQKVO_%@",className];
    const char * name = [newClassName UTF8String];
    
    //動(dòng)態(tài)創(chuàng)建一個(gè)類
    Class myClass = objc_allocateClassPair([self class], name, 0);
    //注冊(cè)類
    objc_registerClassPair(myClass);
    //修改isa
    object_setClass(self, myClass);
    
    //添加方法
    class_addMethod(myClass, @selector(setName:), (IMP)setName, "");
    
void setName(NSString * newName){
    NSLog(@"我收到消息了%@",newName);
}

在VC中實(shí)現(xiàn)

[p lq_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:0];

Command + R運(yùn)行,控制臺(tái)打印

2017-09-24 12:12:58.730 鏈?zhǔn)骄幊蘙69869:4654336] 我收到消息了<LQKVO_Person: 0x604000029500>

看到這個(gè)打印結(jié)果,我懵逼了,尼瑪我傳的不是name嗎,為什么會(huì)打印我動(dòng)態(tài)創(chuàng)建的這個(gè)類呢?難道是系統(tǒng)出問(wèn)題了,接連運(yùn)行幾次結(jié)果還是一樣。

去蘋果的文檔中心查找class_addMethod方法,從他的示例代碼中我發(fā)現(xiàn)了下面的代碼

void myMethodIMP(id self, SEL _cmd)
{
    // implementation ....
}

這時(shí)候我瞬間明白了,原來(lái)這個(gè)方法存在兩個(gè)隱藏參數(shù),于是將代碼改成

void setName(id self, SEL _cmd, NSString * newName)
    NSLog(@"我收到消息了%@",newName);
}

Command + R運(yùn)行,控制臺(tái)打印

2017-09-24 12:24:02.532 鏈?zhǔn)骄幊蘙70499:4698280] 我收到消息了lq

完美,終于搞定了,繼續(xù)完成下面的內(nèi)容。

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();
}

當(dāng)我拿到父類準(zhǔn)備賦值的時(shí)候,準(zhǔn)備調(diào)用父類的setName方法。
但是這里objc_msgSend卻怎么都敲不出參數(shù),Command點(diǎn)擊進(jìn)去看到以下內(nèi)容。

#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

由于自己經(jīng)驗(yàn)水平一般,真的沒(méi)看懂那個(gè)宏具體是要干嘛,但是我肯定是蘋果在某種特定的情況下將它禁止了。去網(wǎng)上查閱資料得知,蘋果不推薦使用objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)該方法,可是我們一定要拿來(lái)玩玩,蘋果也是不會(huì)禁止的。build Setting搜索objc_msgSend改成NO即可

image.png

經(jīng)過(guò)自己一步一步的踩坑,終于完成以下代碼

- (void)lq_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{
    
    //動(dòng)態(tài)生成一個(gè)類
    //獲取類名
    NSString * className = NSStringFromClass(self.class);
    NSString * newClassName = [NSString stringWithFormat:@"LQKVO_%@",className];
    const char * name = [newClassName UTF8String];
    
    //動(dòng)態(tài)創(chuàng)建一個(gè)類
    Class myClass = objc_allocateClassPair([self class], name, 0);
    //注冊(cè)類
    objc_registerClassPair(myClass);
    //修改isa
    object_setClass(self, myClass);
    
    //添加方法
    class_addMethod(myClass, @selector(setName:), (IMP)setName, "v@:@");
    
    //將觀察者保存到當(dāng)前對(duì)象中
    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);
    //拿到當(dāng)前屬性
    id observer = objc_getAssociatedObject(self, @"key");
    objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:),@"name",self,@{@"name":newName},nil);
}

至此,自定義KVO已經(jīng)完成了,趕快給自己點(diǎn)個(gè)贊吧!??????

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,757評(píng)論 0 9
  • 光影變化,夜晚才不會(huì)孤單, 記錄生活中的每一種美, 變成一個(gè)美好的人, 做美好的事情。 拍攝地點(diǎn):撒拉小館,公園,...
    愛(ài)羅莎閱讀 307評(píng)論 2 1
  • 1
    劉有才閱讀 142評(píng)論 0 0
  • 你要如何原諒我的過(guò)錯(cuò) 像我希望的一樣? 如何明白我的措辭 是我斟酌半生的思量 如何才能逃脫這牢籠 掙脫這枷鎖 卻一...
    Z___Y_閱讀 236評(píng)論 0 0
  • 專門請(qǐng)了一天假,自己坐在萬(wàn)圣書園的醒客咖啡,來(lái)回顧2016和制定2017的計(jì)劃。儀式感還是必要的。 希望2017年...
    一個(gè)本子閱讀 106評(píng)論 0 0