跳过正文

runtime中的对象

·6 分钟
目录

前言
#

runtime 是一个运行时系统,是iOS object-c的核心,使 C语言 具有了面向对象的功能。runtime 库实现了面向对象的继承链。利用 c 的结构体和指针。并且有对象模型的定义。

方法的解析(消息转发)在运行时,编译时不会对方法的实现进行校验,因此我们使用 runtime 库对运行时的对象的属性,方法进行操作。省去了 oc 层。运行 的时候对协议,属性,方法进行整合,如包括 rw 和 ro 整合。

isa 与superclass
#

​ 下图为说明:

  • 图解 isa 解决对象的定义,类的定义。(对象方法的起源,类方法的起源)
  • 图解 superClass 解决了继承问题。(方法调用的追溯).

对象和类对象
#

OC中几乎所有的类都继承自NSObject,OC的动态性也是通过NSObject实现的。OC 类、对象和方法等都对应了 C 中的结构体,而且我们都可以在 Rutime 源码中找到它们的定义。

objc_object
#

我们可以找到NSObject的定义:

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

可以看出NSObject里有一个指向Class的isa,其中对于Class的定义在objc.h:

typedef struct objc_class *Class;

*/// Represents an instance of a class.*

struct objc_object {

    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

};

总结:objc_class代表类对象,objc_object代表实例对象,objc_object的isa指向objc_class。这里可以得出一个结论,实例对象的isa是指向类(类对象)的。

objc_class
#

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    class_rw_t *data() const {
        return bits.data();
    }
}

struct objc_object {
private:
    isa_t isa;
}

union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    Class cls;
    uintptr_t bits;
}

class_data_bits_t
#

我们再回来看类中的其他属性,之前表示类的属性、方法、以及遵循的协议都放在了class_data_bits_t中,更准确的说是放在了class_rw_t

struct class_data_bits_t {
    friend objc_class;

    // Values are the FAST_ flags above.
    uintptr_t bits;

    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
}

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t version;
    uint16_t witness;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
}

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;

    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
}

这里面引入了class_rw_tclass_ro_t(rw-readwrite,ro-readonly)两个结构体。可以看到class_rw_t是包含一个常量指针ro,结构体为class_ro_t。这里存储了当前类在编译期就已经确定的属性、方法以及遵循的协议。在 ObjC 运行时的时候会调用 realizeClass 方法,将class_ro_t传入class_rw_t,所以新版的动态性是通过这种方式实现的。

class_rw_t
#

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods; // 方法列表
    property_array_t properties; // 属性列表
    protocol_array_t protocols; // 协议列表

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;
};

class_ro_t
#

class_ro_t *ro 是只读的,内部直接存储的直接就是 method_list_t、protocol_list_t 、property_list_t 类型的一维数组,数组里面分别存放的是类的初始信息,以 method_list_t 为例,method_list_t 中直接存放的就是 method_t,但是是只读的

编译的类的方法,属性,成员变量属性协议等等都是存放在 class_ro_t 中的,当程序运行的时候,需要将分类中的列表跟类初始的列表合并在一起的时,就会将 class_ro_t 中的列表和分类中的列表合并起来存放在 class_rw_t 中,也就是说 class_rw_t 中有部分列表是从 class_ro_t 里面拿出来的。并且最终和分类的方法合并。

cache_t
#

一些多次调用的方法会放入这个集合中。

method_t
#

这是函数的结构体,里面包含3个成员变量。SEL是方法的名字name。types是类型编码,类型可参考Type EncodingIMP是一个函数指针,指向的是函数的具体实现。在runtime中消息传递和转发的目的就是为了找到IMP,并执行函数。

struct method_t {
    SEL name;
    const char *types;
    MethodListIMP imp;

    struct SortBySELAddress :
        public std::binary_function<const method_t&,
                                    const method_t&, bool>
    {
        bool operator() (const method_t& lhs,
                         const method_t& rhs)
        { return lhs.name < rhs.name; }
    };
};

方法被编译为 Method 结构体,同时会有一个 sel 于 imp 对应关系表。可利用 runtime 库 中的函数拿到相应的 Method,imp。

  • Method 结构体包括 sel,imp,name。 sel 是方法的名字编号 imp 是方法实现的指针 name 方法的名字。
  • 直接调用 imp 效率比较高。

objc_msgSend解析
#

objc_msgSend
#

向对象发送的消息在编译阶段被转化为 C 函数,即 objc_msgSend。objc_msgSend(receiver,selector) 在 runtime 库中有 objc_msgSend 实现。objc_msgSendSuper 的实现。

objc_msgSend 具体实现

  1. 检测 selector target 。有些 selector 会被忽略掉不做处理,如果 target 为 nil 会被忽略掉也不处理。

  2. 1.中都不满足,会根据 selector,找到对应 imp 进行执行。

  3. 根据 target isa 指针找到对应的类,先从 cache 里面找,若可以找得到就跳到对应的函数去执行。找不到会在 methodLists 查找如果 methodLists 找不到,就到超类的方法列表里寻找,一直找,直到找到 NSObject 类为止。

  4. 如果还找不到,Runtime 就提供了如下三种方法来处理:动态方法解析、消息接受者重定向、消息重定向,

  5. 这三种方法的调用关系如下图:

实际使用
#

runtime 使用的话是使用里面定义好的 c 函数,如关联函数,获取 class 信息函数,反射函数。

获取 class 的信息
#

属性列表,方法列表,协议列表,成员变量表 属性列表的使用

动态归档解档使用

unsigned int count = 0;
Ivar *ivars = class_copyIvarList([Person class], &count); 
for (int i = 0; i < count; i++) {
Ivar ivar = ivars[i];
const char * name = ivar_getName(ivar);
NSString *key = [[NSString alloc] initWithUTF8String:name]; 
id value = [self valueForKeyPath:key];
[encoder encodeObject:value forKey:key];
  }
  free(ivars);

字典转模型使用

NSDictionary *dict =
    @ { @"name" : @"lxz",
        @"age" : @18,
        @"gender" : @YES };
TestObject *object = [TestObject objectWithDict:dict];
@interface TestObject : NSObject
@property (nonatomic, copy) NSString    *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, assign) BOOL    gender;
+ (instancetype)objectWithDict:(NSDictionary *)dict;
@end
@implementation TestObject
+ (instancetype)objectWithDict:(NSDictionary *)dict
{
    return([[TestObject alloc] initWithDict:dict]);
}


- (instancetype)initWithDict:(NSDictionary *)dict
{
    self = [super init];
    if ( self )
    {
        unsigned int    count        = 0;
        objc_property_t *propertys    = class_copyPropertyList( [self class], &count ); for ( int i = 0; i < count; i++ )
        {
            objc_property_t property    = propertys[i];
            const char    *name        = property_getName( property );
            NSString    *nameStr    = [[NSString alloc] initWithUTF8String:name];
            id        value        = [dict objectForKey:nameStr];
            [self setValue:value forKey:nameStr];
        }
        free( propertys );
    }
    return(self);
}

属性修改使用

Person *ps = [[Person alloc] init];
NSLog(@"ps-nickName: %@",[ps valueForKey:@"nickName"]); //null
// 第一步:遍历对象的所有属性
unsigned int count;
Ivar *ivarList = class_copyIvarList([ps class], &count);
for (int i= 0; i<count; i++) {
    // 第二步:获取每个属性名
    Ivar ivar = ivarList[i];
    const char *ivarName = ivar_getName(ivar);
    NSString *propertyName = [NSString stringWithUTF8String:ivarName];
    if ([propertyName isEqualToString:@"_nickName"]) {
        // 第三步:匹配到对应的属性,然后修改;注意属性带有下划线
        object_setIvar(ps, ivar, @"燕航");
    }
}
NSLog(@"ps-nickName: %@",[ps valueForKey:@"nickName"]); // 燕航

关联
#

runtime 提供了給我们 3 个 API 以管理关联对象(存储、获取、移除):

是一个全局关联表来管理关联的对象以及被关联者的内存。

// 关联对象
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
// 获取关联的对象
id objc_getAssociatedObject(id object, const void *key)
// 移除关联的对象
void objc_removeAssociatedObjects(id object)

其中的参数

  • id object:被关联的对象(属性的所属方)
  • const void *key:关联的 key,要求唯一
  • id value:关联的属性
  • objc_AssociationPolicy policy:内存管理的策略

使用例子 NSObject+AssociatedObject.m

#import "NSObject+AssociatedObject.h"
#import <objc/runtime.h>

@implementation NSObject (AssociatedObject)

- (void)setAssociatedObject:(id)associatedObject
{
    objc_setAssociatedObject(self, @selector(associatedObject), associatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (id)associatedObject
{
    return objc_getAssociatedObject(self, _cmd);
}

@end

第三方框架都有使用如 massonry,MJRefresh 等。 注意:尽管可以模拟地为分类添加“属性”,但毕竟只是模拟。在分类中@property 不会生成_变量,也不会实现 getter 和 setter 方法。我们实现的只是 getter 和 setter 方法,并没有自动生成下划线开头的变量!

操作方法
#

  • class_replaceMethod 替换类方法的定义
  • method_exchangeImplementations 交换 2 个方法的实现
  • method_setImplementation 设置 1 个方法的实现

其中 method_exchangeImplementations 比较常用,替换系统方法调用。如埋点,页面统计,崩溃保护。

交换解决崩溃(可以做崩溃保护 Method Swizzling)

@implementation NSArray (Safe)
+ (void)load
  {
     //注意__NSArray0 类族
    [NSClassFromString(@"__NSArray0") swapMethod:@selector(objectAtIndex:)
                                   currentMethod:@selector(ls_zeroObjectAtIndex:)];

  }

- (id)ls_zeroObjectAtIndex:(NSUInteger)index
{
    if (index >= self.count)
    {
        return nil;
    }
    return [self ls_zeroObjectAtIndex:index];
}
//交换两个实例方法( - 方法)
+ (void)swapMethod:(SEL)originMethod currentMethod:(SEL)currentMethod;
{
    Method firstMethod = class_getInstanceMethod(self, originMethod);
    Method secondMethod = class_getInstanceMethod(self, currentMethod);
    method_exchangeImplementations(firstMethod, secondMethod);
}

反射机制
#

NSString、Class、SEL、Protocol 的转化

FOUNDATION_EXPORT NSString *NSStringFromSelector(SEL aSelector);
FOUNDATION_EXPORT SEL NSSelectorFromString(NSString *aSelectorName);

FOUNDATION_EXPORT NSString *NSStringFromClass(Class aClass);
FOUNDATION_EXPORT Class _Nullable NSClassFromString(NSString *aClassName);

FOUNDATION_EXPORT NSString *NSStringFromProtocol(Protocol *proto) API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
FOUNDATION_EXPORT Protocol * _Nullable NSProtocolFromString(NSString *namestr) API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0))

参考
#