美文网首页
Runtime - isa相关

Runtime - isa相关

作者: 冰棍儿好烫嘴 | 来源:发表于2023-04-05 23:08 被阅读0次
    • Objective-C是一门动态性比较强的编程语言,跟C、C++等语言有着很大的不同
    • Objective-C的动态性是由Runtime API来支撑的
    • Runtime API提供的接口基本都是C语言的,源码由C/C++/汇编语言编写

    isa详解

    • 要想学习Runtime,首先要了解它底层的一些常用数据结构,比如isa指针
    • 在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址
    • 从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息

    &可以用来取出特定的位,想取哪一位,哪一位与1取,其他位与0取
    | 按位或,想让某一位设为1的话,找到某一位,按位或就可以了

    声明一个Person类,有Bool类型的三个属性,分别为:tall、rich、handsome,如果直接用@property声明三个属性,那么需要开辟16个字节空间,代码如下:

    Person.h文件
    #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
    
    main文件
    Person *person = [[Person alloc] init];
    person.tall = YES;
    person.rich = NO;
    person.handsome = YES;
    NSLog(@"%zd",class_getInstanceSize([Person class]));
    打印结果:16
    

    因为Bool类型都只有0或者1的结果,用1位就可以够用了,所以想实现把这三个属性用三个位存储,代码实现如下:

    Person.h文件
    #import <Foundation/Foundation.h>
    @interface Person : NSObject
    - (void)setTall:(BOOL)tall;
    - (void)setRich:(BOOL)rich;
    - (void)setHandsome:(BOOL)handsome;
    
    - (BOOL)isTall;
    - (BOOL)isRich;
    - (BOOL)isHandsome;
    @end
    
    Person.m文件
    #import "Person.h"
    
    //掩码:一般用来按位与(&)运算的,十位进制不方便看,用二位进制
    //#define TallMask 1
    //#define RichMask 2
    //#define HandsomeMask 4
    
    //#define TallMask 0b00000001
    //#define RichMask 0b00000010
    //#define HandsomeMask 0b00000100
    //或者
    #define TallMask (1<<0)
    #define RichMask (1<<1)
    #define HandsomeMask (1<<2)
    @interface Person ()
    {
        char _tallRichHandsome;//0b 0000 0011,最后一位tall,倒数第二位rich,倒数第三位handsome
    }
    @end
    @implementation Person
    //- (instancetype)init{
        //if (self = [super init]) {
           // _tallRichHandsome = 0b00000111;//0b00000011;
        //}
        //return self;
    //}
    - (void)setTall:(BOOL)tall{
        if (tall){
    //        _tallRichHandsome = _tallRichHandsome | TallMask;
            _tallRichHandsome |= TallMask;
        }else{
            _tallRichHandsome &= ~TallMask;//(按位取反是~)
        }
    }
    - (BOOL)isTall{
        return !!(_tallRichHandsome & TallMask);
    }
    - (void)setRich:(BOOL)rich{
        if (rich) {
            _tallRichHandsome |= RichMask;
        }else{
            _tallRichHandsome &= ~RichMask;
        }
    }
    - (BOOL) isRich{
        return !!(_tallRichHandsome & RichMask);
    }
    - (void)setHandsome:(BOOL)handsome{
        if (handsome) {
            _tallRichHandsome |= HandsomeMask;
        }else{
            _tallRichHandsome &= ~HandsomeMask;
        }
    }
    - (BOOL)isHandsome{
        return !!(_tallRichHandsome & HandsomeMask);
    }
    @end
    
    main文件
    Person *person = [[Person alloc] init];
    person.tall = YES;
    person.rich = NO;
    person.handsome = YES;
    NSLog(@"tall:%d rich:%d handsome:%d",person.isTall,person.isRich,person.isHandsome);
    

    还可以使用位域实现:

    Person.h文件
    #import <Foundation/Foundation.h>
    @interface Person : NSObject
    - (void)setTall:(BOOL)tall;
    - (void)setRich:(BOOL)rich;
    - (void)setHandsome:(BOOL)handsome;
    
    - (BOOL)isTall;
    - (BOOL)isRich;
    - (BOOL)isHandsome;
    @end
    
    Person.m文件
    #import "Person.h"
    
    #define TallMask (1<<0)
    #define RichMask (1<<1)
    #define HandsomeMask (1<<2)
    @interface Person ()
    {
    //    char _tallRichHandsome;//0b 0000 0011,最后一位tall,倒数第二位rich,倒数第三位handsome
        //位域
        struct {
            char tall : 1;
            char rich : 1;
            char handsome : 1;
        } _tallRichHandsome;//0b0000 0000,结构体里边,先写的变量会自动排到最后边,所以最后边是tall,倒数第二位是rich,倒数第三位是handsome
    }
    @end
    @implementation Person
    - (void)setTall:(BOOL)tall{
        _tallRichHandsome.tall = tall;
    }
    - (BOOL)isTall{
        return !!_tallRichHandsome.tall;
        //_tallRichHandsome.tall的结果是只有一个二进制位的0b1,但是函数的返回值是bool类型的,是需要一个字节,也就是八位的二进制比如0b0000 0000,那么将一个二进制位的强制转成8位的,就会出现问题,其他的位全用1去覆盖了,返回值就变成了-1,所以需要两次取反
    }
    - (void)setRich:(BOOL)rich{
        _tallRichHandsome.rich = rich;
    }
    - (BOOL) isRich{
        return !!_tallRichHandsome.rich;
    }
    - (void)setHandsome:(BOOL)handsome{
        _tallRichHandsome.handsome = handsome;
    }
    - (BOOL)isHandsome{
        return !!_tallRichHandsome.handsome;
    }
    @end
    
    main文件
    Person *person = [[Person alloc] init];
    person.tall = NO;
    person.rich = YES;
    person.handsome = NO;
    NSLog(@"tall:%d rich:%d handsome:%d",person.isTall,person.isRich,person.isHandsome);
    

    结构体和共用体的区别:
    结构体内各变量之间内存独立,共用体内各变量共用一块内存。
    结构体:

    struct Date{
        int year;
        int month;
        int day;
    };
    
    结构体内存

    共用体

    union Date{
        int year;
        int month;
        int day;
    };
    
    共用体内存
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            union Date date;
            date.year = 2011;
            date.month = 11;
            date.day = 10;
            NSLog(@"union : year = %d month = %d day = %d",date.year,date.month,date.day);
     打印结果:
     union : year = 10 month = 10 day = 10
    
            struct Date1 date1;
            date1.year = 2022;
            date1.month = 12;
            date1.day = 30;
            NSLog(@"struct : year = %d month = %d day = %d",date1.year,date1.month,date1.day);
    打印结果:
    struct : year = 2022 month = 12 day = 30
    }
        return 0;
    }
    

    如上,Person的属性赋值取值实现也用union写一下,

    Person.h文件
    #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;
    
    - (void)setTall:(BOOL)tall;
    - (void)setRich:(BOOL)rich;
    - (void)setHandsome:(BOOL)handsome;
    
    - (BOOL)isTall;
    - (BOOL)isRich;
    - (BOOL)isHandsome;
    @end
    
    Person.m文件
    #import "Person.h"
    #define TallMask (1<<0)
    #define RichMask (1<<1)
    #define HandsomeMask (1<<2)
    @interface Person ()
    {
        //位域
        union{
            char bits;
            struct {
                char tall : 1;
                char rich : 1;
                char handsome : 1;
            };//当前的struct只是增加可读性,没有什么实质性作用,因为去掉当前的struct整个的内容,也不影响代码运行效果
        } _tallRichHandsome;
    }
    @end
    @implementation Person
    
    - (void)setTall:(BOOL)tall{
        if (tall) {
            _tallRichHandsome.bits |= TallMask;
        }else{
            _tallRichHandsome.bits &= ~TallMask;
        }
    }
    - (BOOL)isTall{
        return  !!(_tallRichHandsome.bits & TallMask);
    }
    - (void)setRich:(BOOL)rich{
        if (rich) {
            _tallRichHandsome.bits |= RichMask;
        }else{
            _tallRichHandsome.bits &= ~RichMask;
        }
    }
    - (BOOL) isRich{
        return  !!(_tallRichHandsome.bits & RichMask);
    }
    - (void)setHandsome:(BOOL)handsome{
        if (handsome) {
            _tallRichHandsome.bits |= HandsomeMask;
        }else{
            _tallRichHandsome.bits &= ~HandsomeMask;
        }
    }
    - (BOOL)isHandsome{
        return  !!(_tallRichHandsome.bits & HandsomeMask);
    }
    @end
    
    main文件
    Person *person = [[Person alloc] init];
    person.tall = NO;
    person.rich = YES;
    person.handsome = NO;
    NSLog(@"tall:%d rich:%d handsome:%d",person.isTall,person.isRich,person.isHandsome);
    
    如下是苹果对isa的官方文档:

    所以arm64之后,isa占用8个字节,不仅仅只放地址值,还有很多信息等。

    isa详解 - 位域
    • nonpointer
      0 ,代表普通的指针,存储着Class、Meta-Class对象的内存地址
      1,代表优化过,使用位域存储更多的信息
    • hsa_assoc
      是否有设置过关联对象,如果没有,释放时会更快
    • has_cxx_dtor
      是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快
    • shiftcls
      存储着Class、Meta-Class对象的内存地址信息
    • magic
      用于在调试时分辨对象是否完成初始化
    • weakly_referenced
      是否有被弱引用指向过,如果没有,释放时会更快
    • deallocating
      对象是否正在释放
    • extra_rc
      里面存储的值是引用计数器减1
    • has_sidetable_rc
      引用计数器是否过大无法存储在isa中
      如果为1,那么引用计数会存储在一个叫SideTable的类的属性中
    位运算补充
    #import "ViewController.h"
    typedef enum {
        OptionsOne = 1 << 0,    //0b0001
        OptionsTwo = 1 << 1,    //0b0010
        OptionsThree = 1<<2,    //0b0100
        OptionsFour = 1 << 3    //0b1000
    }Options;
    @interface ViewController ()
    
    @end
    @implementation ViewController
    - (void)setOptions:(Options)options{
        if (options & OptionsOne) {
            NSLog(@"包含了OptionsOne");
        }
        if (options & OptionsTwo) {
            NSLog(@"包含了OptionsTwo");
        }
        if (options & OptionsThree) {
            NSLog(@"包含了OptionsThree");
        }
        if (options & OptionsFour) {
            NSLog(@"包含了OptionsFour");
        }
    }
    - (void)viewDidLoad {
        [super viewDidLoad];
        [self setOptions:OptionsOne | OptionsTwo | OptionsFour];
    
        self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    }
    @end
    

    源代码

    typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
        UIViewAutoresizingNone                 = 0,
        UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,
        UIViewAutoresizingFlexibleWidth        = 1 << 1,
        UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
        UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
        UIViewAutoresizingFlexibleHeight       = 1 << 4,
        UIViewAutoresizingFlexibleBottomMargin = 1 << 5
    };
    

    由此可见,源代码的实现思路和枚举Options的思路是一致的。

    Class的结构

    元类对象是一种特殊的类对象,数据结构都是一样的,只是里边存储的数据不一样,都是Class类型。


    Class的结构
    class_rw_t
    • class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容
    class_ro_t
    • class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容
    method_t
    • method_t是对方法/函数的封装
    • IMP代表函数的具体实现
      typedef id _Nullable (*IMP)(id _Nonnull,SEL _Nonnull, ...);
    • SEL代表方法/函数名,一般叫做选择器,底层结构跟char *类似:可以通过@selector()sel_registerName()获得;可以通过sel_getName()NSStringFromSelector()转成字符串;不同类中相同名字的方法,所对应的方法选择器是相同, typedef struct objc_selector *SEL;
    • types包含了函数返回值、参数编码的字符串,实例化一个Person类,随便写一个对象方法,打断点之后lldb打印出types的值:v 16 @ 0 :8,这里边v,@,:等的意思可以看下边的解释。16为所有参数所占的字节数,@0:从0字节开始, :8 :SEL为8开始,
    Type Encoding
    • iOS中提供了一个叫做@encode的指令,可以将具体的类型表示成字符串编码。
    NSLog(@"打印结果=%s",@encode(int));//打印结果:打印结果=I
    NSLog(@"打印结果=%s",@encode(id));//打印结果:打印结果=@
    NSLog(@"打印结果=%s",@encode(SEL));//打印结果:打印结果=:
    
    方法缓存
    • Class内部结构中有个方法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度
      在往buckets里边存调用过的函数时,并不是下标0开始逐一往里存储,存的时候,就是用@selector(方法名) & _mask = 索引下标,索引下标是几就把这个值存到第几个上,如下图:
      那么存在一种情况是:不同的方法名 & 不同的_mask取得的索引下标可能存在相同的情况,苹果的底层源码是这样处理的,如果索引下标已有存储的值,那么就把&的值存在(索引-1)的下标位置,等到取的时候,方法名&_mask=索引下标,判断不等于key的话就-1,再比较,不相等的话就一直-1下去,直到相等。
      最开始会给散列表一个长度,如果长度不够的话就进行扩容,扩容时,清空之前的缓存。
    • 缓存查找:objc-cache.mm;bucket_t * cache_t::find(cache_key_t k, id receiver)

    objc_msgSend

    Person.h文件
    #import <Foundation/Foundation.h>
    @interface Person : NSObject
    - (void)personTest;
    @end
    Person.m文件
    #import "Person.h"
    
    @implementation Person
    -(void)personTest{
        NSLog(@"%s",__func__);
    }
    @end
    
    #import <Foundation/Foundation.h>
    #import "GoodStudent.h"
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Person *person = [[Person alloc] init];
            [person personTest];
        }
        return 0;
    }
    

    在终端cd 到main文件所在的位置,输入xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp生成c++文件,拖入到工程中不进行编译,会发现 [person personTest];代码在c++文件中是
    ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("personTest"))去掉一些影响学习的强转符号,就变成了objc_msgSend(person,sel_registerName("personTest"));代码,sel_registerName("personTest")是#import <objc/runtime.h>中的方法


    在上图中可以看出sel_registerName("")方法需要一个c语言的字符串参数,返回值是SEL,所以sel_registerName("personTest") == @selector(personTest)
    Person *person = [[Person alloc] init];
    [person personTest];
    NSLog(@"%p %p",sel_registerName("personTest"),@selector((personTest)));
    打印结果:0x100003f96 0x100003f96
    

    地址是一样的,说明sel_registerName("personTest") == @selector(personTest)

    现在再调用一下[Person initialize];方法,在终端中刚刚的位置用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp重新生成一下,再看c++的文件,

    ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("personTest"));
    ((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("initialize"));
    

    同样的,去掉一些强转的符号:

    objc_msgSend(person, sel_registerName("personTest"));等同于
    objc_msgSend(person, @selector(personTest));
    消息接收者(receiver):person
    消息名称:personTest
    objc_msgSend)(objc_getClass("Person"), sel_registerName("initialize"));等同于
    objc_msgSend)([Person class], @selector(initialize));
    消息接收者(receiver):[Person class]
    消息名称:initialize
    //所以OC的方法调用也被称为:消息机制,给方法调用者发送消息
    
    objc_msgSend执行流程
    • OC中的方法调用,其实都是转换为objc_msgSend函数的调用
    • objc_msgSend的执行流程可以分为3大阶段:
      消息发送
      动态方法解析
      消息转发
    objc_msgSend执行流程 - 源码跟读

    objc_msgSend底层代码是用汇编实现的,(底层原理代码,如果调用频次比较高的方法,很多都是用汇编编写,效率更高)

    objc_msgSend执行流程01 - 消息发送
    • 如果是从class_rw_t中查找方法:已经排序的,用二分查找,没有排序的,遍历查找
    • receiver通过isa指针找到receiverClass
    • receiverClass通过superclass指针找到superclass
    objc_msgSend执行流程02 - 动态方法解析
    Person.h文件
    #import <Foundation/Foundation.h>
    @interface Person : NSObject
    - (void)test;
    @end
    
    Person.m文件(第一种写法)
    #import "Person.h"
    #import <objc/runtime.h>
    @implementation Person
    //- (void)test{
    //    NSLog(@"%s",__func__);
    //}
    
    - (void)other{
        NSLog(@"%s",__func__);
    }
    struct method_t {
        SEL sel;
        char *types;
        IMP imp;
    };
    + (BOOL)resolveInstanceMethod:(SEL)sel{
    
        if (sel == @selector(test)) {
            //获取其他方法
            struct method_t *method = (struct method_t *) class_getInstanceMethod(self, @selector(other));
            //动态添加test方法的实现
            class_addMethod(self, sel, method->imp, method->types);
            //返回YES代表有动态添加方法
            return YES;
        }
        return [super resolveInstanceMethod:sel];
    }
    @end
    
    Person.m文件(第二种写法)
    #import "Person.h"
    #import <objc/runtime.h>
    @implementation Person
    //- (void)test{
    //    NSLog(@"%s",__func__);
    //}
    - (void)other{
        NSLog(@"%s",__func__);
    }
    + (BOOL)resolveInstanceMethod:(SEL)sel{
        if (sel == @selector(test)) {
            //获取其他方法
            Method method = class_getInstanceMethod(self, @selector(other));
            //动态添加test方法的实现
            class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
            //返回YES代表有动态添加方法
            return YES;
        }
        return [super resolveInstanceMethod:sel];
    }
    @end
    

    如果person类里边是类方法,不是对象方法,写法如下:

    Person.h文件
    #import <Foundation/Foundation.h>
    @interface Person : NSObject
    + (void)test;
    @end
    
    Person.m文件
    #import "Person.h"
    #import <objc/runtime.h>
    @implementation Person
    void c_other(id self,SEL _cmd){
        NSLog(@"c_other - %@ - %@",self,NSStringFromSelector(_cmd));
    }
    + (BOOL)resolveClassMethod:(SEL)sel{
        if (sel == @selector(test)) {
            //第一个参数是object_getClass(self)
            class_addMethod(object_getClass(self), sel, (IMP)c_other,"v16@0:8");
        }
        return [super resolveClassMethod:sel];
    }
    @end
    

    那么如果没有实现+ (BOOL)resolveInstanceMethod:(SEL)sel方法或者+ (BOOL)resolveClassMethod:(SEL)sel方法,或者在这两个方法里没有动态添加方法,那么就进入第三个流程:消息转发

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

    消息转发:将消息转发给别人


    • 开发者可以在forwardInvocation:方法中自定义任何逻辑
    • 以上方法都有对象方法、类方法两个版本(前面可以是加号+,也可以是减号-)
      上图中所示逻辑代码实现如下:
    Cat.h文件
    #import <Foundation/Foundation.h>
    @interface Cat : NSObject
    - (void)test;
    @end
    
    Cat.m文件
    #import "Cat.h"
    @implementation Cat
    -(void)test{
        NSLog(@"%s",__func__);  
    }
    @end
    
    Person.h文件
    #import <Foundation/Foundation.h>
    @interface Person : NSObject
    - (void)test;
    @end
    
    Person.m文件
    #import "Person.h"
    #import <objc/runtime.h>
    #import "Cat.h"
    @implementation Person
    -(id)forwardingTargetForSelector:(SEL)aSelector{
        if (aSelector == @selector(test)) {
            //objc_msgSend([[Cat alloc] init],aSelector)
            return [[Cat alloc] init];
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    @end
    

    但是如果-(id)forwardingTargetForSelector:(SEL)aSelector方法不实现或者返回值为nil,则- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector方法

    Person.m文件
    #import "Person.h"
    #import <objc/runtime.h>
    #import "Cat.h"
    @implementation Person
    //方法签名:返回值类型、参数类型
    - (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 = [[Cat alloc] init];
    //    [anInvocation invoke];
        [anInvocation invokeWithTarget:[[Cat alloc] init]];
         开发者可以在```forwardInvocation:```方法中自定义任何逻辑的意思是:不一定非要用anInvocation返回一个对象,也可以直接只打印,比如在这个方法里只写一句NSLog(@"abc------");代码,那么person在调用test方法之后,只会打印一句abc------。
    }
    @end
    

    那么如果test方法有一个参数的情况下,用方法签名的实现:

    Person.h文件
    #import <Foundation/Foundation.h>
    @interface Person : NSObject
    - (void)test:(int)age;
    @end
    
    Person.m文件
    #import "Person.h"
    #import <objc/runtime.h>
    #import "Cat.h"
    @implementation Person
    //方法签名:返回值类型、参数类型
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
        if (aSelector == @selector(test)) {
            return [NSMethodSignature signatureWithObjCTypes:"v20@0:8i16"];
            //或者直接写成 return [NSMethodSignature signatureWithObjCTypes:"v@:i"];
        }
        return [super methodSignatureForSelector:aSelector];
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation{
        //参数顺序:receiver、selector、other arguments
        int age;
        [anInvocation getArgument:&age atIndex:2];
        NSLog(@"%d",age + 10);
    }
    @end
    

    下面处理一下类方法:

    Cat.h文件
    #import <Foundation/Foundation.h>
    @interface Cat : NSObject
    + (void)test;
    @end
    
    Cat.m文件
    #import "Cat.h"
    
    @implementation Cat
    + (void)test{
        NSLog(@"%s",__func__);  
    }
    @end
    
    Person.h文件
    #import <Foundation/Foundation.h>
    @interface Person : NSObject
    +(void)test;
    @end
    
    Person.m文件
    #import "Person.h"
    #import <objc/runtime.h>
    #import "Cat.h"
    @implementation Person
    
    + (id)forwardingTargetForSelector:(SEL)aSelector{
        if (aSelector == @selector(test)) return [Cat class];
        return [super forwardingTargetForSelector:aSelector];
    }
    @end
    

    或者+ (id)forwardingTargetForSelector:(SEL)aSelector方法不实现或者返回值为nil

    Person.m文件
    #import "Person.h"
    #import <objc/runtime.h>
    #import "Cat.h"
    @implementation Person
    + (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
        if (aSelector == @selector(test)) return [NSMethodSignature signatureWithObjCTypes:"v@:"];
        return [super methodSignatureForSelector:aSelector];
    }
    
    + (void)forwardInvocation:(NSInvocation *)anInvocation{
        NSLog(@"123......");
    }
    @end
    

    dynamic:提醒编译器不要自动生成setter方法和getter方法的实现、不要自动生成成员变量。正常情况下,如果不写dynamic关键字,代码如下:

    Person.h文件
    #import <Foundation/Foundation.h>
    @interface Person : NSObject
    @property (nonatomic,assign) int age;
    @end
    
    Person.m文件
    #import "Person.h"
    #import <objc/runtime.h>
    @implementation Person
    
    @end
    
    main文件
    Person *person = [[Person alloc] init];
    person.age = 10;
    NSLog(@"age is = %d",person.age);
    打印结果:age is = 10
    

    如果用dynamic修饰,代码如下:

    Person.h文件
    #import <Foundation/Foundation.h>
    @interface Person : NSObject
    @property (nonatomic,assign) int age;
    @end
    
    Person.m文件
    #import "Person.h"
    #import <objc/runtime.h>
    @implementation Person
    @dynamic age;
    @end
    
    main文件
    Person *person = [[Person alloc] init];
    person.age = 10;
    NSLog(@"age is = %d",person.age);
    打印结果:直接崩掉interview-cache[83420:7465918] -[Person setAge:]: unrecognized selector sent to instance 0x108f71930
    
    LLVM的中间代码(IR)

    OC - > 中间代码(.ll)-> 汇编、机器代码

    • Objective-C在变为机器代码之前,会被LLVM编译器转换为中间代码(Intermediate Representation)
    • 可以使用以下命令行指令生成中间代码
      clang -emit-llvm -S main.m
    • 语法简介
      @ - 全局变量
      % - 局部变量
      alloca - 在当前执行的函数的堆栈帧中分配内存,当该函数返回到其调用者时,将自动释放内存
      i32 - 32位4字节的整数
      align - 对齐
      load - 读出,store写入
      icmp - 两个整数值比较,返回布尔值
      br - 选择分支,根据条件来转向label,不根据条件跳转的话类似goto
      label - 代码标签
      call - 调用函数
      具体可以参考官方文档:https://llvm.org/docs/LangRef.html

    相关文章

      网友评论

          本文标题:Runtime - isa相关

          本文链接:https://www.haomeiwen.com/subject/takqddtx.html