基于ResponderChain的事件傳遞

ResponderChain對象交互方式本質(zhì)

響應(yīng)者鏈簡介

Responder Chain也就是響應(yīng)鏈镶苞,響應(yīng)者鏈?zhǔn)怯啥鄠€(gè)響應(yīng)者對象連接起來的鏈條。在iOS中響應(yīng)者鏈的關(guān)系可以用下圖表示:


響應(yīng)鏈圖片.png

響應(yīng)者鏈的事件傳遞過程:

1>如果當(dāng)前view是控制器的view柒傻,那么控制器就是上一個(gè)響應(yīng)者,事件就傳遞給控制器;如果當(dāng)前view不是控制器的view裤唠,那么父視圖就是當(dāng)前view的上一個(gè)響應(yīng)者砂客,事件就傳遞給它的父視圖

2>在視圖層次結(jié)構(gòu)的最頂級視圖泥张,如果也不能處理收到的事件或消息,則其將事件或消息傳遞給window對象進(jìn)行處理

3>如果window對象也不處理鞠值,則其將事件或消息傳遞給UIApplication對象

4>如果UIApplication也不能處理該事件或消息媚创,則將其丟棄

交互本質(zhì)

通過對UIResponder添加分類,實(shí)現(xiàn)事件沿響應(yīng)鏈條傳遞彤恶。(事件傳遞的方向與ResponderChain是保持一致的钞钙,如果未達(dá)到需要響應(yīng)的對象,可拋棄事件繼續(xù)向上進(jìn)行傳遞)

DEMO

cell點(diǎn)擊圖片.png
彈出alert.png

如圖所示声离,如果我們對Cell中的灰色按鈕點(diǎn)擊以實(shí)現(xiàn)控制器中彈出alert芒炼,一般的做法是點(diǎn)擊Cell按鈕后,通過代理或者block回調(diào)至控制器术徊,但兩者的缺點(diǎn)是:

代理:代碼較多

block:代碼較為分散

通過ResponderChain對象交互本刽,因?yàn)镃ell屬于控制器的子view,當(dāng)點(diǎn)擊Cell上的按鈕時(shí)弧关,事件會(huì)傳遞至控制器盅安,我們在控制器中攔截對應(yīng)的事件名稱,調(diào)用控制器的方法實(shí)現(xiàn)alert彈出世囊,而且如果有多個(gè)subView别瞭,事件也可以進(jìn)行統(tǒng)一的管理。

「Talk is cheap. Show me the code」

為UIResponder category

@interface UIResponder (Router)

/**
 為響應(yīng)鏈添加配對的方法株憾,以實(shí)現(xiàn)事件沿響應(yīng)鏈傳遞

 @param eventName 事件名稱
 @param userInfo 傳遞的參數(shù)
 */
- (void)routerEventWithName:(NSString *)eventName userInfo:(NSDictionary *)userInfo;

@end
@implementation UIResponder (Router)

- (void)routerEventWithName:(NSString *)eventName userInfo:(NSDictionary *)userInfo{
    [[self nextResponder] routerEventWithName:eventName userInfo:userInfo];
}

@end

Cell代碼

#import "ResponderTestTableViewCell.h"
#import "UIResponder+Router.h"
#import "ResponderChainName.h"

@implementation ResponderTestTableViewCell{
    UILabel     *_testLabel;
}

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
    if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
        [self initUI];
    }
    return self;
}

- (void)initUI{
    // cell中可點(diǎn)擊按鈕
    UIButton *testBtn = [UIButton buttonWithType:UIButtonTypeCustom];
    [testBtn setFrame:CGRectMake(100, 30, 110, 40)];
    [testBtn setBackgroundColor:[UIColor lightGrayColor]];
    [testBtn addTarget:self action:@selector(testBtnClick) forControlEvents:UIControlEventTouchUpInside];
    [self.contentView addSubview:testBtn];
    
    _testLabel = [[UILabel alloc] initWithFrame:CGRectMake(15, 0, 100, 100)];
    [_testLabel setFont:[UIFont systemFontOfSize:18]];
    [self.contentView addSubview:_testLabel];
}

- (void)configureTextWithIndexRow:(NSInteger)row{
    [_testLabel setText:[NSString stringWithFormat:@"row:%zd", row]];
}

- (void)testBtnClick{
    // 接收到點(diǎn)擊事件時(shí)蝙寨,將事件往上層傳遞
    [self.nextResponder routerEventWithName:kTestCellBtnClickedEvent userInfo:@{kUserInfoObject : _testLabel.text}];
}

@end

由于彈出alert的事件是發(fā)生在控制器中的晒衩,cell里不做處理,只是將事件名稱和參數(shù)向外傳遞

控制器相關(guān)代碼

- (void)routerEventWithName:(NSString *)eventName userInfo:(NSDictionary *)userInfo{
    [[EventProxy shareInstance] handleEvent:eventName userInfo:userInfo];
    
}

當(dāng)傳遞至控制器時(shí)墙歪,進(jìn)行事件的攔截听系,此處用了EventProxy類來進(jìn)行事件的統(tǒng)一處理

EventProxy是將根據(jù)外部傳入的事件名以及方法參數(shù),找到對應(yīng)target和對應(yīng)的方法虹菲,通過NSInvocation的方式進(jìn)行調(diào)用靠胜。

EventProxy相關(guān)實(shí)現(xiàn)

@implementation EventProxy

+ (EventProxy *)shareInstance{
    static EventProxy *shareInstance = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        shareInstance = [[EventProxy alloc] init];
    });
    return shareInstance;
}

- (void)handleEvent:(NSString *)eventName userInfo:(NSDictionary *)userInfo{
    NSInvocation *invocation = [self.eventStrategy objectForKey:eventName];
    if (invocation) {
        if (invocation.methodSignature.numberOfArguments > 2) {
            [invocation setArgument:&userInfo atIndex:2];
        }
        [invocation invoke];
    }
}

- (NSInvocation *)createInvocationWithTarget:(id)target selector:(SEL)action{
    if (!target) {
        return nil;
    }
    NSMethodSignature *methodSignature = [(NSObject *)target methodSignatureForSelector:action];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
    invocation.selector = action;
    invocation.target = target;
    return invocation;
}

- (NSMutableDictionary<NSString *,NSInvocation *> *)eventStrategy{
    if (_eventStrategy == nil) {
        _eventStrategy = @{}.mutableCopy;
    }
    return _eventStrategy;
}

// 重寫set方法是為了在設(shè)置其他target的時(shí)候,添加對應(yīng)的事件

- (void)setMainVc:(ViewController *)mainVc{
    _mainVc = mainVc;
    if (![self.eventStrategy objectForKey:kTestCellBtnClickedEvent]) {
        [self.eventStrategy setObject:[self createInvocationWithTarget:self.mainVc selector:NSSelectorFromString(@"testCellBtnClicked:")] forKey:kTestCellBtnClickedEvent];
    }
}

- (void)setTestAVc:(TestAViewController *)testAVc{
    _testAVc = testAVc;
    if (![self.eventStrategy objectForKey:kTestABtnClickedEvent]) {
        [self.eventStrategy setObject:[self createInvocationWithTarget:self.testAVc selector:NSSelectorFromString(@"hasPushedTestAViewController")] forKey:kTestABtnClickedEvent];
    }
}

@end

控制器中彈出TestA控制器并調(diào)用kTestABtnClickedEvent對應(yīng)事件實(shí)際開發(fā)中是用不到的毕源,此處只為了演示不在同一響應(yīng)鏈上的事件是怎樣傳遞的浪漠。

- (void)testABtnClicked{
    TestAViewController *testAVc = [[TestAViewController alloc] init];
    [self presentViewController:testAVc animated:YES completion:nil];
    [EventProxy shareInstance].testAVc = testAVc;
    [testAVc routerEventWithName:kTestABtnClickedEvent userInfo:nil];
}

小結(jié)

在我看來,響應(yīng)鏈傳遞事件這種做法最適合的場景是位于同一view上的多個(gè)子view的事件傳遞霎褐,對于不同target的事件傳遞址愿,并不是那樣契合,EventProxy類我的實(shí)現(xiàn)方式也不是很Elegant冻璃,在此只為拋磚引玉响谓,希望有好想法的小伙伴可以私聊或者評論。

DEMO

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末省艳,一起剝皮案震驚了整個(gè)濱河市娘纷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拍埠,老刑警劉巖失驶,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件土居,死亡現(xiàn)場離奇詭異枣购,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)擦耀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進(jìn)店門棉圈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人眷蜓,你說我怎么就攤上這事分瘾。” “怎么了吁系?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵德召,是天一觀的道長。 經(jīng)常有香客問我汽纤,道長上岗,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任蕴坪,我火速辦了婚禮肴掷,結(jié)果婚禮上敬锐,老公的妹妹穿的比我還像新娘。我一直安慰自己呆瞻,他們只是感情好台夺,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著痴脾,像睡著了一般颤介。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上赞赖,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天买窟,我揣著相機(jī)與錄音,去河邊找鬼薯定。 笑死始绍,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的话侄。 我是一名探鬼主播亏推,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼年堆!你這毒婦竟也來了吞杭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤变丧,失蹤者是張志新(化名)和其女友劉穎芽狗,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體痒蓬,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡童擎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了攻晒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片顾复。...
    茶點(diǎn)故事閱讀 40,488評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖鲁捏,靈堂內(nèi)的尸體忽然破棺而出芯砸,到底是詐尸還是另有隱情,我是刑警寧澤给梅,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布假丧,位于F島的核電站,受9級特大地震影響动羽,放射性物質(zhì)發(fā)生泄漏包帚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一曹质、第九天 我趴在偏房一處隱蔽的房頂上張望婴噩。 院中可真熱鬧擎场,春花似錦、人聲如沸几莽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽章蚣。三九已至站欺,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間纤垂,已是汗流浹背矾策。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留峭沦,地道東北人贾虽。 一個(gè)月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像吼鱼,于是被迫代替她去往敵國和親蓬豁。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評論 2 359