前言
上一篇文章講了對于MVVM的理解,最后提到了會寫一些模仿RAC
的小技巧.在后面的研究中,我發(fā)現(xiàn)在利用RAC
實現(xiàn)登錄的demo中有這樣一行代碼:
///監(jiān)聽文本框輸入狀態(tài),確定按鈕是否可以點擊
RAC(_loginBtn,enabled) = [RACSignal combineLatest:@[_accountTF.rac_textSignal,_passwordTF.rac_textSignal] reduce:^id _Nullable(NSString * account,NSString * password){
return @(account.length && (password.length > 5));
}];
這里綁定了賬號和密碼兩個輸入信號,然后在reduce
這個block
中回調(diào)了account
和password
這兩個參數(shù),很好奇為啥這個block
能準確的回調(diào)出兩個參數(shù),于是去看了一下實現(xiàn)源碼,然后自己也寫出了一個簡單的實現(xiàn).
先看一個基礎的例子.
最基本的方式
首先我們定義一個block
:
typedef id(^TBlock)() // 第二個括號不能加void
注意:這里XCode會警告你要加void
,但這里不能加,因為無參數(shù)是添加不定參數(shù)的前提.如果要消除這個警告.可以用下面的代碼包裹:
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wstrict-prototypes\"") \
typedef id(^TBlock)();
_Pragma("clang diagnostic pop")
然后我們寫一個這樣的方法:
- (void)numbersOfArguments:(NSUInteger)count block:(TBlock)block{
if (!block) {
return;
}
switch (count) {
case 0:
{
block();
}
break;
case 1:
{
block(@"one");
}
break;
case 2:
{
block(@"one",@"two");
}
break;
default:
break;
}
}
調(diào)用方式如下:
[self numbersOfArguments:2 block:^id(NSString *arg1,NSString *arg2){
NSLog(@"%@ %@",arg1,arg2);
return arg1;
}];
這樣我們通過指定參數(shù)的個數(shù)就可以獲取多個參數(shù)的block的回調(diào)了.但這種方式很僵硬的地方在于需要指定參數(shù)個數(shù),顯得非常不靈活.如果要通過這種方式,達到RAC的效果也是很困難的,因為你無法確定綁定信號個數(shù)多少,所以不知道怎么傳這個count
參數(shù).
利用invocation實現(xiàn)
看RAC的這種寫法,我剛開始的想法是:通過自己構(gòu)造一個block的結(jié)構(gòu)體,將傳入的block進行復制,再從這個復制的block中,拿到原block的方法簽名(signature),然后拿到參數(shù)個數(shù),從而來確定參數(shù)個數(shù)
,貌似這樣也能實現(xiàn).但我去看RAC源碼的時候,發(fā)現(xiàn)它用了更加巧妙的辦法.
先寫出我自己實現(xiàn)的方法:
- (void)bindConditions:(NSArray *)conditions bindBlock:(TBlock)block{
if (conditions.count == 0) {
return;
}
self.block = [block copy];
id returnValue = [self invokeWithArguments:conditions];
}
- conditions : 指傳入的參數(shù)數(shù)組.
-
block : 指回調(diào)的
block
,這個block
是沒有參數(shù)的.
重點就在于這個invokeWithArguments
方法.
- (id)invokeWithArguments:(NSArray *)conditions{
// 這里拿到不定參數(shù)的方法
SEL selector = [self selectorForArgumentCount:conditions.count];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:selector]];
invocation.selector = selector;
for (NSInteger i = 0; i < conditions.count; i++) {
id arg = conditions[i];
NSInteger argIndex = i + 2;
[invocation setArgument:&arg atIndex:argIndex];
}
[invocation invokeWithTarget:self];
// 拿到返回值,__unsafe_unretained的作用是不立即釋放
__unsafe_unretained id returnVal;
[invocation getReturnValue:&returnVal];
return returnVal;
}
上面的代碼其實很普通,很容易理解,唯一可能有點疑問的在于SEL selector = [self selectorForArgumentCount:conditions.count];
這行代碼,這就是RAC里比較巧妙的地方.這個方法內(nèi)容是:
- (SEL)selectorForArgumentCount:(NSUInteger)count {
NSCParameterAssert(count > 0);
switch (count) {
case 0: return NULL;
case 1: return @selector(performWith:);
case 2: return @selector(performWith::);
}
// 暫時只支持 2個參數(shù)
NSCAssert(NO, @"The argument count is too damn high! Only blocks of up to 2 arguments are currently supported.");
return NULL;
}
- (id)performWith:(id)obj1 {
id (^block)(id) = self.block;
return block(obj1);
}
- (id)performWith:(id)obj1 :(id)obj2 {
id (^block)(id, id) = self.block;
return block(obj1, obj2);
}
到這里,不知道大家看懂其中的巧妙之處沒有.其實就是將block
調(diào)用包裝成了performWith
方法,通過@selector(performWith::)
這種方式確定了參數(shù)的個數(shù),從而實現(xiàn)了不定參數(shù)的回調(diào).這里RAC
里是指定了最多存在15個參數(shù),我這里只限定了2個.
這里將參數(shù)回調(diào)出去之后,我們就可以拿到這些參數(shù)在block
中做一些邏輯處理了,而不用分散寫到其他地方,不過這里并沒有進行相關(guān)監(jiān)聽,所以并不能根據(jù)值改變來改變狀態(tài)(待完成).另外,開頭的RAC例子
中,它的block
是有返回值的,并且根據(jù)Button
綁定的不同狀態(tài),返回了不同的值.例如enabled
這個屬性,返回的是NSNumber
,backgroundColor
返回的是UIColor
類型.
在上面的invokeWithArguments
方法中,其實我們已經(jīng)拿到了方法的返回值,后面要解決的問題就是如何根據(jù)綁定的屬性,返回對應的類型值
,具體實現(xiàn)且聽下回分解.