底层五:Runtime

389 阅读10分钟

OC 的消息机制

OC 的消息机制 : OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)

Objective-C是一门动态性比较强的编程语言,跟C、C++等语言有着很大的不同

Runtime API提供的接口基本都是C语言的,源码由C\C++\汇编语言编写

isa详解

共用体(union)

定义:所有参数占用同一块内存

struct {
	int year 
	int month
	int day 
}
// 其中year 、 month、day分别占用自己的内存,每个占用4个字节

union {
	int year 
	int month
	int day 
}
// 其中year 、 month、day是同一块内存,一共占用4个字节

定义一个共用体,并使用

union {
        char bits; // 一个字节
        // 结构体仅仅是增加了可读性,完全可以删掉,结构体也只占一位,位置完全有Mask决定
        struct {
            char tall : 1; // 只占一位,在最右边
            char rich : 1; // 只占一位
            char handsome : 1; // 只占一位
            char thin : 1; // 只占一位, 在最左边
            // 0000 thin / handsome / rich / tall
        };
    } _tallRichHandsome;

// bits 和 结构体,都是一个字节,使用同样的一块一字节内容,相互影响,但是因为只是用bits,所以struct只是为了可读性,没有作用


// `1 << 0` 表示`1左移0位` 即 `0b 0000 0001` 
// `mask`表示掩码,执行`按位与`操作
#define MJTallMask (1<<0)
#define MJRichMask (1<<1)
#define MJHandsomeMask (1<<2)
#define MJThinMask (1<<3)
// .h
#import <Foundation/Foundation.h>

@interface MJPerson : NSObject

- (void)setTall:(BOOL)tall;
- (void)setRich:(BOOL)rich;
- (void)setHandsome:(BOOL)handsome;
- (void)setThin:(BOOL)thin;

- (BOOL)isTall;
- (BOOL)isRich;
- (BOOL)isHandsome;
- (BOOL)isThin;

@end
// .m
#import "MJPerson.h"
// `mask`表示掩码 
#define MJTallMask (1<<0)
#define MJRichMask (1<<1)
#define MJHandsomeMask (1<<2)
#define MJThinMask (1<<3)

@interface MJPerson()
{
    union {
        char bits;
        
        struct {
            char tall : 1;
            char rich : 1;
            char handsome : 1;
            char thin : 1;
        };
    } _tallRichHandsome;
}
@end

@implementation MJPerson

- (void)setTall:(BOOL)tall
{
    if (tall) {
        _tallRichHandsome.bits |= MJTallMask;
    } else {
    // 按位取反
        _tallRichHandsome.bits &= ~MJTallMask;
    }
}

- (BOOL)isTall
{
    // 两个"!",将类型强转为BOOL类型
    return !!(_tallRichHandsome.bits & MJTallMask);
}

- (void)setRich:(BOOL)rich
{
    if (rich) {
        _tallRichHandsome.bits |= MJRichMask;
    } else {
        _tallRichHandsome.bits &= ~MJRichMask;
    }
}

- (BOOL)isRich
{
    // 两个"!",将类型强转为BOOL类型
    return !!(_tallRichHandsome.bits & MJRichMask);
}

- (void)setHandsome:(BOOL)handsome
{
    if (handsome) {
        _tallRichHandsome.bits |= MJHandsomeMask;
    } else {
        _tallRichHandsome.bits &= ~MJHandsomeMask;
    }
}

- (BOOL)isHandsome
{
    // 两个"!",将类型强转为BOOL类型
    return !!(_tallRichHandsome.bits & MJHandsomeMask);
}



- (void)setThin:(BOOL)thin
{
    if (thin) {
        _tallRichHandsome.bits |= MJThinMask;
    } else {
        _tallRichHandsome.bits &= ~MJThinMask;
    }
}

- (BOOL)isThin
{
    // 两个"!",将类型强转为BOOL类型
    return !!(_tallRichHandsome.bits & MJThinMask);
}
@end

isa本质

arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址

arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域(二进制位)来存储更多的信息

截屏2021-03-18 上午10.33.55.png

根据共用体知识,这里的内容全部存储在uintptr_t bits中,结构体只是为了更方便阅读,没有任何作用(可以删掉),共用体后面的数字代表占了多少位

屏幕快照 2018-07-18 23.19.09.png

// ARM64 simulators have a larger address space, so use the ARM64e
// scheme even when simulators build for ARM64-not-e.

# if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR(模拟器)
#   define ISA_MASK        0x007ffffffffffff8ULL
# else
#   define ISA_MASK        0x0000000ffffffff8ULL

可以根据ISA_MASK,推断出要取出共用体中的33位,用来存储类对象、元类对象的地址

nonpointer :

0 代表普通的指针,存储着Class、Meta-Class对象的内存地址 1代表优化过,使用位域存储更多的信息


has_assoc(has_associatedObject) 是否有设置过关联对象,如果没有,释放时会更快


has_cxx_dtor 是否有C++析构函数(.cxx_destruct),如果没有,释放时会更快


shiftcls 存储着Class、Meta-Class对象内存地址信息


magic 用于在调试时分辨对象是否未完成初始化


weakly_referenced 是否有被弱引用指向过,如果没有,释放时会更快


deallocating 对象是否正在释放


extra_rc(extra_retainCount) 里面存储的值是引用计数器的数值减去1(如果引用计数器存储的是10,这里存的就是9)


has_sidetable_rc 引用计数器是否过大无法存储在isa中 如果为1,那么引用计数会存储在一个叫SideTable的类的属性中

释放代码

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    // 可以看到是有判断的,没有释放更快
    if (fastpath(isa.nonpointer                     &&
                 !isa.weakly_referenced             &&
                 !isa.has_assoc                     &&
#if ISA_HAS_CXX_DTOR_BIT
                 !isa.has_cxx_dtor                  &&
#else
                 !isa.getClass(false)->hasCxxDtor() &&
#endif
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}

Class的结构(类对象和元类对象的结构体)

成员变量属性的定义

@interfacePerson :NSObject
{
    //定义成员变量
    int_age;  
}
// 属性
@property(nonatomic,strong) NSString  *name;

Class 的结构

截屏2021-05-06 下午4.55.23.png

class_rw_t的结构

截屏2021-03-18 上午10.35.14.png

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
}

class_ro_t的结构

截屏2021-05-06 下午4.58.09.png

  • class_rw_t里面的methodspropertiesprotocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容。class_ro_t的内容会被放到方法实现类数组methods的栈底,分类的方法会加载到栈顶的位置

  • class_ro_t里面的baseMethodListbaseProtocolsivarsbaseProperties是一维数组,是只读的,包含了类的初始内容

method_t是对方法\函数的封装

截屏2021-03-18 上午10.36.21.png

IMP代表函数的具体实现

截屏2021-03-18 上午10.36.47.png

SEL代表方法名(就是个名字),一般叫做选择器,底层结构跟char *类似

截屏2021-05-06 下午4.59.08.png

  • 可以通过@selector()sel_registerName()获得
  • 可以通过sel_getName()NSStringFromSelector()转成字符串
  • 不同类中相同名字的方法,所对应的方法选择器是相同的

type包含了函数返回值、参数编码的字符串(iOS中提供了一个叫做@encode的指令,可以将具体的类型表示成字符串编码)

截屏2021-03-18 上午10.37.38.png

方法缓存

[person test]相当于给person发一个test消息,person去对应的类对象method_array_t* methods 去遍历查找方法,但是因为methods是一个二维数据,如果没找到还要通过superClass指针去父类里面找,遍历查找的效率还是比较底的

Class内部结构中有个方法缓存cache_t,用散列表(哈希表)<底层是个动态数组>来缓存曾经调用过的方法,可以提高方法的查找速度

截屏2021-03-18 上午10.38.03.png

  • 散列表 使用函数名(SEL)mask_t _mask(散列表长度减一) 按位的方式生成key,如果key相同了,直接将key数值减一,存到散列表中,取出过程中是先去取数据,如果发现取出的数据跟当前的key不一致,也要减一,如果减到零,就变成_mask(散列表最大值),继续减一

  • 达到快速查找方法的实现。以空间换取时间

  • 一旦动态数组扩容,会清空里面的数据

  • 父类的方法也会缓存到当前的类对象中

截屏2021-03-18 上午10.38.30.png

objc_msgSend执行流程

OC中的方法调用(消息机制),其实都是转换为objc_msgSend函数的调用, 就是给方法调用者(reciver)发送消息

objc_messageSend 三大阶段

1.消息发送

2.动态方法解析

3.消息转发

objc_msgSend执行流程 – 源码跟读

截屏2021-03-18 上午10.39.21.png

objc_msgSend执行流程01-消息发送

截屏2021-05-06 下午5.12.05.png

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

截屏2021-03-18 上午10.40.00.png

动态添加方法

截屏2021-03-18 上午10.40.31.png

+ (BOOL)resolveClassMethod:(SEL)sel {
    if (sel == @selector(test)) {
        // 在元类方法列表里面存放的类方法,如果找不到,会转向最后的父类查找对象方法
        // Method  method = class_getInstanceMethod(self, @selector(other));
        
        Method  method = class_getClassMethod(self, @selector(other));
        
        class_addMethod(object_getClass(self),
                        sel,
                        method_getImplementation(method),
                        method_getTypeEncoding(method));
        // 从源码可以看出, 这里返回YES或者NO都可以
        return YES;
    }
    return [super resolveClassMethod:sel];
}

从源码可以看出,在调用完动态方法解析后,会调用goto函数,再次进行消息发送,因为已经在类中添加了方法,所以可以在类对象(或元类对象中找到方法)

  class_addMethod(object_getClass(self),
                        sel,
                        method_getImplementation(method),
                        method_getTypeEncoding(method));

objc_msgSend的执行流程03-消息转发

当前类无法处理消息,就将消息转发给其他类进行处理

截屏2021-05-06 下午5.13.27.png

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

// 方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test:name:)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    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]];
    NSLog(@"%@",NSStringFromSelector(anInvocation.selector));
}

/// 有返回值的处理
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test:)) {
//        return [NSMethodSignature signatureWithObjCTypes:"v20@0:8i16"];
        return [NSMethodSignature signatureWithObjCTypes:"i@:i"];
//        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);
}

super的本质

#import "Student.h"

@implementation Student

- (instancetype)init
{
    self = [super init];
    if (self) {
        NSLog(@"[self class] = %@", [self class]); // Student
        NSLog(@"[self superclass] = %@", [self superclass]); // Person
        
        NSLog(@"--------------------------------");
        
        // objc_msgSendSuper({self, [MJPerson class]}, @selector(class));
        NSLog(@"[super class] = %@", [super class]); // Student
        NSLog(@"[super superclass] = %@", [super superclass]); // Person
    }
    return self;
}
@end
  1. Student中的方法 [super run] 转换成CPP代码,来探究super的本质
// 转换前
- (void)run
{
    [super run];   
}

// 转换后
    objc_msgSendSuper (
                        (__rw_objc_super){
                                self,
                            class_getSuperclass(objc_getClass("Student"))
                        }, @selector(run)
                    );

2.将转换后的代码进行抽取、查找得出如下代码

struct __rw_objc_super arg = {
    self,
    class_getSuperclass(objc_getClass("Student")) // 根据函数可以看出是Peron
}
objc_msgSendSuper(arg, @selector(run))

3.查找 objc_super 的源码

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

4.根据上面的代码和源码可以得出[super run ]转换后的代码

objc_msgSendSuper({
                     self,
                     person
                  }, 
                  @selector(run));

objc_msgSendSuper({self,person}, @selector(run));

5.查看objc_msgSendSuper的源码,在message.h中,找到如下的注释

// 从父类的方法列表里开始查找方法实现
the superclass at which to start searching for the method implementation.

结论super方法,只是从父类对象开始找出方法实现,但是receiver还是当前类

[super message]的底层实现

  • 1.消息接收者仍然是子类对象
  • 2.从父类开始查找方法的实现

回到开始的问题:[super class] 的打印值,就牵扯到class的方法实现,在NSObject.mm 中查看 classsuperclass的源码

+ (Class)class {
    return self;
}

- (Class)class {
    return object_getClass(self);
}

+ (Class)superclass {
    return self->getSuperclass();
}

- (Class)superclass {
    return [self class]->getSuperclass();
}

结论[super class] 打印出来的是recever,也就是student

isKindOfClass 和 isMemberOfClass

NSObject.mm 中找到源码

// 判断cls对象,是否是self的元类
+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

// 判断cls是否是[self class]
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

// 判断cls对象,是否是self的元类、或者self的元类的父类
+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}

// 判断cls是否是[self class],或者[self class]的父类
- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}

runtime基本函数

API

动态创建一个类(参数:父类,类名,额外的内存空间)

Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)

注册一个类(要在类注册之前添加成员变量),成员变量在class_ro_t结构体中是只读的,没法在创建完类后动态添加成员变量

void objc_registerClassPair(Class cls) 

销毁一个类

void objc_disposeClassPair(Class cls)

获取isa指向的Class

Class object_getClass(id obj)

设置isa指向的Class

Class object_setClass(id obj, Class cls)

判断一个OC对象是否为Class

BOOL object_isClass(id obj)

判断一个Class是否为元类

BOOL class_isMetaClass(Class cls)

获取父类

Class class_getSuperclass(Class cls)
成员变量

截屏2021-03-22 下午2.28.18.png

Ivar *class_copyIvarList(Class cls, unsigned int *outCount) 常用,使用最后要调用free释放

属性

截屏2021-05-06 下午5.16.18.png

方法

截屏2021-05-06 下午5.17.45.png

截屏2021-05-06 下午5.17.02.png

  1. 查看类的属性、方法、成员变量、协议
  2. 字典转模型
// .h
@interface NSObject (Json)
+ (instancetype)mj_objectWithJson:(NSDictionary *)json;
@end

// .m
#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

  1. 替换方法实现

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

@implementation UIControl (Extension)
// 交换按钮点击事件,会调用sendAction:to:forEvent:方法,交换方法实现
+ (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];
//    if ([self isKindOfClass:[UIButton class]]) {
//        // 拦截了所有按钮的事件
//
//    }
}
@end

查看 method_exchangeImplementations 的源码

void method_exchangeImplementations(Method m1, Method m2)
{
    if (!m1  ||  !m2) return;

    mutex_locker_t lock(runtimeLock);

    IMP imp1 = m1->imp(false);
    IMP imp2 = m2->imp(false);
    SEL sel1 = m1->name();
    SEL sel2 = m2->name();
     // 交换两个method的imp
    m1->setImp(imp2);
    m2->setImp(imp1);

    // 冲洗缓存,这个方法会清空缓存
    flushCaches(nil, __func__, [sel1, sel2, imp1, imp2](Class c){
        return c->cache.shouldFlush(sel1, imp1) || c->cache.shouldFlush(sel2, imp2);
    });

    adjustCustomFlagsForMethodChange(nil, m1);
    adjustCustomFlagsForMethodChange(nil, m2);
}

通过源码可以看出method_exchangeImplementations本质是交换两个method中的imp

截屏2021-03-22 下午4.22.58.png

object_getClass: 本质是获取isa指向的class

object_setClass: 设置isa指向的class

 Person *person = [[Person alloc] init];
 [person run];
 object_setClass(person, [Car class]);
 [person run];
 
 // 打印结果:可以发现对象的isa指针指向的类对象,被替换掉了
 -[JXPerson run]
 -[Car run]

交换方法实现

设置动态数组,添加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,真实类型是其他类型
        //类簇是Foundation框架中广泛使用的设计模式。类簇将一些私有的、具体的子类组合在一个公共的、抽象的超类下面,以这种方法来组织类可以简化一个面向对象框架的公开架构,而又不减少功能的丰富性。
        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

设置字典的key为nil的情况

#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