Aspects源碼解析之Block的動(dòng)態(tài)調(diào)用

1、前言

我們?cè)诜治鯞lock的動(dòng)態(tài)調(diào)用之前劫瞳,先簡(jiǎn)單了解一下消息的轉(zhuǎn)發(fā)機(jī)制倘潜。作為一個(gè)iOS開(kāi)發(fā)者,消息的轉(zhuǎn)發(fā)機(jī)制應(yīng)該都是我們耳熟能詳?shù)闹R(shí)點(diǎn)了志于,這里貼一個(gè)轉(zhuǎn)發(fā)流程圖涮因,不再占用篇幅:

消息轉(zhuǎn)發(fā)流程簡(jiǎn)圖.png
那我們今天也是使用到NSMethodSignatureNSInvocation來(lái)分析Block的動(dòng)態(tài)調(diào)用。

2伺绽、實(shí)例探究

先解釋一些概念
方法的構(gòu)成:在runtime源碼中养泡,可以清晰的看到,方法method_t是由SEL + types + IMP 組成奈应。
1澜掩、SEL:方法的編號(hào)。
2杖挣、types:方法的簽名肩榕。簡(jiǎn)單的說(shuō)方法簽名包含:返回值、參數(shù)惩妇、參數(shù)類型株汉,一個(gè)簡(jiǎn)單的例子v@:v對(duì)應(yīng)返回值為void歌殃,@對(duì)應(yīng)一個(gè)id類型的對(duì)象乔妈,:對(duì)應(yīng)SEL。具體對(duì)比可以參照Type Encodings氓皱。
3路召、IMP:一個(gè)函數(shù)指針勃刨,保存的是方法的地址。

接下來(lái)我們來(lái)看下面這段代碼:

- (void)viewDidLoad {
    [super viewDidLoad];
    // 方法的簽名
    NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:"];
    // NSInvocation對(duì)象
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    [invocation setTarget:self];
    [invocation setSelector:@selector(testInvocation)];
    [invocation invoke];
}

- (void)testInvocation {
    
    NSLog(@"testInvocation");
}

顯然股淡,我們的testInvocation方法會(huì)被調(diào)用身隐。由我的 上篇文章 可以知道Block的本身也是對(duì)象,那Block如何進(jìn)行動(dòng)態(tài)調(diào)用呢揣非?

我們?cè)倏聪旅鍮lock動(dòng)態(tài)調(diào)用的簡(jiǎn)單寫(xiě)法:

- (void)viewDidLoad {
    [super viewDidLoad];

    void (^testBlock)(NSString *, double) = ^(NSString *test, double a) {
        
        NSLog(@"block test %@ -- %.1f", test, a);
    };

    NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@?@d"];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    [invocation setTarget:testBlock];
    NSString *str = @"invocation block";
    [invocation setArgument:&str atIndex:1];
    double dou = 0.1;
    [invocation setArgument:&dou atIndex:2];
    [invocation invoke];
}

這是一段帶有參數(shù)Block的動(dòng)態(tài)調(diào)用代碼抡医,值得說(shuō)的是,在一般方法的NSInvocation 中setArgument : atIndex :的index傳的值是從2開(kāi)始的早敬,而B(niǎo)lock的動(dòng)態(tài)調(diào)用是從1開(kāi)始的忌傻。原因是跟我們的方法簽名有關(guān),在越界信息中我們是可以看到所有types的遍歷是從-1開(kāi)始的-[NSInvocation setArgument:atIndex:]: index (3) out of bounds [-1, 2]搞监,一般方法的v@:分別是3個(gè)獨(dú)立的代表水孩,而在Blockv@?中@?是綁定一起的。(沒(méi)有相關(guān)解釋文檔)

下面再來(lái)解析Aspects源碼中如何實(shí)現(xiàn)的琐驴。本質(zhì)上來(lái)講俘种,跟上面的簡(jiǎn)單寫(xiě)法一樣。具體的源碼解析绝淡,網(wǎng)上已經(jīng)有一大堆的文章宙刘,這里只做簡(jiǎn)單說(shuō)明,主要說(shuō)一下源碼中對(duì)Block的動(dòng)態(tài)調(diào)用牢酵。
Aspects核心框架.jpg

首先看調(diào)用的API:

+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error;

來(lái)看源碼中定義了AspectBlockRef的結(jié)構(gòu)體:

AspectBlockRef定義.jpg
此Block結(jié)構(gòu)體的定義和我的 上篇文章 的Clang編譯結(jié)構(gòu)一樣悬包,也可以 Clang官方文檔 查看其定義。主要查看AspectIdentifier類就可以了馍乙。首先初始化的時(shí)候布近,在這個(gè)aspect_blockMethodSignature方法中,生成Block的NSMethodSignature丝格,并且包裝在AspectIdentifier對(duì)象中撑瞧。后續(xù)再- (BOOL)invokeWithInfo:(id<AspectInfo>)info中,生成hook方法的NSInvocation中显蝌,觸發(fā)block的調(diào)用预伺。
下面貼上AspectIdentifier類中Block動(dòng)態(tài)調(diào)用過(guò)程的注釋。

+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error {
    NSCParameterAssert(block);
    NSCParameterAssert(selector);
    NSMethodSignature *blockSignature = aspect_blockMethodSignature(block, error); // TODO: check signature compatibility, etc.
    if (!aspect_isCompatibleBlockSignature(blockSignature, object, selector, error)) {
        return nil;
    }

    AspectIdentifier *identifier = nil;
    if (blockSignature) {
        identifier = [AspectIdentifier new];
        // 方法的聲明
        identifier.selector = selector;
        // block對(duì)象
        identifier.block = block;
        // block的簽名信息
        identifier.blockSignature = blockSignature;
        identifier.options = options;
        identifier.object = object; // weak
    }
    return identifier;
}

- (BOOL)invokeWithInfo:(id<AspectInfo>)info {
    // 生成NSInvocation對(duì)象
    NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature];
    // 交換之前的 invocation
    NSInvocation *originalInvocation = info.originalInvocation;
    // 參數(shù)個(gè)數(shù)
    NSUInteger numberOfArguments = self.blockSignature.numberOfArguments;

    // Be extra paranoid. We already check that on hook registration.
    // 檢查block中寫(xiě)了幾個(gè)參數(shù)曼尊,如果比原方法個(gè)數(shù)還多扭屁,那么是有問(wèn)題的
    if (numberOfArguments > originalInvocation.methodSignature.numberOfArguments) {
        AspectLogError(@"Block has too many arguments. Not calling %@", info);
        return NO;
    }

    // The `self` of the block will be the AspectInfo. Optional.
    // 如果block中有參數(shù),那么先把info放到1位置上
    if (numberOfArguments > 1) {
        [blockInvocation setArgument:&info atIndex:1];
    }
    // 根據(jù)block中的參數(shù)個(gè)數(shù)涩禀,從原方法中逐個(gè)賦值過(guò)來(lái)
    void *argBuf = NULL;
    for (NSUInteger idx = 2; idx < numberOfArguments; idx++) {
        const char *type = [originalInvocation.methodSignature getArgumentTypeAtIndex:idx];
        NSUInteger argSize;
        NSGetSizeAndAlignment(type, &argSize, NULL);
        
        if (!(argBuf = reallocf(argBuf, argSize))) {
            AspectLogError(@"Failed to allocate memory for block invocation.");
            return NO;
        }
        
        [originalInvocation getArgument:argBuf atIndex:idx];
        [blockInvocation setArgument:argBuf atIndex:idx];
    }
    // 執(zhí)行block
    [blockInvocation invokeWithTarget:self.block];
    
    if (argBuf != NULL) {
        free(argBuf);
    }
    return YES;
}

3、最后

學(xué)無(wú)止境然眼。在源碼分析中艾船,還是要主要學(xué)思想,知識(shí)點(diǎn),架構(gòu)屿岂。歡迎各位大佬點(diǎn)評(píng)践宴、斧正。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末爷怀,一起剝皮案震驚了整個(gè)濱河市阻肩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌运授,老刑警劉巖烤惊,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異吁朦,居然都是意外死亡柒室,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)逗宜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)雄右,“玉大人,你說(shuō)我怎么就攤上這事纺讲±奕裕” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵熬甚,是天一觀的道長(zhǎng)逢渔。 經(jīng)常有香客問(wèn)我,道長(zhǎng)则涯,這世上最難降的妖魔是什么复局? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮粟判,結(jié)果婚禮上亿昏,老公的妹妹穿的比我還像新娘。我一直安慰自己档礁,他們只是感情好角钩,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著呻澜,像睡著了一般递礼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上羹幸,一...
    開(kāi)封第一講書(shū)人閱讀 49,046評(píng)論 1 285
  • 那天脊髓,我揣著相機(jī)與錄音,去河邊找鬼栅受。 笑死将硝,一個(gè)胖子當(dāng)著我的面吹牛恭朗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播依疼,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼痰腮,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了律罢?” 一聲冷哼從身側(cè)響起膀值,我...
    開(kāi)封第一講書(shū)人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎误辑,沒(méi)想到半個(gè)月后沧踏,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡稀余,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年悦冀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片睛琳。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡盒蟆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出师骗,到底是詐尸還是另有隱情历等,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布辟癌,位于F島的核電站寒屯,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏黍少。R本人自食惡果不足惜寡夹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望厂置。 院中可真熱鬧菩掏,春花似錦、人聲如沸昵济。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)访忿。三九已至瞧栗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間海铆,已是汗流浹背迹恐。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留卧斟,地道東北人殴边。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓通熄,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親找都。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

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

  • 摘要:Aspects用來(lái)干什么?Aspect是一個(gè)簡(jiǎn)潔高效的用于使iOS支持AOP(面向切面編程)的框架廊酣。官方描述...
    京北磊哥閱讀 856評(píng)論 0 1
  • Go-ethereum 源碼解析之 miner/worker.go (下) Appendix D. 詳細(xì)批注 1....
    furnace閱讀 1,914評(píng)論 0 0
  • 我們常常會(huì)聽(tīng)說(shuō) Objective-C 是一門(mén)動(dòng)態(tài)語(yǔ)言能耻,那么這個(gè)「動(dòng)態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,176評(píng)論 0 7
  • 關(guān)于OC中的消息發(fā)送的實(shí)現(xiàn)亡驰,在去年也看過(guò)一次晓猛,當(dāng)時(shí)有點(diǎn)不太理解,但是今年再看卻很容易理解凡辱。 我想這跟知識(shí)體系的構(gòu)建...
    咖啡綠茶1991閱讀 934評(píng)論 0 1
  • 見(jiàn)字如晤戒职。 emmmm…8月7日,又是一年的立秋節(jié)氣透乾,同時(shí)也是你的生日洪燥,首先祝你生日快樂(lè)阿。97年的你也20周歲了...
    劉阿朱閱讀 203評(píng)論 0 0