iOS底层基础知识Runtime

iOS之Runtime(一)彻底搞清类和对象

2018-12-05  本文已影响66人  我阿郑

Objective-C是基于动态运行时类型。C++程序编译时就直接编译成了可令机器读懂的机器语言;用Objective-C编写的程序不能直接编译成机器语言,而是在程序运行时期通过Runtime把程序转为机器语言。Runtime是OC不可缺少的重要一部分。

OC就基于运行时来工作。运行时环境可处理弱类型、函数存在检查工作,会检测注册列表里是否存在对应的函数,类型是否正确,最后确定正确的函数地址,在进行保存寄存器状态,压栈、函数调用等实际操作,确保了OC的灵活性。

理解 实例对象(instance)-》类对象(class object)-》元类对象(meta class) 的概念

我们创建的一个实例对象 底层编译出来其实就是一个结构体(struct objc_object), objc_object这个结构体里有一个isa指针指向了它的类对象 结构体 objc_class,类对象里也有一个isa指针指向Meta Class 元类对象,其实元类对象也是一个objc_class的结构体。因此,元类对象也有一个isa指针,那么它的isa又是指向什么呢?

为了不让这种结构无限延伸下去,OC的设计者让所有的元类对象isa指针都统一指向基类的元类对象(如果是从NSObject中继承而来的那么基类的元类对象就是指的NSObject的元类对象

可直接理解为继承自NSObject的类,它们的类对象isa指针都统一指向NSObject的元类对象,而NSObject的元类对象的isa指针是指向它自己

1 先看看对应的结构体代码

// 实例对象的结构体
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

// 类对象的结构体
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

    Class super_class                                        
    const char *name                                         
    long version                                             
    long info                                                
    long instance_size                                       
    struct objc_ivar_list *ivars                             
    struct objc_method_list **methodLists                    
    struct objc_cache *cache                                 
    struct objc_protocol_list *protocols                     
}

// id 类型
typedef struct objc_object *id;
// Class 类型
typedef struct objc_class *Class;

在上述objc_class结构体定义中,主要关注以下几个字段:

2 通过下面的图来再次说一下实例对象、类对象、元类对象的关系

![img2.png](https://img.haomeiwen.com/i2470124/b316d5979d7e97bd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

说明: 虚线箭头代表isa指针(表示指向);实线箭头代表super_class指针(表示super_class指针的指向)

如果上的解释没明白,看下面的解释

img2.png

D3继承D2,D2继承D1,D1最终继承NSObject。


那么如果我们想获取isa指针的指向对象呢?


    Person *p = [[Person alloc] init];
    
    Class p_isa = object_getClass(p); // 获取p对象isa指针指向的类对象
    Class pclass_isa = object_getClass(p_isa); // 获取类对象isa指针指向的元类对象
    Class pmetaclass_isa = object_getClass(pclass_isa);// 获取元类对象isa指针指向的NSObject的元类对象

    // class_isMetaClass 判断是否是元类对象
    BOOL b1 = class_isMetaClass(p_isa);
    BOOL b2 = class_isMetaClass(pclass_isa);
    BOOL b3 = class_isMetaClass(pmetaclass_isa);
    
    NSLog(@"---%@---%zd",p_isa,b1);
    NSLog(@"---%@---%zd",pclass_isa,b2);
    NSLog(@"---%@---%zd",pmetaclass_isa,b3);
    
    // 打印结果
    ---Person---0
    ---Person---1
    ---NSObject---1

通过代码可以看出

获取 superclass


@interface Person : TestObj

@end

#############

    Person *p = [[Person alloc] init];
    
    Class p_isa = object_getClass(p); // 获取p对象isa指针指向的类对象
    Class pclass_isa = object_getClass(p_isa); // 获取类对象isa指针指向的元类对象
    Class pmetaclass_isa = object_getClass(pclass_isa);// 获取元类对象isa指针指向的NSObject的元类对象
    
    Class p_superclass = class_getSuperclass(p_isa); // 获取p的类对象的superclass
    Class p_superclass_s = class_getSuperclass(p_superclass); // 获取p的类对象的superclass 的superclass
    Class p_superclass_s_s = class_getSuperclass(p_superclass_s); // 获取p的类对象的superclass 的superclass 的superclass
    
    NSLog(@"---%@---%@",p_isa,p_superclass);
    NSLog(@"---%@---%@",pclass_isa,p_superclass_s);
    NSLog(@"---%@---%@",pmetaclass_isa,p_superclass_s_s);
    
    //打印结果
     ---Person---TestObj
     ---Person---NSObject
     --NSObject---(null)

神经病院objc runtime入院考试

(1) 下面的代码输出什么?

@implementation Son : Father
- (id)init {
    self = [super init];
    if (self) {
        NSLog(@"%@", NSStringFromClass([self class]));
        NSLog(@"%@", NSStringFromClass([super class]));
    }
    return self;
}
@end

(2) 下面代码的结果?

BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];

// 上面的题换个写法

    Class c1 = [NSObject class];
    Class s1 = [Sark class];

    BOOL res1 = [c1 isKindOfClass:[NSObject class]];
    BOOL res2 = [c1 isMemberOfClass:[NSObject class]];
    BOOL res3 = [s1 isKindOfClass:[Sark class]];
    BOOL res4 = [s1 isMemberOfClass:[Sark class]];
    
    
    // 因为 NSObject meta class –super -> NSObject class 
    NSObject的元类对象的super_class指向了NSObject class
    相当于c1 是 NSObject class 子类的实例 所以第一个为真
    所以第二个c1是不是NSObject class 当前类的实例就为假了
    同样Sark的元类对象的super_class 最终指向了 NSObject class 所以s1 并不是 Sark 或者其子类的实例 ,所以 第三和第四都为假


(3) 下面的代码会?Compile Error / Runtime Crash / NSLog…?

@interface NSObject (Sark)
+ (void)foo;
@end
@implementation NSObject (Sark)
- (void)foo {
    NSLog(@"IMP: -[NSObject (Sark) foo]");
}
@end

// 测试代码

[NSObject foo];
[[NSObject new] foo];

(4) 下面的代码会?Compile Error / Runtime Crash / NSLog…?

@interface Sark : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation Sark
- (void)speak {
    NSLog(@"my name's %@", self.name);
}
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    id cls = [Sark class];
    void *obj = &cls;
    [(__bridge id)obj speak];
}
@end

答案

-(BOOL) isKindOfClass: classObj判断是否是这个类或者这个类的子类的实例

-(BOOL) isMemberOfClass: classObj 判断是否是这个类的实例
id cls = [Sark class];
void *obj = &cls;
obj已经满足了构成一个objc对象的全部要求(首地址指向ClassObject),遂能够正常走消息机制;

由于这个人造的对象在栈上,而取self.name的操作本质上是self指针在内存向高位地址偏移(32位下一个指针是4字节),按viewDidLoad执行时各个变量入栈顺序从高到底为(self, _cmd, self.class, self, obj)(前两个是方法隐含入参,随后两个为super调用的两个压栈参数),遂栈低地址的obj+4取到了self。

类与对象相关的操作函数

runtime提供了大量的函数来操作类与对象。

1 类相关操作函数

2 实例相关的操作函数

三、什么是‘关联对象’?

所谓“关联对象” 说白了也就是一个对象。 就是把一个类的实例(也叫对象)通过一个**唯一的key**动态绑定到另外一个对象上的过程叫做关联对象。

举例说明:

我们知道一个类的实例(对象)可以作为另外一个类的属性出现。比如有一个Dog类Person类,而Person下有一个属性是dog。

@interface Person ()

@property (nonatomic, strong) Dog *dog;

@end

通俗的理解就是: 可以给一个对象关联许多其它对象,关联的这些对象通过“key”来区分。

怎么进行关联那?

答: 需要用到 Runtime(运行时)里提供的两个方法。

/* Returns the value associated with a given object for a given key.
此方法根据给定的键(key)从某对象(object)中获取相应的关联对象值 */

OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key);

 /* Sets an associated value for a given object using a given key and association policy.
 此方法用给定的键(key)和策略(policy)给某对象(object)设置关联对象值(value) */
 
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)

参数说明:

参数 说明
id object 哪个对象设置关联对象 (比如这里的 Person类的实例对象)
const void *key 指定关联对象唯一的key(之后通过该key来 获取)
id value 指定关联的对象 (比如dog)
objc_AssociationPolicy 指定内存管理策略(枚举)

以下几种内存管理策略:

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
    OBJC_ASSOCIATION_RETAIN = 01401,
    OBJC_ASSOCIATION_COPY = 01403
};

说明:

如果想要解除关联对象,我们不需要去主动调用runtime提供的removeAssociated函数,可以使用setAssociatedObject置为nil来解除关联对象。

参考文档:

Runtime官方翻译

Objective-C Runtime 1小时入门教程

神经病院objc runtime入院考试

上一篇 下一篇

猜你喜欢

热点阅读