Runtime

Runtime基础

Objective-C是一门动态性比较强的编程语言,跟C、C++等语言有着很大的不同,Objective-C的动态性是由Runtime API来支撑的,Runtime API提供的接口基本都是C语言的,源码由C\C++\汇编语言编写

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

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

具体应用

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

isa指针

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

// 掩码,一般用来按位与(&)运算的
#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)

设置一个setter和getter的bool类型

_tallRichHansome = 0b00000100;

- (void)setTall:(BOOL)tall
{
    if (tall) {
        _tallRichHansome |= TallMask;  //按位或
    } else {
        _tallRichHansome &= ~TallMask;  ////按位取反然后与
    }
}

- (BOOL)isTall
{
    return !!(_tallRichHansome & TallMask);  //按位与之后取两次反
}

优化方法,设置结构体位域

// 位域
struct {
    char tall : 1;
    char rich : 1;
    char handsome : 1;
} _tallRichHandsome;

- (void)setTall:(BOOL)tall
{
    _tallRichHandsome.tall = tall;
}

return _tallRichHandsome.tall
直接取值会有一个问题就是得到的是一个二进制位,如果得到的是 0b1,会变成0b1111 1111(会把1当做符号位了),所以用取反去解决,因为 -1 是非0的,所以返回 YES
- (BOOL)isTall
{
    return !!_tallRichHandsome.tall;
}

优化方法,union(共用体)

1.在存储多个成员信息时,编译器会自动给struct每个成员分配存储空间,struct 可以存储多个成员信息,而Union每个成员会用同一个存储空间,只能存储最后一个成员的信息。

2.都是由多个不同的数据类型成员组成,但在任何同一时刻,Union只存放了一个被先选中的成员,而结构体的所有成员都存在。

3.对于Union的不同成员赋值,将会对其他成员重写,原来成员的值就不存在了,而对于struct 的不同成员赋值 是互不影响的。

4.当共用体中存储的数据不一样的时候,eg:存储1和1000,这个时候如果赋值两个,取值其中一个的话,是得不到准确值的

注:在很多地方需要对结构体的成员变量进行修改。只是部分成员变量,那么就不能用联合体Union,因为Union的所有成员变量占一个内存。eg:在链表中对个别数值域进行赋值就必须用struct。

union {
    int bits;
    
    //增加可读性,其实并没有使用,只使用了bits
    struct {
        char tall : 1;
        char rich : 1;
        char handsome : 1;
        char thin : 1;
    };
} _tallRichHandsome;

- (void)setTall:(BOOL)tall
{
    if (tall) {
        _tallRichHandsome.bits |= TallMask;
    } else {
        _tallRichHandsome.bits &= ~TallMask;
    }
}

- (BOOL)isTall
{
    return !!(_tallRichHandsome.bits & TallMask);
}

所以 arm64下的isa结构是这样的


isa结构.png

Class、meta-class的地址值,最后三位永远都是0,比如:

ViewController -> 0x106331e28,8 = 0x1000

NSObject -> 0x106331e50,0 = 0x0000

位运算

定义一个枚举

typedef enum {
//    OptionsNone = 0,    // 0b0000
    OptionsOne = 1<<0,   // 0b0001
    OptionsTwo = 1<<1,   // 0b0010
    OptionsThree = 1<<2, // 0b0100
    OptionsFour = 1<<3   // 0b1000
} Options;

设置对应的变量
- (void)setOptions:(Options)options
{
    if (options & OptionsOne) {
        NSLog(@"包含了OptionsOne");
    }
    
    if (options & OptionsTwo) {
        NSLog(@"包含了OptionsTwo");
    }
    
    if (options & OptionsThree) {
        NSLog(@"包含了OptionsThree");
    }
    
    if (options & OptionsFour) {
        NSLog(@"包含了OptionsFour");
    }
}

调用
[self setOptions: OptionsOne | OptionsFour];

类的结构

class的结构如下图所示


class的结构.png
class_rw_t

class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容


class_rw_t结构.png
class_ro_t

class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容

class_ro_t结构.png
method_t

method_t是对方法\函数的封装


method_t结构体.png

IMP代表函数的具体实现


IMP.png

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

image.png
Type Encoding

iOS中提供了一个叫做@encode的指令,可以将具体的类型表示成字符串编码


Type Encoding.png

方法缓存

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


cache_t结构.png

缓存查找

objc-cache.mm
bucket_t * cache_t::find(cache_key_t k, id receiver)

首先声明一下,@selector(test)是SEL类型,我们可以理解为一个字符串,标识作用
缓存具体实现就是将@selector(test)这个当做key与散列表的长度(mask)的值做&运算,得出的结果就是散列表的下标,然后将方法存储在对应下标的value里面,下次再次取这个值的时候依然做&运算,就可以做到O(1)复杂度的速度。有种情况两个方法获取的key是相同的,arm64是向上查找,x86_64是向下查找,如果获取的结果是重复的,那么会发现key和传入的方法不匹配,则会根据规则向上或者向下查找,查找到尽头则会循环。如果为空,则填入方法。如果列表已经满了,则会2倍扩容散列表,扩容后会清空缓存列表。

objc_msgSend

OC中的方法调用,其实都是转换为objc_msgSend函数的调用

objc_msgSend.png
objc_msgSend([Person class], @selector(initialize));
消息接收者(receiver):[Person class]
消息名称:initialize
    
OC的方法调用:消息机制,给方法调用者发送消息
    
objc_msgSend如果找不到合适的方法进行调用,会报错unrecognized selector sent to instance

objc_msgSend的执行流程可以分为3大阶段

消息发送

根据对象和方法名查找(有序的是二分查找,无序的是线性查找)
Method meth = getMethodNoSuper_nolock(curClass, sel);

将方法添加到缓存中,无论方法实在哪一级的父类中,都会缓存到消息接收者的缓存中
log_and_fill_cache(cls, imp, sel, inst, curClass);

如果都没有找到方法,则进入下一阶段

动态方法解析

动态方法解析.png

实例方法解析:+resolveInstanceMethod:
类方法解析:+resolveClassMethod:

原理如下
if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }
}

方法一

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];

方法二

- (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];
}

方法三

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(test)) {
        // 动态添加test方法的实现
        class_addMethod(self, sel, (IMP)c_other, "v16@0:8");

        // 返回YES代表有动态添加方法
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

方法四

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 YES;
    }
    return [super resolveClassMethod:sel];
}

消息转发

实例方法

本质是经过了消息发送,消息的动态解析,都没有找到方法的实现,所以就会重新定向一个对象,去这个对象里查找发送的方法

@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

如果在 forwardingTargetForSelector 方法中并未找到合适的对象,则会执行下面方法

 方法签名:返回值类型、参数类型
- (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]];
}

类方法

类方法的调用和实例方法类似,都是要用消息接收者去调用,所以要用【+】去实现,因为在编写这个方法的时候是无法关联出来的,所以很多网上的资料说没有类方法的实现,这是不准确的

+ (id)forwardingTargetForSelector:(SEL)aSelector
{
    // objc_msgSend([[Cat alloc] init], @selector(test))
    // [[[Cat alloc] init] test]
    if (aSelector == @selector(test)) return [[Cat alloc] init]; 
//返回的是个实例对象,这个时候就是调用实例方法了
    return [super forwardingTargetForSelector:aSelector];
}

+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test)) return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    
    return [super methodSignatureForSelector:aSelector];
}

+ (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"1123");
}

常见题目

@dynamic和@synthesize

@property (assign, nonatomic) int age;

@synthesize age = _age
//自动生成setter和getter方法和_age成员变量

@dynamic age;
//提醒编译器不要自动生成setter和getter的实现(但不影响声明,外部可以调用,但是会报错,因为没有实现)、不要自动生成成员变量

super

[super message]的底层实现

1.消息接收者仍然是子类对象

2.从父类开始查找方法的实现

临时的结构体
struct objc_super {
    __unsafe_unretained _Nonnull id receiver; // 消息接收者
    __unsafe_unretained _Nonnull Class super_class; // 消息接收者的父类
};

NSLog(@"[self class] = %@", [self class]); // Student
NSLog(@"[self superclass] = %@", [self superclass]); // Person

objc_msgSendSuper({self, [Person class]}, @selector(class));
//这里self就是消息接收者,[Person class]就是父类,@selector(class)是方法名
NSLog(@"[super class] = %@", [super class]); // Student
NSLog(@"[super superclass] = %@", [super superclass]); // Person

真正的底层是objc_super2

objc_msgSendSuper2({self, [Person class]}, @selector(class));
这里第二个参数传入的就是self但是内部处理的时候会执行 self->superClass,这样就还是从父类开始查找了

isKindOfClass 和 isMemberOfClass

  • 区别
//这里使用的是isa指针,因为是类方法,所以指针指向的是元类,这里的判断是指,本身类的指针是否指向传入类,就是说传入类是否是本身类的元类
+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

//传进来的这个类对象是否等于本身的类对象
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

//这里使用的是isa指针,因为是类方法,所以指针指向的是元类,这里的判断是指,本身类的指针是否指向传入类,就是说传入类是否是本身类的元类
//与isMemberOfClass的不同之处,是包含了父元类的判断,这里说明下,NSObject的元类对象又指向NSObject的类对象
所以:
[Person isKindOfClass:[NSObject class]] 是 YES
+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}

//走了一个循环,从传进来的cls是否是等于[self class]开始,若不相等,则去看父类,若还不相等依次向上看父类,直到nil,返回NO
//如果传进来的类对象与本身的类对象相等,或者本身的类对象是传进来的类对象的子类都会返回YES
- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}

指针指向

NSString *test = @"123";

id cls = [Person class];
void *obj = &cls;//类对象的isa
[(__bridge id)obj print];//在类对象里面找到print方法

这个方法,等价于:

Person *person = [[Person alloc] init];
[person print];

-------------------------------------------------------

- (void)print
{
    NSLog(@"my name is %@", self->_name);
}

又因为局部变量分配在栈空间,栈空间分配,从高地址到低地址

void test()
{
    long long a = 4; // 0x7ffee638bff8
    long long b = 5; // 0x7ffee638bff0
    long long c = 6; // 0x7ffee638bfe8
    long long d = 7; // 0x7ffee638bfe0
}

所以print方法打印出来的是"my name is 123",为什么会有这种打印呢:


字符串声明在前面.png
struct Person_IMPL
{
    Class isa;
    NSString *_name;
};

栈是先进后出的,先声明的在高地址,使用的时候是低地址到高地址的
obj->cls->test,这个寻址方法可以看到,打印self->name就是忽略掉isa指针的8个字节,往下寻找,但是是用的obj指向的person的类对象,并不是常规意义的对象结构体,所以没有name这个属性,根据栈空间分配的逻辑,可以知道cls的高地址是test字符串,所以打印结果是 "my name is 123"

如果上面不写 NSString *test = @"123";那么打印出来的就是"my name is ",为什么会打印这个呢,因为咱们会有一个"[super viewDidoad]"方法,这个方法的本质就是

struct abc = {
    self,
    [ViewController class]
};
objc_msgSendSuper2(abc, sel_registerName("viewDidLoad"));

由此可知,如果我们高地址什么都不写,就是cls前面什么都没有的话,默认前面是self对象

没有声明任何字符串,上面是[super viewDidLoad].png

LLVM的中间代码

Objective-C在变为机器代码之前,会被LLVM编译器转换为中间代码(Intermediate Representation)

可以使用以下命令行指令生成中间代码

clang -emit-llvm -S main.m

语法简介
@ - 全局变量
% - 局部变量
alloca - 在当前执行的函数的堆栈帧中分配内存,当该函数返回到其调用者时,将自动释放内存
i32 - 32位4字节的整数
align - 对齐
load - 读出,store 写入
icmp - 两个整数值比较,返回布尔值
br - 选择分支,根据条件来转向label,不根据条件跳转的话类似 goto
label - 代码标签
call - 调用函数

常用方法

动态创建一个类(参数:父类,类名,额外的内存空间)
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)

//     获取成员变量信息
Ivar ageIvar = class_getInstanceVariable([Person class], "_age");
NSLog(@"%s %s", ivar_getName(ageIvar), ivar_getTypeEncoding(ageIvar));
    
//     设置和获取成员变量的值
Ivar nameIvar = class_getInstanceVariable([Person class], "_name");
    
Person *person = [[Person alloc] init];
object_setIvar(person, nameIvar, @"123");
object_setIvar(person, ageIvar, (__bridge id)(void *)10);
NSLog(@"%@ %d", person.name, person.age);
属性
获取一个属性
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)

获取apple隐藏的属性
unsigned int count;
Ivar *ivars = class_copyIvarList([UITextField class], &count);
for (int i = 0; i < count; i++) {
    // 取出i位置的成员变量
    Ivar ivar = ivars[i];
    NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
}
free(ivars);

字典转模型的使用
方法交换

方法交换做了处理,可以添加一些自己的逻辑在里面,避免崩溃,数据统计等功能

#import "NSMutableArray+Extension.h"
#import 

@implementation NSMutableArray (Extension)

+ (void)load
{
//保证只调用一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 类簇:NSString、NSArray、NSDictionary,真实类型是其他类型
        Class cls = NSClassFromString(@"__NSArrayM");
        Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
        Method method2 = class_getInstanceMethod(cls, @selector(_insertObject:atIndex:));
        method_exchangeImplementations(method1, method2);
    });
}

- (void)_insertObject:(id)anObject atIndex:(NSUInteger)index
{
    if (anObject == nil) return;
    
    [self performSelector:<#(SEL)#> withObject:<#(id)#>]
    [self _insertObject:anObject atIndex:index];
}

@end

你可能感兴趣的:(Runtime)