天天看点

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