RunTime详细介绍

runtime是什么?

是一套比较底层的纯C语言API, 属于1个C语言库,runtime算是OC的幕后工作者

runtime可以做什么?

  1. 动态的创建类(KVO的实现原理)
  2. 动态的创建属性/方法
  3. 遍历所有成员变量和属性/方法(比如字典–>模型)

runtime相关头文件和函数

<objc/runtime.h>
<objc/message.h>
举例:
objc_msgSend : 给对象发送消息
class_copyMethodList : 遍历某个类所有的方法
class_copyIvarList : 遍历某个类所有的成员变量...

Ivar : 成员变量 
Method : 成员方法
复制代码

OC程序与运行时系统交互的三个不同场景:

  • 通过OC源代码(编译器创建数据结构和函数调用,实现语言的动态特性)
  • 通过定义在Foudation框架中NSObject中的方法(NSObject方法有一些简单的查询的运行时系统信息,这些方法允许对象自省-自我查找 举例:isKindOfClass、isMemberOfClass、respondsToSelector、conformsToProtocol、methodForSelector)
  • 通过直接调用运行时的函数

runtime相关术语的数据结构

SEL

它是selector在 OC 中的表示。selector 是方法选择器,在相同的类中不会有命名相同的两个方法。selector 对方法名进行包装,以便找到对应的方法实现。它的数据结构是:

typedef struct objc_selector *SEL;
复制代码

你可以通过 OC 编译器器命令@selector() 或者 Runtime 系统的 sel_registerName函数来获取一个 SEL 类型的方法选择器。

注意:不同类中相同名字的方法所对应的 selector 是相同的,由于变量的类型不同,所以不会导致它们调用方法实现混乱。

id

id 是一个参数类型,它是指向某个类的实例的指针。 定义如下:

typedef struct objc_object *id;
struct objc_object { Class isa; };
复制代码

以上定义,看到 objc_object 结构体包含一个isa指针,根据 isa 指针就可以找到对象所属的类。

注意: isa 指针在代码运行时并不总指向实例对象所属的类型,所以不能依靠它来确定类型,要想确定类型还是需要用对象的 - isKindOfClass等方法。

KVO 的实现机理就是将被观察对象的 isa 指针指向一个中间类

Class

typedef struct objc_class *Class;
复制代码

Class 其实是指向 objc_class 结构体的指针。objc_class 的数据结构如下:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
复制代码
// 成员变量列表
struct objc_ivar_list {
    int ivar_count                                           OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_ivar ivar_list[1]                            OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;
// 方法列表
struct objc_method_list {
    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;
    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}
复制代码

我们可以动态修改 *methodList 的值来添加成员方法,这也是 Category 实现的原理,同样解释了 Category 不能添加属性的原因

objc_class 中也有一个 isa 指针,这说明 Objc 类本身也是一个对象。为了处理类和对象的关系,Runtime 库创建了一种叫做 Meta Class(元类) ,类对象所属的类就叫做元类。Meta Class 表述了类对象本身所具备的元数据。

Method

Method 代表类中某个方法的类型

typedef struct objc_method *Method;
复制代码

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}
复制代码

objc_method 存储了方法名,方法类型和方法实现: 方法名类型为 SEL 方法类型 method_types 是个 char 指针,存储方法的参数类型和返回值类型 method_imp 指向了方法的实现,本质是一个函数指针

Ivar

Ivar 是表示成员变量的类型。

typedef struct objc_ivar *Ivar;
复制代码
struct objc_ivar {
    char *ivar_name                                          OBJC2_UNAVAILABLE;
    char *ivar_type                                          OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}
复制代码

其中 ivar_offset 是基地址偏移字节

IMP

IMP在objc.h中的定义是:

typedef id (*IMP)(id, SEL, ...);
复制代码

它就是一个函数指针,这是由编译器生成的。当你发起一个 OC 消息之后,最终它会执行的那段代码,就是由这个函数指针指定的。而 IMP 这个函数指针就指向了这个方法的实现。IMP指向的方法与 objc_msgSend 函数类型相同,参数都包含 id和SEL 类型。每个方法名都对应一个 SEL 类型的方法选择器,而每个实例对象中的 SEL 对应的方法实现肯定是唯一的,通过一组 id和 SEL 参数就能确定唯一的方法实现地址。

Cache

Cache 定义如下:

typedef struct objc_cache *Cache
复制代码
struct objc_cache {
    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
    Method buckets[1]                                        OBJC2_UNAVAILABLE;
};
复制代码

Cache 为方法调用的性能进行优化,每当实例对象接收到一个消息时优先在 Cache 中查找。 Runtime 系统会把被调用的方法存到 Cache中,如果一个方法被调用,那么它有可能今后还会被调用,下次查找的时候就会效率更高。

利用runtime获取信息:

unsigned int count;
//获取属性列表
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i=0; i<count; i++) {
    const char *propertyName = property_getName(propertyList[i]);
    NSLog(@"property---->%@", [NSString stringWithUTF8String:propertyName]);
}

//获取方法列表
Method *methodList = class_copyMethodList([self class], &count);
for (unsigned int i; i<count; i++) {
    Method method = methodList[i];
    NSLog(@"method---->%@", NSStringFromSelector(method_getName(method)));
}

//获取成员变量列表
Ivar *ivarList = class_copyIvarList([self class], &count);
for (unsigned int i; i<count; i++) {
    Ivar myIvar = ivarList[i];
    const char *ivarName = ivar_getName(myIvar);
    NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]);
}

//获取协议列表
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
for (unsigned int i; i<count; i++) {
    Protocol *myProtocal = protocolList[i];
    const char *protocolName = protocol_getName(myProtocal);
    NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]);
}
复制代码

RunTime使用场景举例

1. 消息发送

#import "HTMITest.h"

#import <objc/message.h>

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
        
    // 使用 Runtime 创建一个对象
    // 根据类名获取到类
    Class testClass = objc_getClass("HTMITest");
    
    // 通过类创建实例对象
    // 如果这里报错,请将 Build Setting -> Enable Strict Checking of objc_msgSend Calls 改为 NO
    HTMITest *test = objc_msgSend(testClass, @selector(alloc));

    test = objc_msgSend(test, @selector(init));
    objc_msgSend(test, @selector(test));
    
    HTMITest *otherTest = objc_msgSend(objc_msgSend(objc_getClass("HTMITest"), sel_registerName("alloc")), sel_registerName("init"));
    objc_msgSend(otherTest, @selector(testInteger:), 10);
}

@end
复制代码

2. MethodSwizzling

#import "UIImage+HTMITest.h"

#import <objc/message.h>

@implementation UIImage (Success)

+ (void)load {
    // 获取到两个方法
    Method imageNamedMethod = class_getClassMethod(self, @selector(imageNamed:));
    Method tuc_imageNamedMethod = class_getClassMethod(self, @selector(htmi_imageNamed:));
    
    // 交换方法
    method_exchangeImplementations(imageNamedMethod, htmi_imageNamedMethod);
}

+ (UIImage *)htmi_imageNamed:(NSString *)name {
    // 因为来到这里的时候方法实际上已经被交换过了
    // 这里要调用 imageNamed: 就需要调换被交换过的 htmi_imageNamed
    UIImage *image = [UIImage tuc_imageNamed:name];
    
    return image;
}
@end

复制代码

3. 动态加载方法

#import <objc/message.h>

@implementation HTMITest

void test(id self, SEL _cmd,  NSNumber *test) {
    NSLog(@"测试%@",test);
}

// 当调用了一个未实现的方法会来到这里
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == NSSelectorFromString(@"test:")) {
        // 动态添加方法
        class_addMethod(self, @selector(test:), test, "v@:@");
        
        return YES;
    }
    
    return [super resolveInstanceMethod:sel];
}

@end
复制代码

4. 动态关联属性

.h
@interface NSObject (Property)

// @property 在分类中只会生成 getter/setter 方法
// 不会生成成员属性
@property (nonatomic, copy) NSString *name;

@end
.m
#import <objc/message.h>

@implementation NSObject (Property)

- (void)setName:(NSString *)name {
    // 属性关联给对象
    objc_setAssociatedObject(self, "name", name, OBJC_ASSOCIATION_COPY);
}

- (NSString *)name {
    // 得到属性
    return objc_getAssociatedObject(self, "name");
}

@end

复制代码

5. 字典转模型

#import <objc/message.h>

@implementation NSObject (Model)

+ (instancetype)modelWithDict:(NSDictionary *)dict updateDict:(NSDictionary *)updateDict {
    id model = [[self alloc] init];
    // 遍历模型中属性
    unsigned int count = 0;
    
    Ivar *ivars = class_copyIvarList(self, &count);
    
    for (int i = 0 ; i < count; i++) {
       Ivar ivar = ivars[i];
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        ivarName = [ivarName substringFromIndex:1];
        id value = dict[ivarName];
        // 模型中属性名对应字典中的key
        if (value == nil) {
            if (updateDict) {
                NSString *keyName = updateDict[ivarName];
                value = dict[keyName];
            }
        }
        [model setValue:value forKeyPath:ivarName];
    }
    return model;
}

@end
复制代码

6. 消息转发

#import "HTMIOtherTest.h"
#import <objc/message.h>

@implementation HTMITest

#pragma mark - 实例方法

void test(id self,SEL _cmd) {
    NSLog(@"测试");
}

//
//  1、在没有找到方法时,会先调用此方法,可用于动态添加方法
//  返回 YES 表示相应 selector 的实现已经被找到并添加到了类中,否则返回 NO
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    //    if (sel == NSSelectorFromString(@"test")) {
    //        // 动态添加方法
    //        class_addMethod(self, @selector(test), test, "v@:");//添加的方法会立即执行
    //
    //        return NO;//一旦添加方法了,返回什么都没有影响
    //    }
    //
    //    return [super resolveInstanceMethod:sel];
    return NO;
}

//  2、如果第一步的返回 NO 或者直接返回了 YES 而没有添加方法,该方法被调用
//  在这个方法中,我们可以指定一个可以返回一个可以响应该方法的对象
//  如果返回 self 就会死循环
- (id)forwardingTargetForSelector:(SEL)aSelector {
    
    //    HTMIOtherTest *other =  [[HTMIOtherTest alloc] init];
    //    return other;
    return nil;
}


//  3、如果 `forwardingTargetForSelector:` 返回了 nil,则该方法会被调用,系统会询问我们要一个合法的方法签名
//  若返回 nil,则不会进入下一步,而是无法处理消息
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

// 当实现了此方法后,-doesNotRecognizeSelector: 将不会被调用
// 在这里进行完整的消息转发
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    // 我们还可以改变方法选择器
    [anInvocation setSelector:@selector(myTest)];
    // 改变方法选择器后,还需要指定是哪个对象的方法
    [anInvocation invokeWithTarget:self];
}

//如果不实现forwardInvocation:方法,则会进入此方法,程序不会崩溃
- (void)doesNotRecognizeSelector:(SEL)aSelector {
    NSLog(@"无法处理消息:%@", NSStringFromSelector(aSelector));
}

- (void)myTest {
    NSLog(@"HTMITest 没有实现 -test 方法,并且成功的转成了 -myTest 方法");
}

#pragma mark - 类方法

+ (BOOL)resolveClassMethod:(SEL)sel {
    return NO;
}

+ (id)forwardingTargetForSelector:(SEL)aSelector {
    return nil;
}

+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

+ (void)forwardInvocation:(NSInvocation *)anInvocation {
    [anInvocation setSelector:@selector(myTest)];
    [anInvocation invokeWithTarget:self];
}

+ (void)doesNotRecognizeSelector:(SEL)aSelector {
    NSLog(@"无法处理消息:%@", NSStringFromSelector(aSelector));
}

+ (void)myTest {
    NSLog(@"HTMITest 没有实现 +test 方法,并且成功的转成了 +myTest 方法");
}

@end
复制代码

7. 对象归档、解档

#import <objc/runtime.h>

@implementation NSObject (Archive)

- (NSArray *)ignoredProperty {
    return @[];
}

- (void)htmi_initWithCoder:(NSCoder *)aDecoder {
    Class selfClass = self.class;
    while (selfClass &&selfClass != [NSObject class]) {
        
        unsigned int outCount = 0;
        Ivar *ivars = class_copyIvarList(selfClass, &outCount);
        for (int i = 0; i < outCount; i++) {
            Ivar ivar = ivars[i];
            NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
            
            if ([self respondsToSelector:@selector(ignoredProperty)]) {
                if ([[self ignoredProperty] containsObject:key]) continue;
            }
            
            id value = [aDecoder decodeObjectForKey:key];
            [self setValue:value forKey:key];
        }
        free(ivars);
        selfClass = [selfClass superclass];
    }
    
}

- (void)htmi_encodeWithCoder:(NSCoder *)aCoder {
    Class selfClass = self.class;
    while (selfClass &&selfClass != [NSObject class]) {
        
        unsigned int outCount = 0;
        Ivar *ivars = class_copyIvarList([self class], &outCount);
        for (int i = 0; i < outCount; i++) {
            Ivar ivar = ivars[i];
            NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
            
            if ([self respondsToSelector:@selector(ignoredProperty)]) {
                if ([[self ignoredProperty] containsObject:key]) continue;
            }
            
            id value = [self valueForKeyPath:key];
            [aCoder encodeObject:value forKey:key];
        }
        free(ivars);
        selfClass = [selfClass superclass];
    }
}

@end
复制代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值