Runtime底層學(xué)習(xí)

Objective-C作為一門(mén)高級(jí)編程語(yǔ)言,想要成為可執(zhí)行文件需要先編譯成匯編語(yǔ)言,在匯編成機(jī)器語(yǔ)言,機(jī)器語(yǔ)言也是計(jì)算機(jī)能識(shí)別的唯一語(yǔ)言。但是Objective-C并不能直接編譯成匯編語(yǔ)言,需要先轉(zhuǎn)寫(xiě)為C語(yǔ)言在進(jìn)行編譯和匯編的操作。從Objective-C到C語(yǔ)言的過(guò)渡就是由Runtime來(lái)實(shí)現(xiàn)的。
Objective-C的語(yǔ)言特性是動(dòng)態(tài)性比較強(qiáng),這種動(dòng)態(tài)性就是由Runtime API來(lái)支撐的。想要了解Runtime的原理,首先需要知道OC語(yǔ)言的isa、Cache緩存class_rw_t。

isa詳解

要想學(xué)習(xí)Runtime,首先要了解它底層的一些常用的數(shù)據(jù)結(jié)構(gòu),比如isa指針。
在arm64架構(gòu)之前,isa就是一個(gè)普通的指針,存儲(chǔ)著Class、Meta-Class對(duì)象的內(nèi)存地址。
從arm64架構(gòu)開(kāi)始,對(duì)isa進(jìn)行了優(yōu)化,變成了一個(gè)共用體(union)結(jié)構(gòu),還使用位域來(lái)存儲(chǔ)更多的信息。共用體如下

union isa_t 
{
    Class cls;
    uintptr_t bits;
    struct {
        uintptr_t nonpointer        : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 19;
    };

我們來(lái)看一段代碼

#import <Foundation/Foundation.h>

@interface Person : NSObject
@property (nonatomic,  assign, getter=isTall) BOOL tall;
@property (nonatomic,  assign, getter=isRich) BOOL rich;
@property (nonatomic,  assign, getter=isHandsome) BOOL handsome;
@end

#import "Person.h"

@implementation Person

@end

#import <Foundation/Foundation.h>
#import "Person.h"
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        person.rich = NO;
        person.tall = YES;
        person.handsome = NO;
        NSLog(@"%zd",class_getInstanceSize([Person class]));
    }
    return 0;
}

在main.m函數(shù)中,系統(tǒng)給Person對(duì)象分配了16個(gè)字節(jié)的內(nèi)存,其中Person的isa指針占用8個(gè)字節(jié),三個(gè)屬性占用3個(gè)字節(jié)。
既然Person的屬性是BOOL類(lèi)型,可以考慮使用共用體。

共用體:共用體內(nèi)的成員變量共用一塊內(nèi)存。

Person類(lèi)的.m文件內(nèi),可以修改成如下:

#import "Person.h"

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

@implementation Person

- (void)setTall:(BOOL)tall{
    _tallRichHandsome.bits = tall;
}
- (BOOL)isTall{
    return !!(_tallRichHandsome.bits );
}

- (void)setRich:(BOOL)rich{
    _tallRichHandsome.bits = rich;
}
- (BOOL)isRich{
    return !!(_tallRichHandsome.bits);
}

- (void)setHandsome:(BOOL)handsome{
    _tallRichHandsome.bits = handsome;
}
- (BOOL)isHandsome{
    return !!( _tallRichHandsome.bits);
}
@end

在結(jié)構(gòu)體union中

       struct{
            char tall : 1;
            char rich : 1;
            char handsome  : 1;
        };

只是為了方便閱讀代碼,struct內(nèi)的成員變量,都存儲(chǔ)在unionchar中。所以回到開(kāi)頭的union isa_t中,struct是為了閱讀方便,共用體union中的信息都存儲(chǔ)在uintptr_t bits;中。

Class的結(jié)構(gòu)

objc_class的結(jié)構(gòu)
  • class_rw_t里面的methods、properties、protocols是二維數(shù)組,是可讀可寫(xiě)的,包含了類(lèi)的初始內(nèi)容、分類(lèi)的內(nèi)容。
    class_rw_t方法結(jié)構(gòu)圖

    method_array_t是一個(gè)二維數(shù)組,每一個(gè)元素是一個(gè)分類(lèi)或者類(lèi)的方法數(shù)組method_list_t。在method_list_t數(shù)組中包含的才是分類(lèi)或者類(lèi)的方法信息。
  • class_ro_t里面的baseMethodListbaseProtocols、ivars、baseProperties是一維數(shù)組,是只讀的,包含了類(lèi)的初始內(nèi)容。
    class_ro_t結(jié)構(gòu)圖
  • method_t是對(duì)方法\函數(shù)的封裝
    method_t
  • IMP代表函數(shù)的具體實(shí)現(xiàn)
    IMP
  • SEL代表方法\函數(shù)名,一般叫做選擇器。底層結(jié)構(gòu)跟char *類(lèi)似
    可以通過(guò)@selector()sel_registerName()獲得。

方法緩存

Class內(nèi)部結(jié)構(gòu)中有個(gè)方法緩存(cache_t),用散列表來(lái)緩存曾經(jīng)調(diào)用的方法,可以提高方法的查找速度。

cache_t

buckets是散列表,是數(shù)組。
bucket_t

  • 散列表的存儲(chǔ)方式
    散列表的存儲(chǔ)方式

    在上圖中,當(dāng)Person類(lèi)中有方法- (void)personTest;
  • 首先,通過(guò)@selector(personTest)&_Mask,得出一個(gè)值,比如得到2,則將這個(gè)方法personTest存儲(chǔ)到下表為2的數(shù)組中。一般存儲(chǔ)的位置從高到低。如果當(dāng)前被存儲(chǔ)內(nèi)容,這得到數(shù)值減1,往下存儲(chǔ)。
  • 當(dāng)調(diào)用這個(gè)方法時(shí),通過(guò)@selector(personTest)&_Mask,得到同樣的值,然后直接找到這個(gè)方法的地址值,直接調(diào)用。如果取值時(shí),當(dāng)前的cache_key_t不是所需要的,這得到數(shù)值減1,往下取值。
  • 缺點(diǎn):用空間換時(shí)間。

objc_msgSend

先來(lái)看段代碼

#import <Foundation/Foundation.h>

@interface Person : NSObject
- (void)personTest;
@end

#import "Person.h"

@implementation Person
- (void)personTest{

}
@end

#import <Foundation/Foundation.h>
#import "Person.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
       [person personTest];
    }
    return 0;
}

當(dāng)調(diào)用- (void)personTest;方法時(shí),是在底層轉(zhuǎn)化為C語(yǔ)言調(diào)用

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("personTest"));
    }
    return 0;
}
  • OC中的方法調(diào)用,其實(shí)都是轉(zhuǎn)化為objc_msgSend函數(shù)的調(diào)用,給receiver(方法接收者)發(fā)送了一條消息(selector方法名);
  • objc_msgSend的執(zhí)行流程可以分為3大階段:

?? 消息發(fā)送

  • 1、判斷消息接收者receiver是否為nil。如果是nil,這直接退出;
  • 2、如果receiver不為nil,receiver通過(guò)isa指針找到receiverClass(接收者類(lèi)對(duì)象)。從receiverClasscache中查找方法。如果找到方法,調(diào)用方法,結(jié)束查找;
  • 3、如果沒(méi)有找到方法,則從reveiverClassclass_rw_t中查找方法。如果找到了方法,調(diào)用方法,結(jié)束查找,并將方法緩存到receiverClasscache中;
  • 4、如果沒(méi)找到方法,從superClass的cache中查找方法,如果找到方法,調(diào)用方法,結(jié)束查找,并將方法緩存到receiverClasscache中;
  • 5、如果沒(méi)有找到方法,從superClass的class_rw_t中查找方法。如果找到方法,調(diào)用方法,結(jié)束查找,并將方法緩存到receiverClasscache中;
  • 6、如果沒(méi)找到方法,則判斷是否還有superclass。如果有,則執(zhí)行第4步。如果沒(méi)找到方法,則動(dòng)態(tài)方法解析。

??動(dòng)態(tài)方法解析

  • 是否曾經(jīng)存在過(guò)動(dòng)態(tài)方法解析,如果是,就進(jìn)入消息轉(zhuǎn)發(fā)階段。
  • 如果沒(méi)有動(dòng)態(tài)方法解析過(guò),則調(diào)用+ (BOOL)resolveInstanceMethod:(SEL)sel方法(實(shí)例方法調(diào)用)或者+(BOOL)resolveClassMethod:(SEL)sel(類(lèi)方法調(diào)用)判斷是否有動(dòng)態(tài)綁定方法,并標(biāo)記為已經(jīng)動(dòng)態(tài)方法解析,然后進(jìn)入消息發(fā)送階段。

??消息轉(zhuǎn)發(fā)

  • 通過(guò)動(dòng)態(tài)方法解析不成功,則調(diào)用- (id)forwardingTargetForSelector:(SEL)aSelector方法,把這條消息轉(zhuǎn)給其他接收者來(lái)處理,如果有其他接收者來(lái)處理,返回不為nil,則調(diào)用objc_msgSend(返回值,SEL);
  • 如果返回值為nil,則調(diào)用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector方法,生成方法簽名,然后系統(tǒng)用這個(gè)方法簽名生成NSInvocation對(duì)象,如果這個(gè)方法返回的nil,則崩潰報(bào)錯(cuò)unrecognized selector sent to instance;
  • 如果- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector方法返回值不為nil,則調(diào)用- (void)forwardInvocation:(NSInvocation *)anInvocation方法。

注意:
1、如果調(diào)用的是類(lèi)方法,上述流程中,方法換成類(lèi)方法(+開(kāi)頭)。
2、dynamic告訴編譯器不要自動(dòng)生成setter方法和getter方法的實(shí)現(xiàn),等到運(yùn)行時(shí)在添加方法的實(shí)現(xiàn)。
3、@synthesize age = _age;是為age屬性生成_age,并且自動(dòng)生成setter方法和getter方法,并賦值。

有關(guān)super

[super message]的底層實(shí)現(xiàn)

objc_msgSendSuper(self,[Person class],@selector(message));

[super class]返回的是當(dāng)前類(lèi)

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

[super superClass]返回的是父類(lèi)

- (Class)superclass{
     return class_getSuperclass(object_getClass(self));
}
  • 1、消息接收者仍然是子類(lèi)對(duì)象;
  • 2、從父類(lèi)開(kāi)始查找方法的實(shí)現(xiàn);

Runtime的應(yīng)用API

類(lèi)相關(guān)
  • 1、動(dòng)態(tài)創(chuàng)建一個(gè)類(lèi)(參數(shù):父類(lèi),類(lèi)名,額外的內(nèi)存空間)創(chuàng)建之后要注冊(cè)這個(gè)類(lèi)
    Class objc_allocateClassPair(Class superClass,const char *name,size_t extraBytes)
  • 2、注冊(cè)一個(gè)類(lèi)(要在類(lèi)注冊(cè)之前添加成員變量)
    Void objc_registerClassPair(Class cls)
  • 3、銷(xiāo)毀一個(gè)類(lèi)
    Void objc_disposeClassPair(Class cls)
  • 4、獲取isa指向的Class,或者類(lèi)對(duì)象(元類(lèi)對(duì)象)
    Class object_getClass(id obj)
  • 5、設(shè)置isa指向的class,此isa指向其他的類(lèi)
    Class object_setClass(id obj,class cls)
  • 6、判斷一個(gè)OC對(duì)象是否為class,傳入實(shí)例對(duì)象,BOOL為0,傳入類(lèi)對(duì)象,BOOL為1
    BOOL object_isClass(id obj)
  • 7、判斷一個(gè)Class是否為元素
    BOOL class_isMetaClass(Class cls)
  • 8、獲取父類(lèi)
    Class class_getSuperclass(class cls)
成員變量相關(guān)
  • 9、獲取成員變量信息(獲取不到值)
    Ivar class_getInstanceVariable(Class cls,const char *name)
  • 10、拷貝實(shí)例變量列表(最后需要調(diào)用free釋放)
    Ivar *class_copyIvarList(Class cls,unsigned int *outCount)
  • 11、設(shè)置和獲取成員變量的值
    Void object_setIvar(id obj,Ivar ivar,id value)
    Id object_getIvar(id obj,Ivar ivar)
  • 12、動(dòng)態(tài)添加成員變量
    BOOL class_addIvar(Class cls,const char *name,size_t size,unit8_t alignment,
    const char *types)
  • 13、獲取成員變量的相關(guān)信息
    const char *ivar_getName(Ivar v)
    const char *ivar_getTypeEncoding(Ivar v)
屬性相關(guān)
  • 14、獲取一個(gè)屬性
  • 15、拷貝屬性列表(最后需要調(diào)用free釋放)
    objc_property_t *class_copyPropertyList(Class cls,unsigned int *outCount)
  • 16、動(dòng)態(tài)添加屬性
    BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t
    *attributes, unsigned int attributeCount)
  • 17、動(dòng)態(tài)替換屬性
    void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t
    *attributes,unsigned int attributeCount)
  • 18、獲取屬性的一些信息
    const char *property_getName(objc_property_t property)
    const char *property_getAttributes(objc_property_t property)
方法相關(guān)
  • 19、獲得一個(gè)實(shí)例方法、類(lèi)方法
    Method class_getInstanceMethod(Class cls, SEL name)
    Method class_getClassMethod(Class cls, SEL name)
  • 20、方法實(shí)現(xiàn)相關(guān)操作
    IMP class_getMethodImplementation(Class cls, SEL name)
    IMP method_setImplementation(Method m, IMP imp)
    void method_exchangeImplementations(Method m1, Method m2)
  • 21、拷貝方法列表(最后需要調(diào)用free釋放)
    Method *class_copyMethodList(Class cls, unsigned int *outCount)
  • 22、動(dòng)態(tài)添加方法
    BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
  • 23、動(dòng)態(tài)替換方法
    IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
  • 24、獲取方法的相關(guān)信息(帶有copy的需要調(diào)用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)
  • 25、選擇器相關(guān)
    const char *sel_getName(SEL sel)
    SEL sel_registerName(const char *str)
  • 26、用block作為方法實(shí)現(xiàn)
    IMP imp_implementationWithBlock(id block)
    id imp_getBlock(IMP anImp)
    BOOL imp_removeBlock(IMP anImp)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容