天天看點

iOS小技能:消息發送的步驟(利用類型編碼加快消息分發)

前言

運作時API的應用:

  1. 路由的實作(接口控制app跳任意界面)
  2. 擷取修改對象的成員屬性
  3. 動态添加/交換方法的實作
  4. 屬性關聯
  5. iOS 間接實作多繼承的方式:消息轉發、類别、delegate和protocol(委托協助主體完成操作任務,将需要定制化的操作預留給委托對象來自定義實作 與block類似) 。

I runtime的作用

因為objective-c是一門動态語言,也就是說隻有編譯器是不夠的,還需要一個運作時系統(runtime system)來執行編譯後的代碼。

runtime簡稱運作時,其中最主要的就是消息機制。

對于編譯期語言,會在編譯的時候決定調用哪個函數。對于OC的函數,是動态調用的,在編譯的時候并不能決定真正調用哪個函數,隻有在運作時才會根據函數的名稱找到對應的函數來調用。

1.1 消息發送的步驟

messages aren’t bound to method implementations until Runtime。(消息直到運作時才會與方法實作進行綁定)

objc_msgSend 是當方法的實作被調用後才會傳回資料。
iOS小技能:消息發送的步驟(利用類型編碼加快消息分發)
  1. 當向someObject發送消息時,先在本類中的方法緩存清單中進行查找
  2. 如果找不到就在本類中的法清單中進行查找
  3. 如果沒找到,就去父類中進行查找。
  4. 如果沒找到,runtime system并不會立即報錯使程式崩潰,而是依次執行消息轉發。
iOS小技能:消息發送的步驟(利用類型編碼加快消息分發)

檢查是否有動态添加對應的方法->檢查是否有其他對象實作了對應的方法(快速轉發給其他對象處理)->(标準消息轉發)

  1. 動态方法解析 :​

    ​向目前類發送 resolveInstanceMethod: 信号,檢查是否動态向該類添加了方法。​

  2. 快速消息轉發: 擷取轉發對象,​

    ​檢查該類是否實作了 forwardingTargetForSelector: 方法,若實作了則調用這個方法​

    ​。若該方法傳回值對象非nil或非self,那麼就進行消息的正常轉發。
  3. 标準消息轉發:擷取方法簽名,​

    ​runtime發送methodSignatureForSelector:消息擷取Selector對應的方法簽名​

    ​​。傳回值非空則通過​

    ​forwardInvocation:​

    ​​轉發消息,傳回值為空則向目前對象發送​

    ​doesNotRecognizeSelector:​

    ​消息,程式崩潰退出。

1.2 Runtime API

  1. 通過對 Runtime 庫函數的直接調用
runtime源碼:​​github.com/opensource-…​​
​​opensource.apple.com/source/objc…​​
iOS小技能:消息發送的步驟(利用類型編碼加快消息分發)
其中主要使用的函數定義在message.h和runtime.h這兩個檔案中。
  1. 通過 Foundation 架構的 NSObject 類定義的方法

Cocoa 程式中絕大部分類都是 NSObject 類的子類,是以都繼承了 NSObject 的行為。(NSProxy 類是個例外,它是個抽象超類)

  • class方法傳回對象的類;
  • isKindOfClass: 和 -isMemberOfClass: 方法檢查對象是否存在于指定的類的繼承體系中(是否是其子類或者父類或者目前類的成員變量);
  • respondsToSelector: 檢查對象能否響應指定的消息;
  • ​conformsToProtocol:​

    ​檢查對象是否實作了指定協定類的方法;
  • ​methodForSelector: ​

    ​傳回指定方法實作的位址。

1.3 了解instance、class object、metaclass的關系

iOS小技能:消息發送的步驟(利用類型編碼加快消息分發)

一個執行個體對象​

​struct objc_object​

​​的isa指針指向它的​

​struct objc_class​

​​類對象,類對象的isa指針指向它的元類;​

​super_class​

​​指針指向了父類的​

​類對象​

​​,而​

​元類​

​​的​

​super_class​

​​指針指向了父類的​

​元類​

​。

II 消息轉發

2.1 消息處理(動态地添加一個方法實作)

當在相應的類以及父類中找不到類方法實作時會執行​

​+resolveInstanceMethod:​

​這。該方法如果在類中不被重寫的話,預設傳回NO。如果傳回NO就表明不做任何處理,走下一步。

如果傳回YES的話,就說明在該方法中對這個找不到實作的方法進行了處理。

在​

​+resolveInstanceMethod:​

​方法中,我們可以為找不到實作的SEL動态地添加一個方法實作,添加完畢後,就會執行我們添加的方法實作。這樣,當一個類調用不存在的方法時,就不會崩潰了。具體做法如下所示:

//運作時方法攔截
- (void)dynamicAddMethod: (NSString *) value {
    NSLog(@"OC替換的方法:%@", value);
}

/**
 沒有找到SEL的IMP實作時會執行下方的方法

 @param sel 目前對象調用并且找不到IMP的SEL
 @return 找到其他的執行方法,并傳回yes
 */
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return NO;    //當傳回NO時,會接着執行forwordingTargetForSelector:方法,
    [KNRuntimeKit addMethod:[self class] method:sel method:@selector(dynamicAddMethod:)];//動态地添加一個方法實作
    return YES;
}      

2.2 消息快速轉發

如果不對上述消息進行處理的話,也就是+resolveInstanceMethod:傳回NO時,會走下一步消息轉發,即​

​-forwardingTargetForSelector:​

​。

該方法會傳回一個類的對象,這個類的對象有SEL對應的實作。當調用這個找不到的方法時,就會被轉發到SecondClass中去進行處理。

/**
 将目前對象不存在的SEL傳給其他存在該SEL的對象

 @param aSelector 目前類中不存在的SEL
 @return 存在該SEL的對象
 */
- (id)forwardingTargetForSelector:(SEL)aSelector {
    return self;//當該方法傳回self或者nil, 說明不對相應的方法進行轉發,那麼就進行消息的正常轉發。 

    return [SecondClass new];   //讓SecondClass中相應的SEL去執行該方法      

例子: 教師執行手術

//Returns the object to which unrecognized messages should first be directed.
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    Doctor *doctor = [[Doctor alloc]init];
    if ([doctor respondsToSelector:aSelector]) {
        return doctor;
    }
    return nil;
}      

這個方式的好處是隐藏了我要轉發的消息,沒有類目那麼清晰。

2.3 消息正常轉發

如果不将消息轉發給其他類的對象,那麼就隻能自己進行處理了。

如果上述方法傳回nil或者self的話,會執行​

​-methodSignatureForSelector:​

​方法來擷取方法的參數以及傳回資料類型(方法簽名)。

傳回值非空則通過forwardInvocation:轉發消息,傳回值為空則向目前對象發送 ​

​doesNotRecognizeSelector:​

​消息,程式崩潰,報出找不到相應的方法實作的崩潰資訊。

//在+resolveInstanceMethod:傳回NO時就會執行下方的方法,下方也是将該方法轉發給SecondClass。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
    //查找父類的方法簽名
    NSMethodSignature *signature = [super methodSignatureForSelector:selector];
    if(signature == nil) {
       // signature = [NSMethodSignature signatureWithObjCTypes:"@@:"];
                signature = [someObj methodSignatureForSelector:aSelector];  //傳回指定方法簽名


    }
    return signature;
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    SecondClass * forwardClass = [SecondClass new];
    SEL sel = invocation.selector;
    if ([forwardClass respondsToSelector:sel]) {
        [invocation invokeWithTarget:forwardClass];
    } else {
        [self      

2.4 兩種消息轉發方式的比較

  1. 快速消息轉發:簡單、快速、但僅能轉發給一個對象。
  2. 标準消息轉發:稍複雜、較慢、但轉發操作實作可控,可以實作多對象轉發

III 編譯器指令 @encode()

蘋果的 Objective-C 運作時庫内部利用類型編碼幫助加快消息分發。

​​ocrtTypeEncodings​​

NSValue 的​

​ +valueWithBytes:objCType:​

​​第二個參數需要用​

​@encode()​

​指令來建立一種内部表示的字元串 。

@encode(int) → i


+ (NSValue *)valueWithBytes:(const void *)value objCType:(const char *)type;
+ (NSValue *)value:(const void *)value withObjCType:(const char      

3.1 Objective-C type encodings

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)
void)testtypeEncodings{


    NSLog(@"int        : %s", @encode(int));
    NSLog(@"float      : %s", @encode(float));
    NSLog(@"float *    : %s", @encode(float*));//指針的标準編碼是加一個前置的 ^
    NSLog(@"char       : %s", @encode(char));
    NSLog(@"char *     : %s", @encode(char *));//char * 擁有自己的編碼 *,因為 C 的字元串被認為是一個實體,而不是指針。
    NSLog(@"BOOL       : %s", @encode(BOOL));//BOOL 是 c,而不是i。原因是 char 比 int 小。BOOL 更确切地說是 signed char 。
    NSLog(@"void       : %s", @encode(void));
    NSLog(@"void *     : %s", @encode(void *));
    NSLog(@"NSObject * : %s", @encode(NSObject *));
    NSLog(@"NSObject   : %s", @encode(NSObject));
    NSLog(@"[NSObject] : %s", @encode(typeof([NSObject class])));
    NSLog(@"NSError ** : %s", @encode(typeof(NSError **)));

    int intArray[5] = {1, 2, 3, 4, 5};
    NSLog(@"int[]      : %s", @encode(typeof(intArray)));

    float floatArray[3] = {0.1f, 0.2f, 0.3f};
    NSLog(@"float[]    : %s", @encode(typeof(floatArray)));

    typedef struct _struct {
        short a;
        long long b;
        unsigned long long c;
    } Struct;
    NSLog(@"struct     : %s", @encode(typeof(Struct)));

}      

結果:

2022-06-14 11:18:06.069425+0800 SDKSample[9300:3648699] int        : i
2022-06-14 11:18:06.069517+0800 SDKSample[9300:3648699] float      : f
2022-06-14 11:18:06.069559+0800 SDKSample[9300:3648699] float *    : ^f
2022-06-14 11:18:06.069597+0800 SDKSample[9300:3648699] char       : c
2022-06-14 11:18:06.069634+0800 SDKSample[9300:3648699] char *     : *
2022-06-14 11:18:06.069669+0800 SDKSample[9300:3648699] BOOL       : B
2022-06-14 11:18:06.069706+0800 SDKSample[9300:3648699] void       : v
2022-06-14 11:18:06.070191+0800 SDKSample[9300:3648699] void *     : ^v
2022-06-14 11:18:06.070230+0800 SDKSample[9300:3648699] NSObject * : @
2022-06-14 11:18:06.070267+0800 SDKSample[9300:3648699] NSObject   : {NSObject=#}
2022-06-14 11:18:06.070303+0800 SDKSample[9300:3648699] [NSObject] : #
2022-06-14 11:18:06.070339+0800 SDKSample[9300:3648699] NSError ** : ^@
2022-06-14 11:18:06.070374+0800 SDKSample[9300:3648699] int[]      : [5i]
2022-06-14 11:18:06.070870+0800 SDKSample[9300:3648699] float[]    : [3f]
2022-06-14 11:18:06.072298+0800 SDKSample[9300:3648699] struct     : {_struct=sqQ}      

3.2 method encodings

Code Meaning
r const
n in
N inout
o out
O bycopy
R byref
V oneway

帶inout 的參數表明它在發消息時對象即可傳入又可傳出,将參數特别标注為 in 或 out,程式将避免一些來回的開銷。

增加一個 bycopy 修飾符以保證發送了一份完整的拷貝

IV 運作時API應用

  1. 路由的實作(接口控制app跳任意界面)
  2. 擷取修改對象的成員屬性
  3. 動态添加/交換方法的實作
  4. 屬性關聯
  5. iOS 間接實作多繼承的方式:消息轉發、類别、delegate和protocol(委托協助主體完成操作任務,将需要定制化的操作預留給委托對象來自定義實作 與block類似) 。
  6. NSClassFromString、NSSelectorFromString 使用例子
id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")];      
#import "KNRuntimeKit.h"

@implementation KNRuntimeKit



/**
 擷取類名

 @param class 相應類
 @return NSString:類名
 */
+ (NSString *)fetchClassName:(Class)class {
    const char *className = class_getName(class);
    return [NSString stringWithUTF8String:className];
}

/**
 擷取成員變量

 @param class Class
 @return NSArray
 */
+ (NSArray *)fetchIvarList:(Class)class {
    unsigned int count = 0;
    Ivar *ivarList = class_copyIvarList(class, &count);

    NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
    for (unsigned int i = 0; i < count; i++ ) {
        NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithCapacity:2];
        const char *ivarName = ivar_getName(ivarList[i]);
        const char *ivarType = ivar_getTypeEncoding(ivarList[i]);
        dic[@"type"] = [NSString stringWithUTF8String: ivarType];
        dic[@"ivarName"] = [NSString stringWithUTF8String: ivarName];

        [mutableList addObject:dic];
    }
    free(ivarList);
    return [NSArray arrayWithArray:mutableList];
}

/**
 擷取類的屬性清單, 包括私有和公有屬性,以及定義在延展中的屬性

 @param class Class
 @return 屬性清單數組
 */
+ (NSArray *)fetchPropertyList:(Class)class {
    unsigned int count = 0;
    objc_property_t *propertyList = class_copyPropertyList(class, &count);

    NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
    for (unsigned int i = 0; i < count; i++ ) {
        const char *propertyName = property_getName(propertyList[i]);
        [mutableList addObject:[NSString stringWithUTF8String: propertyName]];
    }
    free(propertyList);
    return [NSArray arrayWithArray:mutableList];
}


/**
 擷取類的執行個體方法清單:getter, setter, 對象方法等。但不能擷取類方法

 @param class <#class description#>
 @return <#return value description#>
 */
+ (NSArray *)fetchMethodList:(Class)class {
    unsigned int count = 0;
    Method *methodList = class_copyMethodList(class, &count);

    NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
    for (unsigned int i = 0; i < count; i++ ) {
        Method method = methodList[i];
        SEL methodName = method_getName(method);
        [mutableList addObject:NSStringFromSelector(methodName)];
    }
    free(methodList);
    return [NSArray arrayWithArray:mutableList];
}

/**
 擷取協定清單

 @param class <#class description#>
 @return <#return value description#>
 */
+ (NSArray *)fetchProtocolList:(Class)class {
    unsigned int count = 0;
    __unsafe_unretained Protocol **protocolList = class_copyProtocolList(class, &count);

    NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
    for (unsigned int i = 0; i < count; i++ ) {
        Protocol *protocol = protocolList[i];
        const char *protocolName = protocol_getName(protocol);
        [mutableList addObject:[NSString stringWithUTF8String: protocolName]];
    }

    return [NSArray arrayWithArray:mutableList];
    return nil;
}


/**
 往類上添加新的方法與其實作

 @param class 相應的類
 @param methodSel 方法的名
 @param methodSelImpl 對應方法實作的方法名
 */
+ (void)addMethod:(Class)class method:(SEL)methodSel method:(SEL)methodSelImpl {
    Method method = class_getInstanceMethod(class, methodSelImpl);
    IMP methodIMP = method_getImplementation(method);
    const char *types = method_getTypeEncoding(method);
    class_addMethod(class, methodSel, methodIMP, types);
}

/**
 方法交換

 @param class 交換方法所在的類
 @param method1 方法1
 @param method2 方法2
 */
+ (void)methodSwap:(Class)class firstMethod:(SEL)method1 secondMethod:(SEL)method2 {
    Method firstMethod = class_getInstanceMethod(class, method1);
    Method secondMethod = class_getInstanceMethod(class, method2);
    method_exchangeImplementations(firstMethod, secondMethod);
}
@end      

see also