Runtime 相关知识笔记

Runtime 相关知识笔记

  • isa详解
struct objc_object {
private:
    isa_t isa;
}
union isa_t  // arm64 架构
{
    Class cls;
    uintptr_t bits;
    struct {
        uintptr_t nonpointer        : 1;    // 0,代表普通的指针,存储着Class、Meta-Class对象的内存地址1,代表优化过,使用位域存储更多的信息
        uintptr_t has_assoc         : 1;    // 是否有过关联对象
        uintptr_t has_cxx_dtor      : 1;    // 是否有C++析构函数
        uintptr_t shiftcls          : 33;   // 存储Class、Meta-Class的内存地址信息
        uintptr_t magic             : 6;    // 用于在调试时分辨对象是否未完成初始化
        uintptr_t weakly_referenced : 1;    // 是否有弱引用
        uintptr_t deallocating      : 1;    // 对象是否正在释放
        uintptr_t has_sidetable_rc  : 1;    // 如果为1,那么引用计数会存储在一个叫SideTable的类的属性中
        uintptr_t extra_rc          : 19;   // 里面存储的值是引用计数减1
    };
}
  • Class的结构内容分析

class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容
struct class_rw_t {
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
}

class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容
struct class_ro_t {
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    property_list_t *baseProperties;
};

method_t是对方法\函数的封装
struct method_t {
    SEL name;   // 函数名
    const char *types;  // 编码(返回值类型、参数类型)
    IMP imp;    // 指向函数的指针(函数地址)
};

IMP代表函数的具体实现
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
typedef struct objc_selector *SEL;

SEL代表方法\函数名,一般叫做选择器,底层结构跟char *类似
1. 可以通过@selector()和sel_registerName()获得
2. 可以通过sel_getName()和NSStringFromSelector()转成字符串
3. 不同类中相同名字的方法,所对应的方法选择器是相同的

Class底层结构图
在这里插入图片描述

Type Encoding

CodeMeaning
cA char
iAn int
sA short
lA long
qA long long
CAn unsigned char
IAn unsigned int
SAn unsigned short
LAn unsigned long
QAn unsigned long long
fA float
dA double
BA C++ bool or a C99 _Bool
vA void
*A character string (char *)
@An object
#A class object(Class)
:A method selector (SEL)
  • 方法缓存

Class内部结构中有个方法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度。

struct cache_t {
    struct bucket_t *_buckets; // 散列表
    mask_t _mask;   // 散列表长度 - 1
    mask_t _occupied;   // 已经缓存的方法数量
}

struct bucket_t {
private:
    cache_key_t _key; // SEL作为key
    IMP _imp;   // 函数的内存地址
}

// 缓存查找的源码
objc-cache.mm
bucket_t * cache_t::find(cache_key_t k, id receiver)
{
    assert(k != 0);

    bucket_t *b = buckets();
    mask_t m = mask();
    mask_t begin = cache_hash(k, m);
    mask_t i = begin;
    do {
        if (b[i].key() == 0  ||  b[i].key() == k) {
            return &b[i];
        }
    } while ((i = cache_next(i, m)) != begin);

    // hack
    Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
    cache_t::bad_cache(receiver, (SEL)k, cls);
}
static inline mask_t cache_hash(cache_key_t key, mask_t mask) 
{
    return (mask_t)(key & mask);
}

static inline mask_t cache_next(mask_t i, mask_t mask) {
    return i ? i-1 : mask;
}
  • objc_msgSend的执行流程

OC中的方法调用,其实都是转换为objc_msgSend函数的调用
objc_msgSend的执行流程可以分为3大阶段:
消息发送、动态方法解析、消息转发

objc_msgSend执行流程 – 源码跟读
objc-msg-arm64.s
1. ENTRY _objc_msgSend
2. b.le	LNilOrTagged
3. CacheLookup NORMAL
4. .macro CacheLookup
5. .macro CheckMiss
6. STATIC_ENTRY __objc_msgSend_uncached
7. .macro MethodTableLookup
8. __class_lookupMethodAndLoadCache3

objc-runtime-new.mm
1. _class_lookupMethodAndLoadCache3
2. lookUpImpOrForward
3. getMethodNoSuper_nolock、search_method_list、log_and_fill_cache
4. cache_getImp、log_and_fill_cache、getMethodNoSuper_nolock、log_and_fill_cache
5. _class_resolveInstanceMethod
6. _objc_msgForward_impcache

objc-msg-arm64.s
1. STATIC_ENTRY __objc_msgForward_impcache
2. ENTRY __objc_msgForward

Core Foundation
__forwarding__(不开源)

阅读流程:objc-msg-arm64.s
1、首先在objc-msg-arm64.s找到ENTRY _objc_msgSend 方法,比较cmp x0, #0,如果x0为空,则return(x0是消息接收者)	
2、然后查看CacheLookup NORMAL的实现找到.macro CheckMiss,然后调用__objc_msgSend_uncached,最后查看MethodTableLookup的实现,发现调用__class_lookupMethodAndLoadCache3方法,该方法的实现在objc-runtime-new.mm中

objc-runtime-new.mm代码流程:
1、首先查看lookUpImpOrForward(cls, sel, obj, YES/*initialize*/, NO/*cache*/, YES/*resolver*/)方法的实现
2、其次调用cache_getImp在方法缓存中查找方法,如果没有在方法列表中查找->getMethodNoSuper_nolock获取方法列表->search_method_list->findMethodInSortedMethodList查找方法-> 如果找到方法写入缓存log_and_fill_cache
3、如果没有找到方法就在super的方法缓存查找方法(Class curClass = cls->superclass, cache_getImp),如果找到该方法写入子类方法缓存中(log_and_fill_cache),如果没有找到则在父类方法列表中查找(getMethodNoSuper_nolock),如果找到则写入子类方法缓存中
4、如果没有找到方法的实现则会调用_class_resolveMethod(实际上是查看对象是否实现了resolveInstanceMethod),如果实现动态解析,则会触发以上的方法查找流程
5、如果没有则会调用_objc_msgForward_impcache(实际上是看对象是否实现forwardingTargetForSelector方法),触发消息转发流程


// 伪代码
int __forwarding__(void *frameStackPointer, int isStret) {
    id receiver = *(id *)frameStackPointer;
    SEL sel = *(SEL *)(frameStackPointer + 8);
    const char *selName = sel_getName(sel);
    Class receiverClass = object_getClass(receiver);
    
    // 调用 forwardingTargetForSelector:
    if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
        id forwardingTarget = [receiver forwardingTargetForSelector:sel];
        if (forwardingTarget && forwardingTarget != receiver) {
            if (isStret == 1) {
                int ret;
                objc_msgSend_stret(&ret,forwardingTarget, sel, ...);
                return ret;
            }
            return objc_msgSend(forwardingTarget, sel, ...);
        }
    }
    
    // 僵尸对象
    const char *className = class_getName(receiverClass);
    const char *zombiePrefix = "_NSZombie_";
    size_t prefixLen = strlen(zombiePrefix); // 0xa
    if (strncmp(className, zombiePrefix, prefixLen) == 0) {
        CFLog(kCFLogLevelError,
              @"*** -[%s %s]: message sent to deallocated instance %p",
              className + prefixLen,
              selName,
              receiver);
        <breakpoint-interrupt>
    }
    
    // 调用 methodSignatureForSelector 获取方法签名后再调用 forwardInvocation
    if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
        NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
        if (methodSignature) {
            BOOL signatureIsStret = [methodSignature _frameDescriptor]->returnArgInfo.flags.isStruct;
            if (signatureIsStret != isStret) {
                CFLog(kCFLogLevelWarning ,
                      @"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'.  Signature thinks it does%s return a struct, and compiler thinks it does%s.",
                      selName,
                      signatureIsStret ? "" : not,
                      isStret ? "" : not);
            }
            if (class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
                NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];
    
                [receiver forwardInvocation:invocation];
    
                void *returnValue = NULL;
                [invocation getReturnValue:&value];
                return returnValue;
            } else {
                CFLog(kCFLogLevelWarning ,
                      @"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message",
                      receiver,
                      className);
                return 0;
            }
        }
    }
    
    SEL *registeredSel = sel_getUid(selName);
    
    // selector 是否已经在 Runtime 注册过
    if (sel != registeredSel) {
        CFLog(kCFLogLevelWarning ,
              @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort",
              sel,
              selName,
              registeredSel);
    } // doesNotRecognizeSelector
    else if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
        [receiver doesNotRecognizeSelector:sel];
    }
    else {
        CFLog(kCFLogLevelWarning ,
              @"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort",
              receiver,
              className);
    }
    // The point of no return.
    kill(getpid(), 9);
}

objc_msgSend消息发送流程:
1、首先从消息接收对象(receiver)的cache方法列表中查找,如果没有找到
2、再从该对象中的方法列表(class_rw_t)中查找,如果找到缓存到当前对象的cache方法列表中,如果没有找到
3、再从接收对象的父类中的cache方法列表中查找,如果找到,将方法缓存到对象的cache列表中,如果没有找到
4、再从该接收对象的父类的方法列表中查找,如果找到就缓存到该对象的cache方法列表中,如果没有找到
5、触发方法解析流程

如果是从class_rw_t中查找方法
已经排序的,二分查找
没有排序的,遍历查找

receiver通过isa指针找到receiverClass
receiverClass通过superclass指针找到superClass

objc_msgSend的动态方法解析流程:
1、判断该方法是否动态解析,如果解析就出发消息转发,如果没有
2、再调用resolveInstanceMethod: 或者resolveClassMethod:方法来动态解析
3、标记为已经动态解析然后重新走消息发送流程

objc_msgSend的消息转发流程:
1、调用forwardingTargetForSelector,如果返回值不为空,则触发消息发送流程,如果返回值为空
2、则调用methodSignatureForSelector,如果返回nil,则触发doesNotRecognizeSelector异常,如果返回值不为nil
3、则调用forwardInvocation方法(方法中自定义任何逻辑)

消息转发流程
在这里插入图片描述

void testIMP (void){
    NSLog(@"testIMP invoke");
}

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(test)) {
        NSLog(@"resolveInstanceMethod:");
        // 第一种。利用Runtime动态添加方法实现,返回yes,
        //class_addMethod([self class], @selector(test), (IMP)testIMP, "v@:");
        //return YES;
        
        // 第二种返回NO
        return NO;
    }
    return [super resolveInstanceMethod:sel];
}

// 查看有没有其他对象处理该消息
- (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(test)) {
        NSLog(@"forwardingTargetForSelector:");
        // 返回给其他实现了 test 方法的对象处理(在.h中没有定义test方法也可以)
        // Other * other = [[Other alloc] init];
        // return other;

        return nil;
    }
    return [super forwardingTargetForSelector:aSelector];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(test)) {
        NSLog(@"methodSignatureForSelector:");
        // 返回方法签名 + 实现forwardInvocation方法处理该消息
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"forwardInvocation:");
}
  • super的本质

super调用,底层会转换为objc_msgSendSuper2函数的调用,接收2个参数
struct objc_super2、SEL

[super test];
转换成c++的编译代码: 
objc_msgSendSuper({self,class_getSuperclass(objc_getClass("SubTestNSObject"))}, sel_registerName("test"));
    
struct objc_super {
    id receiver;    // 消息接受者
    Class super_class;
};

NSLog(@"self class] = %@", [self class]);
NSLog(@"super class] = %@", [super class]);
NSLog(@"self superclass] = %@", [self superclass]);
NSLog(@"super superclass] = %@", [super superclass]);

class和superclass的底层实现
+ (Class)class {
    return self;
}
- (Class)class {
    return object_getClass(self);
}
+ (Class)superclass {
    return self->superclass;
}
- (Class)superclass {
    return [self class]->superclass;
}

根据以上分析super的消息接受者还是当前类的实例对象,只不过super查找方法是从父类开始寻找的,所以[super class]返回的是当前实例对象的类对象

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

isMemberOfClass和isKindOfClass的区别:

源码实现
- (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;
}

isMemberOfClass是判断self和传进来的类是否相同
isKindOfClass是判断self的类对象是否是传进来的类或子类
  • 讲一下 OC 的消息机制

OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)
objc_msgSend底层有3大阶段
消息发送(当前类、父类中查找)、动态方法解析、消息转发

  • 什么是Runtime?平时项目中有用过么?

OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行
OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数
平时编写的OC代码,底层都是转换成了Runtime API进行调用

具体应用
利用关联对象(AssociatedObject)给分类添加属性
遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
交换方法实现(交换系统的方法)
利用消息转发机制解决方法找不到的异常问题

  • Runtime常用API
类:
动态创建一个类(参数:父类,类名,额外的内存空间)
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
注册一个类(要在类注册之前添加成员变量)
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)

成员变量:
获取一个实例变量信息
Ivar class_getInstanceVariable(Class cls, const char *name)
拷贝实例变量列表(最后需要调用free释放)
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
设置和获取成员变量的值
void object_setIvar(id obj, Ivar ivar, id value)
id object_getIvar(id obj, Ivar ivar)
动态添加成员变量(已经注册的类是不能动态添加成员变量的)
BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)
获取成员变量的相关信息
const char *ivar_getName(Ivar v)
const char *ivar_getTypeEncoding(Ivar v)

属性:
获取一个属性
objc_property_t class_getProperty(Class cls, const char *name)
拷贝属性列表(最后需要调用free释放)
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
动态添加属性
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
                  unsigned int attributeCount)
动态替换属性
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
                      unsigned int attributeCount)
获取属性的一些信息
const char *property_getName(objc_property_t property)
const char *property_getAttributes(objc_property_t property)

方法:
获得一个实例方法、类方法
Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls, SEL name)

方法实现相关操作
IMP class_getMethodImplementation(Class cls, SEL name) 
IMP method_setImplementation(Method m, IMP imp)
void method_exchangeImplementations(Method m1, Method m2) 

拷贝方法列表(最后需要调用free释放)
Method *class_copyMethodList(Class cls, unsigned int *outCount)

动态添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

动态替换方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)

获取方法的相关信息(带有copy的需要调用free去释放)
SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char *method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m)
char *method_copyReturnType(Method m)
char *method_copyArgumentType(Method m, unsigned int index)

选择器相关
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str)

用block作为方法实现
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值