底层原理(六)------Runtime

2021-05-26  本文已影响0人  SwordDevil

一、引入

二、OC的方法调用:消息机制,给方法调用者发送消息

三、objc_msgSend的执行流程可以分为3大阶段

消息发送
动态方法解析
消息转发

注:objc_msgSend如果找不到合适的方法调用,会报错 unrecognized selector send to instance

objc_msgSend执行流程-源码阅读
objc_msgSend源码汇编解析
ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame
    MESSENGER_START
    // x0寄存器:消息接受者,receiver
    cmp    x0, #0            // nil check and tagged pointer check
    // b是跳转 判断消息接收者是否为nil  是的话 跳转到LNilOrTagged
    b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
    ldr    x13, [x0]        // x13 = isa
    and    x16, x13, #ISA_MASK    // x16 = class
LGetIsaDone:
    CacheLookup NORMAL        // calls imp or objc_msgSend_uncached

LNilOrTagged:
    b.eq    LReturnZero        // nil check

    // tagged
    mov    x10, #0xf000000000000000
    cmp    x0, x10
    b.hs    LExtTag
    adrp    x10, _objc_debug_taggedpointer_classes@PAGE
    add    x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
    ubfx    x11, x0, #60, #4
    ldr    x16, [x10, x11, LSL #3]
    b    LGetIsaDone

LExtTag:
    // ext tagged
    adrp    x10, _objc_debug_taggedpointer_ext_classes@PAGE
    add    x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
    ubfx    x11, x0, #52, #8
    ldr    x16, [x10, x11, LSL #3]
    b    LGetIsaDone
    
LReturnZero:
    // x0 is already zero
    mov    x1, #0
    movi    d0, #0
    movi    d1, #0
    movi    d2, #0
    movi    d3, #0
    MESSENGER_END_NIL
    ret // 相当于return

    END_ENTRY _objc_msgSend

注:b在汇编中是跳转的含义
hit是命中的含义
汇编语言中__class_lookupMethodAndLoadCache3这种写法在C语言中变成少一个下划线,也就是_class_lookupMethodAndLoadCache3

在class_rw_t里面查找method_array_t的method_t方法 如果是排好序的通过二分法查找,如果不是 则通过遍历循环查找


objc_msgSend执行流程01-消息发送

1、cache中查找:也就是从struct bucket_t *_buckets中查找;也就是@selector(方法名)&_mask(散列表的长度 - 1 mask_t会默认开辟一个空间,如果超出空间大小,就会重新开辟新空间,新空间=旧空间 * 2),这里面涉及到散列表(哈希表)查找,通过key生成索引,如果生成的索引一样,则自动-1。查找的时候,如果索引为0,则继续通过_mask开始查找也就是数组的最后一位开始
SEL作为key,IMP作为value
2、class_rw_t中查找方法列表:也就是在method_lists_t二维数组中查找
(class_ro_t是一维数组,只存储类的基本数据,而method_array_t methods中的二维数组会把分类信息也存储到methods中,先编译的分类会存储到数组的前边)

objc_msgSend执行流程02-动态方法解析

动态方法解析,又会重新走消息发送流程进行查找

动态方法解析方法一
动态方法解析方法二
动态方法解析方法三(c语言写法)
动态方法解析方法四(如果是+类方法)
动态方法总结

注:如果动态方法解析没有添加方法实现,也会进入消息转发,但是最后会程序报错
@dynamic是告诉编译器不用自动生成getter和setter的实现,等到运行时再添加方法实现

objc_msgSend执行流程03-消息转发

代码剖析

#import <objc/runtime.h>
#import "MJCat.h"

/* 假如forwardingTargetForSelector返回值有值调用下边方法,不会走方法前面 */
// 消息转发+ - 两种方法写法
//+ (BOOL)resolveInstanceMethod:(SEL)sel
//{
//    class_addMethod(<#Class  _Nullable __unsafe_unretained cls#>, <#SEL  _Nonnull name#>, <#IMP  _Nonnull imp#>, <#const char * _Nullable types#>)
//}

//- (id)forwardingTargetForSelector:(SEL)aSelector
//{
//    if (aSelector == @selector(test)) {
//        // objc_msgSend([[MJCat alloc] init], aSelector)
//        return [[MJCat alloc] init];
//    }
//    return [super forwardingTargetForSelector:aSelector];
//}

/* 假如forwardingTargetForSelector返回nil 则需要走方法签名,如果方法签名也为nil,则报错找不到该方法:doesNotRecognizeSelector: */
// 方法签名:返回值类型、参数类型
//- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
//{
//    if (aSelector == @selector(test)) {
//        return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
//    }
//    return [super methodSignatureForSelector:aSelector];
//}

// NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
//    anInvocation.target 方法调用者
//    anInvocation.selector 方法名
//    [anInvocation getArgument:NULL atIndex:0]
//- (void)forwardInvocation:(NSInvocation *)anInvocation
//{
////    anInvocation.target = [[MJCat alloc] init];
////    [anInvocation invoke];
//
//    [anInvocation invokeWithTarget:[[MJCat alloc] init]];
//}

方法签名的应用

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test:)) {
//        return [NSMethodSignature signatureWithObjCTypes:"v20@0:8i16"];
        return [NSMethodSignature signatureWithObjCTypes:"i@:I"];
        // 另一种写法调用test方法
//        return [[[MJCat alloc] init] methodSignatureForSelector:aSelector];
    }
    return [super methodSignatureForSelector:aSelector];
}

// 可以任意处理
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    // 参数顺序:receiver、selector、other arguments
//    int age;
//    [anInvocation getArgument:&age atIndex:2];
//    NSLog(@"%d", age + 10);
    
    
    // anInvocation.target == [[MJCat alloc] init]
    // anInvocation.selector == test:
    // anInvocation的参数:15
    // [[[MJCat alloc] init] test:15]
    
    [anInvocation invokeWithTarget:[[MJCat alloc] init]];
    
    int ret;
    [anInvocation getReturnValue:&ret];
    
    NSLog(@"%d", ret);
}
类方法的消息转发

// 元类对象是一种特殊的类对象

+ (id)forwardingTargetForSelector:(SEL)aSelector
{
    // objc_msgSend([[MJCat alloc] init], @selector(test))
    // [[[MJCat alloc] init] test]
    if (aSelector == @selector(test)) return [[MJCat alloc] init];

    return [super forwardingTargetForSelector:aSelector];
}

//+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
//{
//    if (aSelector == @selector(test)) return [NSMethodSignature signatureWithObjCTypes:"v@:"];
//    
//    return [super methodSignatureForSelector:aSelector];
//}
//
//+ (void)forwardInvocation:(NSInvocation *)anInvocation
//{
//    NSLog(@"1123");
//}
消息转发机制的用途

1、可以收集bug



2、统一拦截(可以统一处理方法,或者统一处理找不到方法)


面试题

1、OC的消息机制


扩展--super的底层原理

1、[super run]的底层讲解:底层包含2个参数 第一个是消息接收者,第二个是消息接收者的父类,objc_super里面的[MJPerson class] 只是为了让self从MJPerson里面开始查钊



汇编转译以后


关于super的面试题

1、[self class],[self superclass],[super class],[super superclass]输出结果

不论是self calss 还是super class 最后查找到的都是NSObject里面的class

/*
 [super message]的底层实现
 1.消息接收者仍然是子类对象
 2.从父类开始查找方法的实现
 */

struct objc_super {
    __unsafe_unretained _Nonnull id receiver; // 消息接收者
    __unsafe_unretained _Nonnull Class super_class; // 消息接收者的父类
};

- (void)run
{
    // super调用的receiver仍然是MJStudent对象
    [super run];
    
    
//    struct objc_super arg = {self, [MJPerson class]};
//
//    objc_msgSendSuper(arg, @selector(run));
//
//
//    NSLog(@"MJStudet.......");
    
}

- (instancetype)init
{
    if (self = [super init]) {
        NSLog(@"[self class] = %@", [self class]); // MJStudent
        NSLog(@"[self superclass] = %@", [self superclass]); // MJPerson

        NSLog(@"--------------------------------");

        // objc_msgSendSuper({self, [MJPerson class]}, @selector(class));
        NSLog(@"[super class] = %@", [super class]); // MJStudent
        NSLog(@"[super superclass] = %@", [super superclass]); // MJPerson
    }
    return self;
}

class 和superclass的底层实现

// 通过objc_msgSend和objc_msgSendSuper去查找receiver接收者是谁
- (Class)class
{
    return object_getClass(self);
}

- (Class)superclass
{
    return class_getSuperclass(object_getClass(self));
}
isKindOfClass和isMemberOfClass

isKindOfClass 当前类是否属于对比类或者是对比类的superclass
isMemberOfClass当前类是否跟对比类是同一个类

isKindOfClass和isMemberOfClass底层实现

 @implementation NSObject

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}


+ (BOOL)isMemberOfClass:(Class)cls {
    return object_getClass((id)self) == cls;
}


+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
@end

上述图片答案
这种写法前面的NSObject 指向的都是元类对象
+号方法是拿左边的元类对象跟右边的类对象进行比较
-号方法是拿左边的类对象跟右边的类对象进行比较

        NSLog(@"%d", [NSObject isKindOfClass:[NSObject class]]); // 1
        NSLog(@"%d", [NSObject isMemberOfClass:[NSObject class]]); // 0
        NSLog(@"%d", [MJPerson isKindOfClass:[MJPerson class]]); // 0
        NSLog(@"%d", [MJPerson isMemberOfClass:[MJPerson class]]); // 0
面试题

可以运行,打印结果为



剖析:cls等价于实例对象的isa指针,在实例对象中isa指针存储在第一个位置也就是前8个字节,obj的第一个参数就是cls,所以cls跟isa指针类似 都是指向class对象


注:栈空间的地址是从高到低排列
obj进行查找实例对象里面存储 第一位是isa指针第二位才是_name参数,所以obj查找需要忽略8个字节进行查找,所以找到test对应的数值



为啥都没有的时候是viewcontroller,因为是[super viewdidload];


引申--查看源码的三种方式

1、转换为c++代码

在使用clang转换OC为C++代码时,可能会遇到以下问题
cannot create __weak reference in file using manual reference

解决方案:支持ARC、指定运行时系统版本,比如
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

2、运行时候转换汇编代码



3、直接转换成汇编代码(这里面会有详细的注释跟行号)


LLVM

注:LLVM:参考https://llvm.org/docs/LangRef.html
OC->中间代码->汇编、机器代码

Runtime API

一、Runtime API 01-类
二、Runtime API 02-成员变量
三、Runtime API 03-属性
四、Runtime API 04-方法


用法一:普通设置

    MJPerson *person = [[MJPerson alloc] init];
    [person run];
    
    // 设置isa指向的Class
    object_setClass(person, [MJCar class]);
    [person run];
    
    // 判断一个OC对象是否为Class
    NSLog(@"%d %d %d",
          object_isClass(person),
          object_isClass([MJPerson class]),
          object_isClass(object_getClass([MJPerson class]))
          );
    
    // 获取isa指向的Class
    NSLog(@"%p %p", object_getClass([MJPerson class]), [MJPerson class]);

用法二:创建类

void run(id self, SEL _cmd)
{
    NSLog(@"_____ %@ - %@", self, NSStringFromSelector(_cmd));
}

void testClass()
{
    // 注:成员变量必须在注册类之前添加,因为成员变量存储在class_ro_t里面是只读的
    //    属性、方法、协议可以在注册类之后添加,因为存储在class_rw_t里面
    // 创建类
    Class newClass = objc_allocateClassPair([NSObject class], "MJDog", 0);
    // 动态添加成员变量
    class_addIvar(newClass, "_age", 4, 1, @encode(int));
    class_addIvar(newClass, "_weight", 4, 1, @encode(int));
    // 添加方法
    class_addMethod(newClass, @selector(run), (IMP)run, "v@:");
    // 注册类
    objc_registerClassPair(newClass);
    
//    MJPerson *person = [[MJPerson alloc] init];
//    // 将person的isa指针指向全新的class
//    object_setClass(person, newClass);
//    [person run];
//
//    id dog = [[newClass alloc] init];
//    [dog setValue:@10 forKey:@"_age"];
//    [dog setValue:@20 forKey:@"_weight"];
//    [dog run];
//
//    NSLog(@"%@ %@", [dog valueForKey:@"_age"], [dog valueForKey:@"_weight"]);
    
    // 在不需要这个类时释放
    objc_disposeClassPair(newClass);
}

用法三:成员变量的处理

void testIvars()
{
    // 获取成员变量信息
//    Ivar ageIvar = class_getInstanceVariable([MJPerson class], "_age");
//    NSLog(@"%s %s", ivar_getName(ageIvar), ivar_getTypeEncoding(ageIvar));
    
    // 设置和获取成员变量的值
//    Ivar nameIvar = class_getInstanceVariable([MJPerson class], "_name");
//
//    MJPerson *person = [[MJPerson alloc] init];
//    object_setIvar(person, nameIvar, @"123");
//    object_setIvar(person, ageIvar, (__bridge id)(void *)10);
//    NSLog(@"%@ %d", person.name, person.age);
    
    // 成员变量的数量
    unsigned int count;
    Ivar *ivars = class_copyIvarList([MJPerson class], &count);
    for (int i = 0; i < count; i++) {
        // 取出i位置的成员变量
        Ivar ivar = ivars[I];
        NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
    }
    free(ivars); // 在runtime中涉及到copy操作 比如class_copyIvarList这种,必须结束以后释放
}

实战应用一:查看私有成员边量->UITextField里面更改placeholder的属性

- (void)viewDidLoad {
    [super viewDidLoad];
    
//    // 先查找UITextField里面的成员变量
//    unsigned int count;
//    Ivar *ivars = class_copyIvarList([UITextField class], &count);
//    for (int i = 0; i < count; i++) {
//        // 取出i位置的成员变量
//        Ivar ivar = ivars[I];
//        NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
//    }
//    free(ivars);
    
    self.textField.placeholder = @"请输入用户名";
    
    // 直接设置
    [self.textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
    
    // 直接取值操作
//    UILabel *placeholderLabel = [self.textField valueForKeyPath:@"_placeholderLabel"];
//    placeholderLabel.textColor = [UIColor redColor];
    
    // 系统的方法
//    NSMutableDictionary *attrs = [NSMutableDictionary dictionary];
//    attrs[NSForegroundColorAttributeName] = [UIColor redColor];
//    self.textField.attributedPlaceholder = [[NSMutableAttributedString alloc] initWithString:@"请输入用户名" attributes:attrs];
}

实战应用二:字典转模型

#import <Foundation/Foundation.h>

@interface NSObject (Json)

+ (instancetype)mj_objectWithJson:(NSDictionary *)json;

@end

#import "NSObject+Json.h"
#import <objc/runtime.h>

@implementation NSObject (Json)

+ (instancetype)mj_objectWithJson:(NSDictionary *)json
{
    id obj = [[self alloc] init];
    
    unsigned int count;
    Ivar *ivars = class_copyIvarList(self, &count);
    for (int i = 0; i < count; i++) {
        // 取出i位置的成员变量
        Ivar ivar = ivars[I];
        NSMutableString *name = [NSMutableString stringWithUTF8String:ivar_getName(ivar)];
        [name deleteCharactersInRange:NSMakeRange(0, 1)];
        
        // 设值
        id value = json[name];
        if ([name isEqualToString:@"ID"]) {
            value = json[@"id"];
        }
        [obj setValue:value forKey:name];
    }
    free(ivars);
    
    return obj;
}

@end

// 快捷的就存储起来了(仿照MJExtension)
        // 字典转模型
        NSDictionary *json = @{
                               @"id" : @20,
                               @"age" : @20,
                               @"weight" : @60,
                               @"name" : @"Jack"
                               };
        
        MJPerson *person = [MJPerson mj_objectWithJson:json];

实战应用三:替换方法

#import <Foundation/Foundation.h>
#import "MJPerson.h"
#import <objc/runtime.h>


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MJPerson *person = [[MJPerson alloc] init];
        
        // 交换方法实现
        Method runMethod = class_getInstanceMethod([MJPerson class], @selector(run));
        Method testMethod = class_getInstanceMethod([MJPerson class], @selector(test));
        method_exchangeImplementations(runMethod, testMethod);

        [person run];
    }
    return 0;
}

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

void test()
{
    MJPerson *person = [[MJPerson alloc] init];
    
    // 替换方法
//    class_replaceMethod([MJPerson class], @selector(run), (IMP)myrun, "v");
    
    // block 作为返回值
    class_replaceMethod([MJPerson class], @selector(run), imp_implementationWithBlock(^{
        NSLog(@"123123");
    }), "v");
    
    [person run];
}

总结:什么是Runtime?平时项目中有用过吗?



例子:交换UIButton的点击事件

#import "UIControl+Extension.h"
#import <objc/runtime.h>

@implementation UIControl (Extension)

+ (void)load
{
    // hook:钩子函数
    Method method1 = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
    Method method2 = class_getInstanceMethod(self, @selector(mj_sendAction:to:forEvent:));
    method_exchangeImplementations(method1, method2);
}

- (void)mj_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
{
    NSLog(@"%@-%@-%@", self, target, NSStringFromSelector(action));
    
    // 调用系统原来的实现
    [self mj_sendAction:action to:target forEvent:event];
    
//    [target performSelector:action];
    
//    if ([self isKindOfClass:[UIButton class]]) {
//        // 拦截了所有按钮的事件
//
//    }
}

@end

NSMutableNSArray 添加nil值处理

#import "NSMutableArray+Extension.h"
#import <objc/runtime.h>

@implementation NSMutableArray (Extension)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 类簇:NSString、NSArray、NSDictionary,真实类型是其他类型
        Class cls = NSClassFromString(@"__NSArrayM");
        Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
        Method method2 = class_getInstanceMethod(cls, @selector(mj_insertObject:atIndex:));
        method_exchangeImplementations(method1, method2);
    });
}

- (void)mj_insertObject:(id)anObject atIndex:(NSUInteger)index
{
    if (anObject == nil) return;
    
    [self mj_insertObject:anObject atIndex:index];
}

@end

NSMutableDictionary也可以这么处理

#import "NSMutableDictionary+Extension.h"
#import <objc/runtime.h>

@implementation NSMutableDictionary (Extension)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class cls = NSClassFromString(@"__NSDictionaryM");
        Method method1 = class_getInstanceMethod(cls, @selector(setObject:forKeyedSubscript:));
        Method method2 = class_getInstanceMethod(cls, @selector(mj_setObject:forKeyedSubscript:));
        method_exchangeImplementations(method1, method2);
        
        Class cls2 = NSClassFromString(@"__NSDictionaryI");
        Method method3 = class_getInstanceMethod(cls2, @selector(objectForKeyedSubscript:));
        Method method4 = class_getInstanceMethod(cls2, @selector(mj_objectForKeyedSubscript:));
        method_exchangeImplementations(method3, method4);
    });
}

- (void)mj_setObject:(id)obj forKeyedSubscript:(id<NSCopying>)key
{
    if (!key) return;
    
    [self mj_setObject:obj forKeyedSubscript:key];
}

- (id)mj_objectForKeyedSubscript:(id)key
{
    if (!key) return nil;
    
    return [self mj_objectForKeyedSubscript:key];
}

@end
上一篇 下一篇

猜你喜欢

热点阅读