Runtime - isa相关

2023-04-05  本文已影响0人  冰棍儿好烫嘴

isa详解

&可以用来取出特定的位,想取哪一位,哪一位与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详解 - 位域
位运算补充
#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_ro_t
method_t
Type Encoding
NSLog(@"打印结果=%s",@encode(int));//打印结果:打印结果=I
NSLog(@"打印结果=%s",@encode(id));//打印结果:打印结果=@
NSLog(@"打印结果=%s",@encode(SEL));//打印结果:打印结果=:
方法缓存

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执行流程
objc_msgSend执行流程 - 源码跟读

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

objc_msgSend执行流程01 - 消息发送
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 - 消息转发

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


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)-> 汇编、机器代码

上一篇下一篇

猜你喜欢

热点阅读