探究Block之MethodSignature

在iOS開發(fā)中,Block是常用的數(shù)據(jù)類型,Block的源碼是開放的,對于Blcok的其他探究可以查看這篇文章深入研究Block捕獲外部變量和__block實現(xiàn)原理.先來簡單介紹一般MethodSignature的獲取和配合Invocation的使用.

方法簽名

Person類為例,該類只有一個類方法和對象方法:

@interface Person : NSObject

- (void)say:(NSString *)name;
+ (void)setAge:(int)age;

@end

@implementation Person

- (void)say:(NSString *)name
{
    NSLog(@"say : %@",name);
}

+ (void)setAge:(int)age{
}
@end

一般我們獲取對象方法和類方法的簽名可以這樣:

/// 根據(jù)方法簽名獲取TypeEncoding
NSString *getMethodSignatureTypeEncoding(NSMethodSignature *methodSignature){
    NSMutableString *str = @"".mutableCopy;
    const char *rtvType = methodSignature.methodReturnType;
    [str appendString:[NSString stringWithUTF8String:rtvType]];

    for (int i = 0; i < methodSignature.numberOfArguments; i ++) {
        const char *type = [methodSignature getArgumentTypeAtIndex:i];
        [str appendString:[NSString stringWithUTF8String:type]];
    }
    return [str copy];
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {

    // 對象方法簽名
    NSMethodSignature *instanceMethodSignature = [Person instanceMethodSignatureForSelector:@selector(say:)];
    // 類方法簽名
    NSMethodSignature *classMethodSignature = [Person methodSignatureForSelector:@selector(setAge:)];
    NSLog(@"instanceMethodSignature : %@ \n classMethodSignature : %@",getMethodSignatureTypeEncoding(instanceMethodSignature),getMethodSignatureTypeEncoding (classMethodSignature));

    }
    return 0;
}

/*
  打印結(jié)果:
 instanceMethodSignature : v@:@ 
 classMethodSignature : v@:i
*/

方法簽名記錄著一個方法的返回值類型編碼(TypeEncoding)、形參個數(shù)期揪、每一個形參的類型編碼.有了方法簽名之后,可以通過類型編碼來反解出真實類型,類型的映射關(guān)系可以查看官方資料.
根據(jù)Personsay:方法獲得的方法簽名結(jié)果為v@:@,v代表void類型,@代表對象類型,對應(yīng)的是隱藏參數(shù)self,:代表SEL類型,對應(yīng)的是隱藏參數(shù)_cmd,@對應(yīng)的是第一個形參.每一個OC的類方法或?qū)ο蠓椒ǘ紩瑑蓚€隱藏參數(shù)self_cmd,并且它們的位置是固定的,在invocation對象中的索引分別是0和1,返回值是不占索引的,需要額外獲取.

配合NSInvocation使用

有了方法簽名之后,我們可以組裝一個NSInvocation對象并調(diào)用對應(yīng)方法,這里就簡單直接設(shè)置了索引2為name了,一般都會動態(tài)循環(huán)設(shè)置設(shè)置,例如JSPatch.

Person *p = [Person new];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:instanceMethodSignature];
invocation.selector = @selector(say:);
NSString *name = @"abc";
[invocation setArgument:&name atIndex:2];
[invocation invokeWithTarget:p];
/*
  打印結(jié)果: say : abc
*/

如果不是block對象,invocation必須要設(shè)置selector,原因跟類的方法結(jié)構(gòu)有關(guān),關(guān)于類的方法結(jié)構(gòu)<<深入解析 ObjC 中方法的結(jié)構(gòu)>>這篇文章分析的非常深入.Aspects也對NSInvocation做了分類來獲取方法簽名并獲取對應(yīng)實參.

Block的方法簽名

Aspects 源碼中,有一段獲取Blcok方法簽名的代碼比較有意思:

typedef NS_OPTIONS(int, AspectBlockFlags) {
    AspectBlockFlagsHasCopyDisposeHelpers = (1 << 25),
    AspectBlockFlagsHasSignature          = (1 << 30)
};
typedef struct _AspectBlock {
    __unused Class isa;
    AspectBlockFlags flags;
    __unused int reserved;
    void (__unused *invoke)(struct _AspectBlock *block, ...);
    struct {
        unsigned long int reserved;
        unsigned long int size;
        // requires AspectBlockFlagsHasCopyDisposeHelpers
        void (*copy)(void *dst, const void *src);
        void (*dispose)(const void *);
        // requires AspectBlockFlagsHasSignature
        const char *signature;
        const char *layout;
    } *descriptor;
    // imported variables
} *AspectBlockRef;

static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
    AspectBlockRef layout = (__bridge void *)block;
    if (!(layout->flags & AspectBlockFlagsHasSignature)) {
        NSString *description = [NSString stringWithFormat:@"The block %@ doesn't contain a type signature.", block];
        AspectError(AspectErrorMissingBlockSignature, description);
        return nil;
    }
    void *desc = layout->descriptor;
    desc += 2 * sizeof(unsigned long int);
    if (layout->flags & AspectBlockFlagsHasCopyDisposeHelpers) {
        desc += 2 * sizeof(void *);
    }
    if (!desc) {
        NSString *description = [NSString stringWithFormat:@"The block %@ doesn't has a type signature.", block];
        AspectError(AspectErrorMissingBlockSignature, description);
        return nil;
    }
    const char *signature = (*(const char **)desc);
    return [NSMethodSignature signatureWithObjCTypes:signature];
}

_AspectBlock結(jié)構(gòu)體是不是很熟悉? 對,就是跟Block指向的結(jié)構(gòu)體是基本一樣的!為什么說基本一樣的呢?因為官方文檔里面block是長這樣子的:

struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};

先來分析一下這段代碼,AspectBlockFlags枚舉是block的標(biāo)記.aspect_blockMethodSignature方法先強制將Blcok轉(zhuǎn)換成了AspectBlockRef類型指針,然后利用flags判斷是否存在方法簽名,再獲取block的descriptor,descriptor結(jié)構(gòu)體的copy或者signature存在著方法簽名.但是由于全局block和堆block存在的位置不同,所以需要判斷.如果是全局block,則方法簽名存在于signature上,如果是堆block,則存在于copy上.我們可以來驗證一下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // _NSConcreteMallocBlock 堆block
        __block int a = 0;
        id mallocBlock = ^(int b){
            a ++;
            NSLog(@"mallocBlock: a = %d ,b = %d",a,b);
        };

        // _NSConcreteGlobalBlock 全局block
        id globalBlock = ^(NSString *name){
            NSLog(@"globalBlock : name = %@",name);
        };

        NSMethodSignature *mallocBlockSignature = aspect_blockMethodSignature(mallocBlock, NULL);
        NSMethodSignature *globalBlockSignature = aspect_blockMethodSignature(globalBlock, NULL);

        NSLog(@"mallocBlockSignature : %@ \n globalBlockSignature : %@",getMethodSignatureTypeEncoding(mallocBlockSignature),getMethodSignatureTypeEncoding (globalBlockSignature));

    }
    return 0;
}

/*
  打印結(jié)果: 
 mallocBlockSignature : v@?i 
 globalBlockSignature : v@?@"NSString"
*/

aspect_blockMethodSignature方法的desc += 2 * sizeof(void *);處打斷點,然后發(fā)現(xiàn)全局block執(zhí)行了這句,而堆block沒有執(zhí)行這句.

棧block由于在ARC下無法驗證,所以目前不知道.

我們發(fā)現(xiàn)block的方法簽名和對象的方法簽名不一樣,第二位和第三位不再是@:了,而是被@?這個block標(biāo)記代替了.再看看類型編碼,基本數(shù)據(jù)類型在兩者情況下是一樣的,但是對象類型在block的方法簽名對應(yīng)的@后面會標(biāo)明對象類型.

調(diào)用block

既然獲取到了block的方法簽名,這樣就可以組裝一個NSInvocation對象來調(diào)用block.

__block int a = 0;
id mallocBlock = ^(int b){
    a ++;
    NSLog(@"mallocBlock: a = %d ,b = %d",a,b);
};
NSMethodSignature *methodSignature = aspect_blockMethodSignature(mallocBlock, NULL);
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
int b = 1;
[invocation setArgument:&b atIndex:1];
[invocation invokeWithTarget:mallocBlock];
/*
  打印結(jié)果: mallocBlock: a = 1 ,b = 1
*/

由于block的方法簽名最前面除了返回值以外只有@?,所以所以需要從1開始設(shè)置參數(shù).

總結(jié):我們可以利用block來表示一個方法的實現(xiàn),block的參數(shù)類型和個數(shù)可以根據(jù)實際的方法來動態(tài)一一對應(yīng).這樣做的好處是如果方法的參數(shù)個數(shù)和類型是不確定的,利用block可以將整個方法的實現(xiàn)替換成一個block,可以達到替換任意方法的目的,這也是Aspects這個AOP實現(xiàn)框架的重要一部分.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末奉芦,一起剝皮案震驚了整個濱河市蔑滓,隨后出現(xiàn)的幾起案子色解,更是在濱河造成了極大的恐慌根悼,老刑警劉巖却音,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件楞抡,死亡現(xiàn)場離奇詭異瓢湃,居然都是意外死亡理张,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進店門绵患,熙熙樓的掌柜王于貴愁眉苦臉地迎上來雾叭,“玉大人,你說我怎么就攤上這事落蝙≈” “怎么了?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵筏勒,是天一觀的道長移迫。 經(jīng)常有香客問我,道長管行,這世上最難降的妖魔是什么厨埋? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮捐顷,結(jié)果婚禮上荡陷,老公的妹妹穿的比我還像新娘。我一直安慰自己迅涮,他們只是感情好废赞,可當(dāng)我...
    茶點故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著逗柴,像睡著了一般蛹头。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天渣蜗,我揣著相機與錄音屠尊,去河邊找鬼。 笑死耕拷,一個胖子當(dāng)著我的面吹牛讼昆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播骚烧,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼浸赫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了赃绊?” 一聲冷哼從身側(cè)響起既峡,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎碧查,沒想到半個月后运敢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡忠售,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年传惠,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片稻扬。...
    茶點故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡卦方,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出泰佳,到底是詐尸還是另有隱情盼砍,我是刑警寧澤,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布乐纸,位于F島的核電站衬廷,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏汽绢。R本人自食惡果不足惜吗跋,卻給世界環(huán)境...
    茶點故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望宁昭。 院中可真熱鬧跌宛,春花似錦、人聲如沸积仗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽寂曹。三九已至哎迄,卻和暖如春回右,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背漱挚。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工翔烁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人旨涝。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓蹬屹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親白华。 傳聞我的和親對象是個殘疾皇子慨默,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,492評論 2 348

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