[iOS][Block]block不定參數(shù)的回調(diào)方式

前言

上一篇文章講了對于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)了accountpassword這兩個參數(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)且聽下回分解.

demo地址在此

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市萌京,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌臭觉,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辱志,死亡現(xiàn)場離奇詭異蝠筑,居然都是意外死亡,警方通過查閱死者的電腦和手機揩懒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門什乙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人已球,你說我怎么就攤上這事臣镣。” “怎么了智亮?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵忆某,是天一觀的道長。 經(jīng)常有香客問我阔蛉,道長弃舒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任馍忽,我火速辦了婚禮棒坏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘遭笋。我一直安慰自己坝冕,他們只是感情好,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布瓦呼。 她就那樣靜靜地躺著喂窟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪央串。 梳的紋絲不亂的頭發(fā)上磨澡,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天,我揣著相機與錄音质和,去河邊找鬼稳摄。 笑死,一個胖子當著我的面吹牛饲宿,可吹牛的內(nèi)容都是我干的厦酬。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼瘫想,長吁一口氣:“原來是場噩夢啊……” “哼仗阅!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起国夜,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤减噪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后车吹,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體筹裕,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年礼搁,在試婚紗的時候發(fā)現(xiàn)自己被綠了饶碘。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡馒吴,死狀恐怖扎运,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情饮戳,我是刑警寧澤豪治,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站扯罐,受9級特大地震影響负拟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜歹河,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一掩浙、第九天 我趴在偏房一處隱蔽的房頂上張望花吟。 院中可真熱鬧,春花似錦厨姚、人聲如沸衅澈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽今布。三九已至,卻和暖如春拭抬,著一層夾襖步出監(jiān)牢的瞬間部默,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工造虎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留傅蹂,地道東北人。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓算凿,卻偏偏與公主長得像贬派,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子澎媒,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348