Type Encodings 鸣戴、NSMethodSignature 、NSInvocation三部曲

前言

一般來說粘拾,寫東西都是由上而下頂層聯(lián)想窄锅,由于這次是知識(shí)總結(jié),準(zhǔn)備從淺入深缰雇,一點(diǎn)一點(diǎn)寫下去入偷。文章段落之間好想關(guān)系耦合度有點(diǎn)低,見諒寓涨。


Type Encodings

Type Encodings作為對(duì)Runtime的補(bǔ)充盯串,編譯器將每個(gè)方法的返回值參數(shù)類型編碼成一個(gè)C字符串,并將這個(gè)字符串和OC的selector進(jìn)行關(guān)聯(lián)戒良。我們可以用編譯器指令@encode來獲取這個(gè)C字符串体捏。當(dāng)你給一個(gè)指定的類型(這個(gè)類型可以是基本數(shù)據(jù)類型,如int糯崎,可以是一個(gè)指針几缭,一個(gè)結(jié)構(gòu)體,類名等沃呢,也就是可以作為sizeof()參數(shù)的任何類型),@encode會(huì)返回一個(gè)將這個(gè)類型編碼后的C字符串年栓。總結(jié)為一句話:用一個(gè)C字符串來表示一個(gè)數(shù)據(jù)類型

在Objective-C Runtime Programming Guide中的Type Encoding一節(jié)中薄霜,列出了Objective-C中所有的類型編碼某抓。需要注意的是這些類型很多是與我們用于存檔和分發(fā)的編碼類型是相同的纸兔。但有一些不能在存檔時(shí)使用。

注:Objective-C不支持long double類型否副。@encode(long double)返回d汉矿,與double是一樣的。

下面這個(gè)列表列出了一些類型碼及其代表的意義:

Code Meaning
c A char
i An int
s A short
l A long
l is treated as a 32-bit quantity on 64-bit programs.
q A long long
C An unsigned char
I An unsigned int
S An unsigned short
L An unsigned long
Q An unsigned long long
f A float
d A double
B A C++ bool or a C99 _Bool
v A void
* A character string (char *)
@ An object (whether statically typed or typed id)
# A class object (Class)
: A method selector (SEL)
[array type] An array
{name=type...} A structure
(name=type...) A union
bnum A bit field of num bits
^type A pointer to type
? An unknown type (among other things, this code is used for function pointers)

打印出來看看:

    NSLog(@"char --> %s",@encode(char));
    NSLog(@"int --> %s",@encode(int));
    NSLog(@"short --> %s",@encode(short));
    NSLog(@"long --> %s",@encode(long));
    NSLog(@"long long --> %s",@encode(long long));
    NSLog(@"unsigned char --> %s",@encode(unsigned char));
    NSLog(@"unsigned int --> %s",@encode(unsigned int));
    NSLog(@"unsigned short --> %s",@encode(unsigned short));
    NSLog(@"unsigned long --> %s",@encode(unsigned long long));
    NSLog(@"float --> %s",@encode(float));
    NSLog(@"bool --> %s",@encode(bool));
    NSLog(@"void --> %s",@encode(void));
    NSLog(@"char * --> %s",@encode(char *));
    NSLog(@"id --> %s",@encode(id));
    NSLog(@"Class --> %s",@encode(Class));
    NSLog(@"SEL --> %s",@encode(SEL));
    int array[] = {1,2,3};
    NSLog(@"int[] --> %s",@encode(typeof(array)));
    typedef struct person{
        char *name;
        int age;
    }Person;
    NSLog(@"struct --> %s",@encode(Person));
    
    typedef union union_type{
        char *name;
        int a;
    }Union;
    NSLog(@"union --> %s",@encode(Union));

    int a = 2;
    int *b = {&a};
    NSLog(@"int[] --> %s",@encode(typeof(b)));

打印結(jié)果:

2020-06-29 18:21:07.002557+0800 LibanayCollection[4116:440746] char --> c
2020-06-29 18:21:07.002640+0800 LibanayCollection[4116:440746] int --> i
2020-06-29 18:21:07.002702+0800 LibanayCollection[4116:440746] short --> s
2020-06-29 18:21:07.002762+0800 LibanayCollection[4116:440746] long --> q
2020-06-29 18:21:07.002828+0800 LibanayCollection[4116:440746] long long --> q
2020-06-29 18:21:07.002883+0800 LibanayCollection[4116:440746] unsigned char --> C
2020-06-29 18:21:07.002940+0800 LibanayCollection[4116:440746] unsigned int --> I
2020-06-29 18:21:07.002994+0800 LibanayCollection[4116:440746] unsigned short --> S
2020-06-29 18:21:07.003052+0800 LibanayCollection[4116:440746] unsigned long --> Q
2020-06-29 18:21:07.003115+0800 LibanayCollection[4116:440746] float --> f
2020-06-29 18:21:07.003177+0800 LibanayCollection[4116:440746] bool --> B
2020-06-29 18:21:07.003250+0800 LibanayCollection[4116:440746] void --> v
2020-06-29 18:21:07.003333+0800 LibanayCollection[4116:440746] char * --> *
2020-06-29 18:21:07.003455+0800 LibanayCollection[4116:440746] id --> @
2020-06-29 18:21:07.003600+0800 LibanayCollection[4116:440746] Class --> #
2020-06-29 18:21:07.003723+0800 LibanayCollection[4116:440746] SEL --> :
2020-06-29 18:21:07.003811+0800 LibanayCollection[4116:440746] int[] --> [3i]
2020-06-29 18:21:07.003903+0800 LibanayCollection[4116:440746] struct --> {person=*i}
2020-06-29 18:21:07.007271+0800 LibanayCollection[4116:440746] union --> (union_type=*i)
2020-06-29 18:21:07.007343+0800 LibanayCollection[4116:440746] int[] --> ^i

此時(shí)我們能想到最簡(jiǎn)單的使用备禀,類型判斷:

    NSNumber *num1 = [NSNumber numberWithInt:1];
    if (strcmp([num1 objCType], @encode(int)) == 0 ) {
        NSLog(@"--- num1 是int類型 ---");
    }
    
    NSNumber *num2 = [NSNumber numberWithFloat:1.0];
    if (strcmp([num2 objCType], @encode(float)) == 0 ) {
        NSLog(@"--- num2 是float類型 ---");
    }
    
    NSNumber *num3 = [NSNumber numberWithChar:'A'];
    if (strcmp([num3 objCType], @encode(char)) == 0 ) {
        NSLog(@"--- num3 是char類型 ---");
    }

結(jié)論:

2020-06-29 19:08:14.655418+0800 LibanayCollection[4269:463051] --- num1 是int類型 ---
2020-06-29 19:08:14.655496+0800 LibanayCollection[4269:463051] --- num2 是float類型 ---
2020-06-29 19:08:14.655569+0800 LibanayCollection[4269:463051] --- num3 是char類型 ---

當(dāng)然洲拇,它的主要用處我們也提到了,將返回值參數(shù)類型編碼成字符串曲尸,我們一點(diǎn)一點(diǎn)來看赋续。

NSMethodSignature

顧名思義, NSMethodSignature就是“方法簽名”,蘋果官方定義該類為對(duì)方法的參數(shù)類型另患、返回值類型進(jìn)行封裝 .

扒一下api:

#import <Foundation/NSObject.h>

NS_ASSUME_NONNULL_BEGIN

NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available")
@interface NSMethodSignature : NSObject

//初始化方法
+ (nullable NSMethodSignature *)signatureWithObjCTypes:(const char *)types;

//參數(shù)數(shù)量
@property (readonly) NSUInteger numberOfArguments;

//獲取參數(shù)類型
- (const char *)getArgumentTypeAtIndex:(NSUInteger)idx NS_RETURNS_INNER_POINTER;

@property (readonly) NSUInteger frameLength;

//是否是單向
- (BOOL)isOneway;

//返回值類型
@property (readonly) const char *methodReturnType NS_RETURNS_INNER_POINTER;

//返回長(zhǎng)度
@property (readonly) NSUInteger methodReturnLength;

@end

NS_ASSUME_NONNULL_END

我們可以清晰的查看到纽乱,只有一個(gè)初始化方法,+ (nullable NSMethodSignature *)signatureWithObjCTypes:(const char *)types;我們來看一下:

 NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"@@:*"];

看下里邊的@@:*昆箕,結(jié)合Type Encodings我們能看出第一個(gè)@是返回一個(gè)id類型迫淹。那么其它的東西是什么呢?

OC為支持消息的轉(zhuǎn)發(fā)和動(dòng)態(tài)調(diào)用,Objective-C Method 的 Type 信息以 “返回值 Type + 參數(shù) Types” 的形式組合編碼为严,還需要考慮到 self和 _cmd 這兩個(gè)隱含參數(shù):

即:@@:* 可以分解為 id(返回值) + self + _cmd + char*(參數(shù))

當(dāng)然,我們查看NSObject類的定義api肺稀,會(huì)驚喜的發(fā)現(xiàn)這兩個(gè)幾個(gè)方法:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");

+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");

所以我們可以在類中輕松的獲取簽名對(duì)象:

- (void)viewDidLoad {
    [super viewDidLoad];
    //獲取實(shí)例方法簽名
    NSMethodSignature * signature1 = [self methodSignatureForSelector:@selector(instanceLog)];
    NSMethodSignature * signature2 = [ViewController instanceMethodSignatureForSelector:@selector(instanceLog)];
    //獲取類方法簽名
    NSMethodSignature * signature3 = [ViewController methodSignatureForSelector:@selector(classLog)];
}

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

+ (void)classLog{
    NSLog(@"--classLog--");
}

NSInvocation

NSInvocation是一個(gè)消息調(diào)用類第股,主要作用是存儲(chǔ)和傳遞消息。它存儲(chǔ)的信息包含了一個(gè)iOS消息全部的成分:target话原、selector夕吻、參數(shù)、返回值繁仁、方法簽名涉馅。也就是說,NSInvocation可以將傳統(tǒng)的iOS消息發(fā)送這個(gè)過程轉(zhuǎn)換成一個(gè)對(duì)象黄虱,然后執(zhí)行這個(gè)對(duì)象的發(fā)送消息的方法就可以發(fā)送消息稚矿,NSInvocation對(duì)象包含的每一個(gè)組成部分能夠直接設(shè)定(如消息target,參數(shù)之類的)捻浦。

先上api:

@interface NSInvocation : NSObject
//初始化方法
+ (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig;
//方法簽名
@property (readonly, retain) NSMethodSignature *methodSignature;
//防止參數(shù)釋放掉
- (void)retainArguments;
@property (readonly) BOOL argumentsRetained;
//target
@property (nullable, assign) id target;
//SEL
@property SEL selector;
//獲取返回值
- (void)getReturnValue:(void *)retLoc;
//設(shè)置返回值
- (void)setReturnValue:(void *)retLoc;
//獲取參數(shù)
- (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
//設(shè)置參數(shù)
- (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
//執(zhí)行
- (void)invoke;
// target發(fā)送消息晤揣,即target執(zhí)行方法
- (void)invokeWithTarget:(id)target;
@end

初始化方法需要傳遞一個(gè)NSMethodSignature對(duì)象,我們?cè)谏衔闹幸呀?jīng)掌握了 NSMethodSignature朱灿,就不多做解釋了昧识,那么我們?nèi)绾问褂媚兀覀冮_始修改上邊的代碼:

- (void)viewDidLoad {
    [super viewDidLoad];
    // -- 獲取instanceLog的簽名
    NSMethodSignature * signature = [self methodSignatureForSelector:@selector(instanceLog)];
    // -- 根據(jù)方法簽名創(chuàng)建一個(gè)NSInvocation
    NSInvocation * invocation = [NSInvocation  invocationWithMethodSignature:signature];
    // -- 設(shè)置調(diào)用者
    [invocation setTarget:self];
    // -- 設(shè)在被調(diào)用的消息
    [invocation setSelector:@selector(instanceLog)];
    // --  執(zhí)行
    [invocation invoke];
}

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

執(zhí)行結(jié)果:

2020-06-30 15:48:55.647225+0800 LibanayCollection[6359:869723] ---instanceLog----

結(jié)論:
NSInvocation可以看做命令模式在iOS中的實(shí)現(xiàn)盗扒,即將目標(biāo)(target)跪楞、選擇器(selector)缀去、方法簽名(NSMethodSignature)、參數(shù)(argument)等信息打包在一起使用甸祭。

用于消息轉(zhuǎn)發(fā)

最初對(duì)于NSInvocation和NSMethodSignature的認(rèn)知缕碎,我是從消息轉(zhuǎn)發(fā)流程開始的,不知道大家是否是一樣的淋叶。不太了解的可以看我另一篇文章 iOS消息機(jī)制消息轉(zhuǎn)發(fā)詳解阎曹。

與performSelector:的聯(lián)系與區(qū)別

iOS中可以直接調(diào)用某個(gè)對(duì)象的消息方式有兩種:一種是performSelector:withObject;另一種就是NSInvocation, 關(guān)于performSelector能完成簡(jiǎn)單調(diào)用煞檩,我們無需多言处嫌。但是對(duì)于>2個(gè)的參數(shù)或者有返回值的處理,那performSelector:withObject就顯得有點(diǎn)有心無力了斟湃,那么在這種情況下熏迹,我們就可以使用NSInvocation來進(jìn)行這些相對(duì)復(fù)雜的操作。我們繼續(xù)修改上邊代碼:

- (void)viewDidLoad {
    [super viewDidLoad];
    // -- 獲取instanceLog的簽名
    NSMethodSignature * signature = [self methodSignatureForSelector:@selector(instanceLogStr1:andStr2:)];
    // -- 根據(jù)方法簽名創(chuàng)建一個(gè)NSInvocation
    NSInvocation * invocation = [NSInvocation  invocationWithMethodSignature:signature];
    // -- 設(shè)置調(diào)用者
    [invocation setTarget:self];
    // -- 設(shè)在被調(diào)用的消息
    [invocation setSelector:@selector(instanceLogStr1:andStr2:)];
    NSString * str1 = @"test1";
    NSString * str2 = @"test2";
    // -- 添加參數(shù)
    [invocation setArgument:&str1 atIndex:2];
    [invocation setArgument:&str2 atIndex:3];
    // -- 執(zhí)行
    [invocation invoke];
}

- (void)instanceLogStr1:(NSString *)str1 andStr2:(NSString *)str2{
    NSLog(@"---instanceLog || %@ || %@----",str1,str2);
}

執(zhí)行結(jié)果:

2020-06-30 16:32:27.277291+0800 LibanayCollection[6488:890762] ---instanceLog || test1 || test2----

這里要注意點(diǎn)是添加參數(shù)從Index2開始凝赛,這是因?yàn)檫€需要考慮到 self和 _cmd 這兩個(gè)隱含參數(shù)注暗。

結(jié)語(yǔ):

這篇寫的大部分都是概念性的東西,也找了很多文章來學(xué)習(xí)進(jìn)行歸納墓猎。寫好一篇文章不容易捆昏,本文最下方的參考文獻(xiàn)希望各位看官有機(jī)會(huì)也去讀一下,互相印證毙沾。

文獻(xiàn)參考

OC-類型編碼(TypeEncodings)
iOS 如何實(shí)現(xiàn)Aspect Oriented Programming
NSMethodSignature與NSInvocation使用
NSInvocation NSMethodSignature 全面解析
NSInvocation的基本用法

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末骗卜,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子左胞,更是在濱河造成了極大的恐慌寇仓,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烤宙,死亡現(xiàn)場(chǎng)離奇詭異遍烦,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)躺枕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門服猪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人屯远,你說我怎么就攤上這事蔓姚。” “怎么了慨丐?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵坡脐,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我房揭,道長(zhǎng)备闲,這世上最難降的妖魔是什么晌端? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮恬砂,結(jié)果婚禮上咧纠,老公的妹妹穿的比我還像新娘。我一直安慰自己泻骤,他們只是感情好漆羔,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著狱掂,像睡著了一般演痒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上趋惨,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天鸟顺,我揣著相機(jī)與錄音,去河邊找鬼器虾。 笑死讯嫂,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的兆沙。 我是一名探鬼主播欧芽,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼葛圃!你這毒婦竟也來了渐裸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤装悲,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后尚氛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體诀诊,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年阅嘶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了属瓣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡讯柔,死狀恐怖抡蛙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情魂迄,我是刑警寧澤粗截,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站捣炬,受9級(jí)特大地震影響熊昌,放射性物質(zhì)發(fā)生泄漏绽榛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一婿屹、第九天 我趴在偏房一處隱蔽的房頂上張望灭美。 院中可真熱鬧,春花似錦昂利、人聲如沸届腐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)犁苏。三九已至,卻和暖如春窝撵,著一層夾襖步出監(jiān)牢的瞬間傀顾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工碌奉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留短曾,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓赐劣,卻偏偏與公主長(zhǎng)得像嫉拐,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子魁兼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348