Wrappres' Studio.

iOS 知识补齐-Runtime

字数统计: 5k阅读时长: 21 min
2019/02/11 Share

描述

OC 语言是一门动态语言,会将程序的一些决定工作从编译期推迟到运行期。所以 OC 不止需要依赖编译器,还需要依赖运行时环境。
OC 语言在编译期都会被编译为 C 语言的 Runtime 代码。二进制执行过程中执行的都是 C 语言代码。
OC 的类本质上都是结构体。在编译时都会以结构体的形式被编译到二进制中。
Runtime 是一套由 C,C++,汇编实现的 API,所有的方法调用都叫做发送消息。
Runtime 在 NSObject 中定义了一些基础操作。

数据结构

简单解释一下下面这张图:
一个普通的 objc_object(实例对象)中有一个 isa 指针,这个指针指向的是该实例对象所对应的 objc_class(类对象)。
因为 objc_class 继承自 objc_object,所以类对象(objc_class)中的 isa 指针指向的是 meta class(元类),子元类的 isa 指针都是根元类,根元类的 isa 指针指向其本身。

基础定义

OC 中主要有如下这些定义:

1
2
3
4
5
6
typedef struct objc_class *Class; 
typedef struct objc_object *id;
typedef struct method_t *Method;
typedef struct ivar_t *Ivar;
typedef struct category_t *Category;
typedef struct property_t *objc_property_t;

objc_object

从 Runtime 源码中可以看出,每个对象都是一个 objc_object 的结构体,在结构体中有一个 isa 指针,该指针指向自己所属的类,由 Runtime 负责创建对象。

objc_class

类被定义为 objc_class 结构体,objc-class 结构体继承自 objc_object,所以类也是对象。在 objc-class 结构体中定义了对象 method,list,protocol,ivar list 等,来表示类的等行为。

meta class

既然类是对象,那么类对象也是其他类的实例。所以 Runtime 设计了 meta class,通过 meta class 来创建类对象,所以类对象的 isa 指针指向对应的 meta class。而 meta class 也是一个对象,所有 meta class 的 isa 都指向其根元类,根元类的 isa 指针指向自己。

method_t

Method 用来表示方法,其包含 SEL 和 IMP,定义如下:

1
2
3
4
5
6
typedef struct method_t *Method;
struct method_t {
SEL name;
const char *types;
IMP imp;
};

IMP

在 Runtime 中 IMP 本质上就是一个函数指针,有两个默认的参数 id 和 SEL,id 也就是方法中的 self,这和 objc——msgSend()函数传递的参数一样。

1
typedef void (*IMP)(void /* id, SEL, ... */ );

property_t

Runtime 中定义了属性的结构体 property_t,用来表示对象中定义的属性。用@property 修饰符来修饰属性,修饰的属性为 objc_property_t 类型,其本质是 property_t 结构体,定义如下:

1
2
3
4
5
typedef struct property_t *objc_property_t;
struct property_t {
const char *name;
const char *attributes;
};

可以通过下面两个函数,分别获取实例对象的属性列表和协议列表

1
2
class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount)
class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount)

例获取属性列表:

1
2
3
4
5
6
7
8
9
10
11
unsigned int count;
objc_property_t *propertyList = class_copyPropertyList([Person class], &count);
for (int i = 0; i < count; i++) {
const char *propertyName = property_getName(propertyList[i]);
NSLog(@"property-->%@", [NSString stringWithUTF8String:propertyName]);
}
--------
Output:
2019-02-11 11:48:26.373741+0800 OCDemo[5467:1732583] property-->name
2019-02-11 11:48:26.373945+0800 OCDemo[5467:1732583] property-->name1
2019-02-11 11:48:26.374066+0800 OCDemo[5467:1732583] property-->name2

可以通过下面的方法,通过传入 Class 和 PropertyName,获取对应的 objc_property_t 属性结构体

1
2
3
4
class_getProperty(Class _Nullable cls, const char * _Nonnull name);
protocol_getProperty(Protocol * _Nonnull proto,
const char * _Nonnull name,
BOOL isRequiredProperty, BOOL isInstanceProperty);

NSObject

之前的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 声明Class和id
typedef struct objc_class *Class;
typedef struct objc_object *id;

// 声明常用变量
typedef struct objc_method *Method;
typedef struct objc_ivar *Ivar;
typedef struct objc_category *Category;
typedef struct objc_property *objc_property_t;

// objc_object和objc_class
struct objc_object {
Class _Nonnull isa OBJC_isa_AVAILABILITY;
};

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

之后的定义

1
2
3
4
5
6
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_isa_AVAILABILITY;
#pragma clang diagnostic pop
}

NSObject 中只有一个 Class 类型的 isa 变量,其他信息都隐藏起来了。

对象结构体

objc_object

OC 中每个对象都是一个结构体,结构体中都包含一个 isa 的成员变量,其位于成员变量的第一位。isa 的成员变量之前是 Class 类型,后来苹果将其改为 isa_t。

1
2
3
4
struct objc_object {
private:
isa_t isa;
};

OC 中的类和元类也是一样,都是结构体构成,由于类的结构体定义继承自 objc_object,所以其也是一个对象,也具有对象的 isa 特征。所以可以通过 isa_t 来查找对应的类或元类。

isa_t 定义

isa_t 是一个 union 的结构对象,union 类似于 C++结构体,其内部可以定义成员变量和函数。在 isa_t 中定义了 cls,bits,isa_t 三部分。
下面代码不是完整代码,只保留了 arm64 部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
union isa_t 
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }

Class cls;
uintptr_t bits;

# if __arm64__
# define isa_MASK 0x0000000ffffffff8ULL
# define isa_MAGIC_MASK 0x000003f000000001ULL
# define isa_MAGIC_VALUE 0x000001a000000001ULL
struct {
uintptr_t nonpointer : 1; // 是32位还是64位
uintptr_t has_assoc : 1; // 对象是否含有或曾经含有关联引用,如果没有关联引用,可以更快的释放对象
uintptr_t has_cxx_dtor : 1; // 表示是否有C++析构函数或OC的析构函数
uintptr_t shiftcls : 33; // 对象指向类的内存地址,也就是isa指向的地址
uintptr_t magic : 6; // 对象是否初始化完成
uintptr_t weakly_referenced : 1; // 对象是否被弱引用或曾经被弱引用
uintptr_t deallocating : 1; // 对象是否被释放中
uintptr_t has_sidetable_rc : 1; // 对象引用计数太大,是否超出存储区域
uintptr_t extra_rc : 19; // 对象引用计数
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};

# elif __x86_64__
// ····
# else
// ····
# endif
};

类结构体

objc_class 结构体

在 Runtime 中类也是一个对象,类的结构体 objc_class 是继承自 objc_object 的,具备对象所有的特征。
在 objc_class 中定义了三个成员变量:

  • superclass 是一个 objc_class 类型的指针,其指向其父类的 objc_class 结构体。
  • cache 用来处理已调用方法的缓存。
  • bits 是 objc_class 的主角,其内部只定义了一个 uintptr_t 类型的 bits 成员变量。存储了 class_rw_t 的地址。bits 中还定义了一些基本操作,例如获取 class_rw_t, raw isa 状态,是否 swift 等函数。objc_class 结构体中定义的一些函数,其内部都是通过 bits 实现的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct objc_class : objc_object {
// Class isa;
Class superclass;
cache_t cache;
class_data_bits_t bits;

class_rw_t *data() {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
// .....
}

class_ro_t 和 class_rw_t

和 class_data_bits_t 相关的有两个很重要的结构体,class_rw_t 和 class_ro_t,其中都定义着 method list, protocol list, property list 等关键信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct class_rw_t {
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_data_bits_t 指向的是一个 class_ro_t 的地址,这个结构体是不可变的(只读)。在运行时,才会通过 realizeClass 函数将 bits 指向 class_rw_t.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
uint32_t reserved;

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,在这个过程中,会把编译期存储在 bits 中的 class_ro_t 取出,然后创建 class_rw_t,并把 ro 赋值给 rw,成为 rw 的一个成员变量,最后把 rw 设置给 bits,代替之前 bits 中存储的 ro。除了这些操作外,还会有一些其他的赋值的操作。
下面是初始化 Class 的精简代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
static Class realizeClass(Class cls) 
{
const class_ro_t *ro;
class_rw_t *rw;
Class supercls;
Class metacls;
bool isMeta;

if (!cls) return nil;
if (cls->isRealized()) return cls;

ro = (const class_ro_t *)cls->data();
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
rw->ro = ro;
rw->flags = RW_REALIZED|RW_REALIZING;
cls->setData(rw);

isMeta = ro->flags & RO_META;
rw->version = isMeta ? 7 : 0;

supercls = realizeClass(remapClass(cls->superclass));
metacls = realizeClass(remapClass(cls->isa()))

cls->superclass = supercls;
cls->initClassisa(metacls);
cls->setInstanceSize(ro->instanceSize);

if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}

methodizeClass(cls);
return cls;
}

其中 addRootClass 和 addSubclass 函数,这两个函数的职责是将某个类的子类串成一个列表,大致是下面的链接顺序。由此,我们可以通过 class_rw_t,获取当前类的所有子类。

1
superClass.firstSubclass -> subClass1.nextSiblingClass -> subClass2.nextSiblingClass -> ...

初始化 rw 和 ro 之后,rw 的 method list,protocol list 都是空的,需要在下面 methodizeClass 函数中进行赋值。函数中会把 ro 的 list 都取出来,然后赋值给 rw,如果在运行时动态修改,也是对 rw 做的操作。所以 ro 中存储的是编译时就已经决定的原数据,rw 才是运行时动态修改的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
static void methodizeClass(Class cls)
{
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro;

method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
rw->methods.attachLists(&list, 1);
}

property_list_t *proplist = ro->baseProperties;
if (proplist) {
rw->properties.attachLists(&proplist, 1);
}

protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
rw->protocols.attachLists(&protolist, 1);
}

if (cls->isRootMetaclass()) {
// root metaclass
addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO);
}

// Attach categories.
category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
attachCategories(cls, cats, false /*don't flush caches*/);
}

例如:
创建一个类 LXZObject,继承自 NSObject,并为其加入一个 testMethod 方法。因为在编译后 objc_class 的 bits 对应的是 class_ro_t 结构体,所以打印一下结构体的成员变量,看一下编译后的 class_ro_t 是什么样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
struct class_ro_t {
flags = 128
instanceStart = 8
instanceSize = 8
reserved = 0
ivarLayout = 0x0000000000000000 <no value available>
name = 0x0000000100000f7a "LXZObject"
baseMethodList = 0x00000001000010c8
baseProtocols = 0x0000000000000000
ivars = 0x0000000000000000
weakIvarLayout = 0x0000000000000000 <no value available>
baseProperties = 0x0000000000000000
}

工作中的运用

动态添加属性

使用场景:分类是不能自定义属性和变量的,这时候可以使用 runtime 动态添加属性方法;
原理:给一个类声明属性,其实本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到类存空间。

1
2
3
4
5
6
7
8
9
10
11
/** 关联对象、set方法
id object:给哪个对象添加关联,给哪个对象设置属性
const void *key:关联的key,要求唯一,建议用char 可以节省字节
id value:关联的value,给属性设置的值
objc_AssociationPolicy policy:内存管理的策略
*/
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
// 获取关联的对象、get方法
id objc_getAssociatedObject(id object, const void *key)
// 移除关联的对象
void objc_removeAssociatedObjects(id object)

例子:实现一个 UIView 的 Category 添加自定义属性 defaultColor。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@interface UIView (Color)

@property (nonatomic, strong) UIColor *defaultColor;

@end

@implementation UIView (Color)

static char kDefaultColorKey;
- (void)setDefaultColor:(UIColor *)defaultColor
{
objc_setAssociatedObject(self, &kDefaultColorKey, defaultColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (id)defaultColor {
return objc_getAssociatedObject(self, &kDefaultColorKey);
}
@end

动态添加方法

使用场景:如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决。
主要面试题:有没有使用过 performSelector?

1
2
3
4
5
// 参数1:给哪个类添加方法
// 参数2:添加方法的方法编号SEL
// 参数3:添加方法的函数实现IMP(函数地址)
// 参数4:函数的类型,(返回值+参数类型)
class_addMethod(Class cls, SEL name, IMP imp, const char * types)

例子:
假如 Person 对象调用 eat 方法,而该方法并没有实现,则会报错。我们可以利用 Runtime 在 Person 类中动态添加 eat 方法,来实现该方法的调用。

1
[p performSelector:@selector(eat)];

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@implementation Person

/**
void的前面没有+、-号,因为只是C的代码;
必须有两个指定参数(id self,SEL _cmd)
*/
void eat(id self, SEL sel)
{
NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}

// 当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来.
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(eat)) {
//函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
class_addMethod(self, sel, (IMP)eat, "v@:");
}
return [super resolveInstanceMethod:sel];
}

@end

方法交换 Method Swizzling

使用场景:在没有一个类的实现源码的情况下,想改变其中一个方法的实现,除了继承它重写,和借助类别重名方法暴力抢先外,还有更灵活的方法 Method Swizzle。

1
2
// 交换方法地址,交换两个方法的实现
method_exchangeImplementations(Method m1, Method m2)

封装后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@implementation NSObject (Swizzling)

+ (void)methodSwizzlingWithOriginalSelector:(SEL)originalSelector bySwizzledSelector:(SEL)swizzledSelector
{
Class class = [self class];
//原有方法
Method originalMethod = class_getInstanceMethod(class, originalSelector);
//替换原有方法的新方法
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
//先尝试給源SEL添加IMP,这里是为了避免源SEL没有实现IMP的情况
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {//添加成功:说明源SEL没有实现IMP,将源SEL的IMP替换到交换SEL的IMP
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {//添加失败:说明源SEL已经有IMP,直接将两个SEL的IMP交换即可
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
@end

例子:例如我们想要替换 ViewController 生命周期方法,可以这样做

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@implementation UIViewController (Swizzling)

+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self methodSwizzlingWithOriginalSelector:@selector(viewWillAppear:) bySwizzledSelector:@selector(mj_viewWillAppear:)];
});
}

- (void)mj_viewWillAppear:(BOOL)animated{
[self mj_viewWillAppear:animated];

NSLog(@"被调用了");
}
@end

注意点:

  1. swizzling 建议在+load 中完成。+load 和 +initialize 是 Objective-C runtime 会自动调用两个类方法。+load 是在一个类被初始加载时调用,一定会被调用;+initialize 是在应用第一次调用该类的类方法或实例方法前调用,相当于懒加载方式,可能不被调用。此外 +load 方法还有一个非常重要的特性,那就是子类、父类和分类中的 +load 方法的实现是被区别对待的。换句话说在 Objective-C runtime 自动调用 +load 方法时,分类中的 +load 方法并不会对主类中的 +load 方法造成覆盖。
  2. swizzling 应该只在 dispatch_once 中完成,由于 swizzling 改变了全局的状态,所以我们需要确保在任何情况下(多线程环境,或者被其他人手动再次调用+load 方法)只交换一次,防止再次调用又将方法交换回来。+load 方法本身即为线程安全,为什么仍需添加 dispatch_once,其原因就在于+load 方法本身无法保证其中代码只被执行一次。

    NSCoding 自动归档解档

    场景:如果一个模型有许多个属性,实现自定义模型数据持久化时,需要对每个属性都实现一遍 encodeObject 和 decodeObjectForKey 方法,比较麻烦。我们可以使用 Runtime 来解决。
    原理:用 runtime 提供的函数遍历 Model 自身所有属性,并对属性进行 encode 和 decode 操作。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    #import "MJMusicModel.h"
    #import <objc/runtime.h>

    @implementation MJMusicModel

    // 设置不需要归解档的属性
    - (NSArray *)ignoredNames {
    return @[@"_musicUrl"];
    }

    // 归档调用方法
    - (void)encodeWithCoder:(NSCoder *)encoder
    {
    unsigned int count = 0;
    // 获得这个类的所有成员变量
    Ivar *ivars = class_copyIvarList([self class], &count);
    for (int i = 0; i<count; i++) {
    // 取出i位置对应的成员变量
    Ivar ivar = ivars[i];
    // 获得成员变量的名字
    const char *name = ivar_getName(ivar);
    // 将每个成员变量名转换为NSString对象类型
    NSString *key = [NSString stringWithUTF8String:name];
    // 忽略不需要归档的属性
    if ([[self ignoredNames] containsObject:key]) {
    continue;
    }
    // 归档
    id value = [self valueForKey:key];
    [encoder encodeObject:value forKey:key];
    }
    // 注意释放内存!
    free(ivars);
    }

    // 解档方法
    - (id)initWithCoder:(NSCoder *)decoder
    {
    if (self = [super init]) {
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([self class], &count);
    for (int i = 0; i<count; i++) {
    // 取出i位置对应的成员变量
    Ivar ivar = ivars[i];
    // 获得成员变量的名字
    const char *name = ivar_getName(ivar);
    // 将每个成员变量名转换为NSString对象类型
    NSString *key = [NSString stringWithUTF8String:name];
    // 忽略不需要解档的属性
    if ([[self ignoredNames] containsObject:key]) {
    continue;
    }
    // 解档
    id value = [decoder decodeObjectForKey:key];
    // 设置到成员变量身上
    [self setValue:value forKey:key];
    }
    free(ivars);
    }
    return self;
    }
    @end

面试题

objc 在向一个对象发送消息时,发生了什么

objc 在向一个对象发送消息时,runtime 会根据对象的 isa 指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,如果一直到根类还没找到,转向拦截调用,走消息转发机制,一旦找到 ,就去执行它的实现 IMP 。

objc 中向一个 nil 对象发送消息将会发生什么

如果向一个 nil 对象发送消息,首先在寻找对象的 isa 指针时就是 0 地址返回了,所以不会出现任何错误。也不会崩溃。

objc 中向一个对象发送消息[obj foo]和 objc_msgSend()函数之间有什么关系

在编译时,[obj foo] => objc_msgSend(obj, @selector(foo));。

什么时候会报 unrecognized selector 的异常

objc 在向一个对象发送消息时,runtime 库会根据对象的 isa 指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,如果,在最顶层的父类中依然找不到相应的方法时,会进入消息转发阶段,如果消息三次转发流程仍未实现,则程序在运行时会挂掉并抛出异常 unrecognized selector sent to XXX 。

能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量

不能向编译后得到的类中增加实例变量;
因为编译后的类已经注册在 runtime 中,类结构体中的 objc_ivar_list 实例变量的链表和 instance_size 实例变量的内存大小已经确定,同时 runtime 会调用 class_setvarlayout 或 class_setWeaklvarLayout 来处理 strong weak 引用.所以不能向存在的类中添加实例变量。
能向运行时创建的类中添加实例变量;
运行时创建的类是可以添加实例变量,调用 class_addIvar 函数. 但是必须在调用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上.

给类添加一个属性后,在类结构体里哪些元素会发生变化

instance_size :实例的内存大小;
objc_ivar_list *ivars:属性列表

[self class] 与 [super class]

1
2
3
4
5
6
7
8
9
10
11
@implementation Son : Father
- (id)init
{
self = [super init];
if (self) {
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end

输出 Son Son
self 是类的隐藏参数,指向当前调用方法的这个类的实例;
super 本质是一个编译器标示符,和 self 是指向的同一个消息接受者。不同点在于:super 会告诉编译器,当调用方法时,去调用父类的方法,而不是本类中的方法。
在 [self class] 时,runtime 会调用 objc_msgSend。而在 [super class] 时,runtime 会调用 objc_msgSendSuper 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )

/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained id receiver;

/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained Class class;
#else
__unsafe_unretained Class super_class;
#endif
/* super_class is the first class to search */
};

其中有两个参数 receiver 和 super_class。即在 objc_super 结构体指向 superClass 父类的方法列表中查找 selector,找到后以 objc -> receiver 去调用父类的这个 selector。注意调用者还是 objc -> receiver 而不是 super_class 。

runtime 如何通过 selector 找到对应的 IMP 地址

每一个类对象中都一个方法列表,方法列表中记录着方法的名称,方法实现,以及参数类型,其实 selector 本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现。

_objc_msgForward 函数是做什么的,直接调用它将会发生什么

_objc_msgForward 是 IMP 类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward 会尝试做消息转发。

runtime 如何实现 weak 变量的自动置 nil

  1. 初始化时:runtime 会调用 objc_initWeak 函数,初始化一个新的 weak 指针指向对象的地址。
  2. 添加引用时:objc_initWeak 函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
  3. 释放时,调用 clearDeallocating 函数。clearDeallocating 函数首先根据对象地址获取所有 weak 指针地址的数组,然后遍历这个数组把其中的数据设为 nil,最后把这个 entry 从 weak 表中删除,最后清理对象的记录。

    使用 runtime Associate 方法关联的对象,需要在主对象 dealloc 的时候释放么

    无论在 MRC 下还是 ARC 下均不需要,被关联的对象在生命周期内要比对象本身释放的晚很多,它们会在被 NSObject -dealloc 调用的 object_dispose()方法中释放。
CATALOG
  1. 1. 描述
  2. 2. 数据结构
    1. 2.1. 基础定义
      1. 2.1.1. objc_object
      2. 2.1.2. objc_class
      3. 2.1.3. meta class
      4. 2.1.4. method_t
      5. 2.1.5. IMP
      6. 2.1.6. property_t
    2. 2.2. NSObject
      1. 2.2.1. 之前的定义
      2. 2.2.2. 之后的定义
    3. 2.3. 对象结构体
      1. 2.3.1. objc_object
      2. 2.3.2. isa_t 定义
    4. 2.4. 类结构体
      1. 2.4.1. objc_class 结构体
      2. 2.4.2. class_ro_t 和 class_rw_t
  3. 3. 工作中的运用
    1. 3.1. 动态添加属性
    2. 3.2. 动态添加方法
    3. 3.3. 方法交换 Method Swizzling
    4. 3.4. NSCoding 自动归档解档
  4. 4. 面试题
    1. 4.1. objc 在向一个对象发送消息时,发生了什么
    2. 4.2. objc 中向一个 nil 对象发送消息将会发生什么
    3. 4.3. objc 中向一个对象发送消息[obj foo]和 objc_msgSend()函数之间有什么关系
    4. 4.4. 什么时候会报 unrecognized selector 的异常
    5. 4.5. 能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量
    6. 4.6. 给类添加一个属性后,在类结构体里哪些元素会发生变化
    7. 4.7. [self class] 与 [super class]
    8. 4.8. runtime 如何通过 selector 找到对应的 IMP 地址
    9. 4.9. _objc_msgForward 函数是做什么的,直接调用它将会发生什么
    10. 4.10. runtime 如何实现 weak 变量的自动置 nil
    11. 4.11. 使用 runtime Associate 方法关联的对象,需要在主对象 dealloc 的时候释放么