鏈?zhǔn)骄幊?/h3>
在實(shí)際開發(fā)過(guò)程中,基本上鏈?zhǔn)介_發(fā)思想很常見(jiàn)。第三方框架Masonry
和ReactiveCocoa
都大量用了這種思想。
鏈?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)Person
的isa
指針指向NSKVONotifying_Person
。當(dāng)屬性被修改,isa
指向的中間類NSKVONotifying_Person
的setter
方法會(huì)被調(diào)用,接下來(lái)就是進(jìn)行下面一系列的操作了。
手動(dòng)實(shí)現(xiàn)KVO
既然明白了KVO的底層實(shí)現(xiàn)原理,那我們也嘗試實(shí)現(xiàn)一個(gè)自己的KVO。分析下來(lái),我們需要做的事情有:
- 在添加KVO的時(shí)候動(dòng)態(tài)創(chuàng)建一個(gè)子類
- 在子類里面實(shí)現(xiàn)屬性的
setter
屬性 - 將
isa
指針指向子類 - 在子類
setter
調(diào)用父類方法修改屬性 - 通知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
即可
經(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è)贊吧!??????