Runtime

RunTime运行时,最主要的就是消息机制,根据函数的名称找到对应的函数来调用。

有什么用?
runtime是属于OC的底层, 可以进行一些非常底层的操作。

1> 发送消息(objc_msgSend)
2> 交换方法(method_exchangeImplementations)
3> 动态创建一个类(比如KVO的底层实现)  
4> 动态添加方法,(performSelector)
5> 给分类添加属性。(其实本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到类存空间) objc_setAssociatedObject与objc_getAssociatedObject
6> 字典转模型(利用runtime,遍历模型中所有属性,根据模型的属性名,去字典中查找key,取出对应的值,给模型的属性赋值。)
6> 可以把消息转发给想要的对象,或者随意交换一个方法的实现。

objc的文件夹中的源码文件,看完就能明白runtime和对象消息转发机制。

Objective-C的元素

你创建的每个类(派生形式NSObject / NSObject子类)将在堆上分配并将返回一个指针。
先看objc.h文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.**Class是一个指向objc_class结构体的指针**
//我们所说的类
typedef struct objc_class *Class;

/// Represents an instance of a class.isa是一个指向objc_class结构体的指针
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.id是一个指向objc_object结构体的指针
//我们所说的对象
typedef struct objc_object *id;
#endif

再看runtime.h文件中关于objc_class的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef struct objc_class *Class;
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; // metaclass
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0,可以通过runtime函数class_setVersion或者class_getVersion进行修改、读取
long info OBJC2_UNAVAILABLE; // 类信息,供运行时期使用的一些位标识,如CLS_CLASS (0x1L) 表示该类为普通 class,其中包含实例方法和变量;CLS_META (0x2L) 表示该类为 metaclass,其中包含类方法;
long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小(包括从父类继承下来的实例变量)
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量地址列表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法地址列表,与 info 的一些标志位有关,如CLS_CLASS (0x1L),则存储实例方法,如CLS_META (0x2L),则存储类方法;
struct objc_cache *cache OBJC2_UNAVAILABLE; // 缓存最近使用的方法地址,用于提升效率;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 存储该类声明遵守的协议的列表
#endif
}
/* Use `Class` instead of `struct objc_class *` */

由此可以看到,对象(id)就是一个isa指针,而类(class)中不仅有isa指针,还有其他成员变量。类也可以当做一个objc_object来对待,也就是说类和对象都是对象,分别称作类对象(class object)和实例对象(instance object也就是中的isa),这样我们就可以区别对象和类了。

再仔细看其中成员变量的含义:

1. isa

id中的isa指针指向的类结构称为class(也就是该对象所属的类)其中存放着普通成员变量与动态方法(“-”开头的方法)。
而class中的isa指针指向的类结构称为metaclass,其中存放着static类型的成员变量与static类型的方法(“+”开头的方法)。

2. super_class:

指向该类的父类的指针,如果该类是根类(如NSObject或NSProxy),那么super_class就为nil。

类与对象的继承层次关系如图(图片源自网络):

所有的class中isa指针都是指向根metaclass,而根metaclass则指向自身。根metaclass是通过继承根类产生的,与根class结构体成员一致,不同的是根metaclass的isa指针指向自身。

3. SEL:

typedef struct objc_selector *SEL;SEL是selector在Objective-C中的表示类型。selector可以理解为区别方法的ID。

4. IMP:

是“implementation”的缩写,它是由编译器生成的方法实现的一个函数指针。当你发起一个消息后(下文介绍),这个函数指针决定了最终执行哪段代码。(obj.h)
一个函数是由一个selector(SEL),和一个implement(IML)组成的;Selector相当于门牌号,而Implement才是真正的住户(函数实现)。理解Selector和Implementation的关系蛮重要的!

1
2
3
4
5
6
/// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id (*IMP)(id, SEL, ...);
#endif

5. Method:

Method代表类中的某个方法的类型。(runtime.h中)

typedef struct objc_method *Method;
objc_method的定义如下:

1
2
3
4
5
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE; // 方法名
char *method_types OBJC2_UNAVAILABLE; // 方法类型
IMP method_imp OBJC2_UNAVAILABLE; // 方法实现
}

6. Ivar:

代表类中实例变量的类型typedef struct objc_ivar *Ivar;
objc_ivar的定义如下:

1
2
3
4
5
6
7
8
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
}

7. Category :

代表一个分类typedef struct objc_category *Category;
objc_category的定义如下:

1
2
3
4
5
6
7
struct objc_category {
char *category_name OBJC2_UNAVAILABLE;
char *class_name OBJC2_UNAVAILABLE;
struct objc_method_list *instance_methods OBJC2_UNAVAILABLE;
struct objc_method_list *class_methods OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;

在运行时,类(包括class和metaclass)的objc_class结构体是固定的,不可能往这个结构体中添加数据,只能修改!譬如可以修改isa指针,让它指向一个中间类;在我的理解里,应该也可以修改ivars和methodLists,让它们指向一个新的区域;若可以这样,那么就可以在运行时随意添加/修改/删除成员变量和方法了。
但是,貌似Objective-C Runtime没有提供修改ivars和methodLists指针值的接口。
也因此,ivars在运行时指向的是一个固定区域,当然可以修改这个区域的值了,但这其实只是修改成员变量值而已;「在这个内存区域后面续上一段空余区域用于存放新的成员变量」?呵呵,想多了吧!因此,我们没办法在运行时为对象添加成员变量,这解释了为什么category中不能定义property(dynamic property不算);
P.S: 那为什么protocol中可以添加变量,在我的理解里,protocol是在编译器处理的。所以objc_class中有一个变量叫protocols;
和ivars不同,methodLists是一个二维数组。虽然我们没办法扩展methodLists指向的内存区域,但是我们可以改变这个内存区域(这个内存区域存储的都是指针)的值。因此,我们可以在运行时动态添加(以及做其他的处理,譬如交换等)方法!

8. objc_property_t:

objc_property_t是属性,它可以动态的为已存在的类添加新的方法。它的定义如下:

typedef struct objc_property *objc_property_t;
objc_property是内置的类型,与之关联的还有一个objc_property_attribute_t,它是属性的attribute,也就是其实是对属性的详细描述,包括属性名称、属性编码类型、原子类型/非原子类型等。它的定义如下:

1
2
3
4
typedef struct {
const char *name; // 名称
const char *value; // 值(通常是空的)
} objc_property_attribute_t;

9. Cache

Catch的定义如下:

typedef struct objc_cache *Cache
objc_cache的定义如下:

struct objc_cache {
unsigned int mask OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};
mask: 指定分配cache buckets的总数。在方法查找中,Runtime使用这个字段确定数组的索引位置。
occupied: 实际占用cache buckets的总数。
buckets: 指定Method数据结构指针的数组。这个数组可能包含不超过mask+1个元素。需要注意的是,指针可能是NULL,表示这个缓存bucket没有被占用,另外被占用的bucket可能是不连续的。这个数组可能会随着时间而增长。
objc_msgSend(下文讲解)每调用一次方法后,就会把该方法缓存到cache列表中,下次的时候,就直接优先从cache列表中寻找,如果cache没有,才从methodLists中查找方法。

10.Module

Module定义如下:
typedef struct objc_module *Module OBJC2_UNAVAILABLE;
objc_module定义如下:

1
2
3
4
5
6
struct objc_module {
unsigned long version OBJC2_UNAVAILABLE;
unsigned long size OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
Symtab symtab OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;

Objective-C的消息传递

消息发送(Messaging)是 Runtime 通过 selector 快速查找 IMP 的过程,有了函数指针就可以执行对应的方法实现;

消息机制原理:对象根据方法编号SEL去映射表查找对应的方法实现

消息转发(Message Forwarding)是在查找 IMP 失败后执行一系列转发流程的慢速通道,如果不作转发处理,则会打日志和抛出异常。

objc_msgSend的作用

在面向对象编程中,对象调用方法叫做发送消息。在编译时,程序的源代码就会从对象发送消息转换成Runtime的objc_msgSend函数调用。
例如某实例变量receiver实现某一个方法oneMethod

[receiver oneMethod];
Runtime会将其转成类似这样的代码

objc_msgSend(receiver, selector);
具体会转换成什么代码呢?

objc_msgSend:普通的消息都会通过该函数发送
objc_msgSend_stret:消息中有数据结构作为返回值(不是简单值)时,通过此函数发送和接收返回值
objc_msgSendSuper:和objc_msgSend类似,这里把消息发送给父类的实例
objc_msgSendSuper_stret:和objc_msgSend_stret类似,这里把消息发送给父类的实例并接收返回值。

objc_msgSend函数的调用过程:

  1. 检测这个 selector 是不是要忽略的。比如 Mac OS X 开发,有了垃圾回收就不理会 retain, release 这些函数了。
  2. 检测这个 target 是不是 nil 对象。ObjC 的特性是允许对一个 nil 对象执行任何一个方法不会 Crash,因为会被忽略掉。
  3. 如果上面两个都过了,那就开始查找这个类的 IMP,先从 cache 里面找,完了找得到就跳到对应的函数去执行。
  4. 如果 cache 找不到就找一下方法分发表。
  5. 如果分发表找不到就到超类的分发表去找,一直找,直到找到NSObject类为止。
  6. 如果还找不到就要开始进入动态方法解析了,后面会提到。
    PS:这里说的分发表其实就是Class中的方法列表,它将方法选择器和方法实现地址联系起来。

举 objc_msgSend(obj, foo) 这个例子来说:

首先,通过 obj 的 isa 指针找到它的 class ;
在 class 的 method list 找 foo ;
如果 class 中没到 foo,继续往它的 superclass 中找 ;
一旦找到 foo 这个函数,就去执行它的实现IMP .

每个消息都需要遍历一次 objc_method_list 并不合理。如果把经常被调用的函数缓存下来,那可以大大提高函数查询的效率。这也就是 objc_class 中另一个重要成员 objc_cache 做的事情 - 再找到 foo 之后,把 foo 的 method_name 作为 key ,method_imp 作为 value 给存起来。当再次收到 foo 消息的时候,可以直接在 cache 里找到,避免去遍历 objc_method_list.

第11条:理解objc_msgSend的作用

消息有名称(name)或选择子(selector).可以接受参数,而且可能有返回值。方法在objc文件中的message.h。
OC的消息机制
[someObject messageName: parameter]
id returnValue = objc_msgSend(someObject,@selector(messageName:),parameter);

objc_msgSend会根据接收者和选择子的类型来调用适当的方法, 流程如下

查找接收者的所属类的cache列表, 如果没有则下一步
查找接收者所属类的"方法列表"
如果能找到与选择子名称相符的方法, 就跳至其实现代码
找不到, 就沿着继承体系继续向上查找
如果能找到与选择子名称相符的方法, 就跳至其实现代码
找不到, 执行"消息转发".

另一些函数:
objc_msgSend_stret:待发送的消息要返回结构体
objc_msgSend_fpret:消息返回的是浮点数
objc_msgSendSuper:要给超类发送消息

动态方法解析和转发

动态解析流程图:

  1. 对象在收到无法解读的消息后,首先将调用其所属类的下列方法:(NSObject.h中)
1
2
3
4
//尚未实现的方法是类方法
+ (BOOL)resolveClassMethod:(SEL)sel
//尚未实现的方法是实例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel

方法决定是否动态添加方法。如果返回Yes则通过class_addMethod动态添加方法,消息得到处理,结束;如果返回No,则进入下一步;

  1. 这步会进入forwardingTargetForSelector:方法,
    - (id)forwardingTargetForSelector:(SEL)aSelector用于指定备选对象响应这个selector,不能指定为self。如果返回某个对象则会调用对象的方法,结束。如果返回nil,则进入第三部;

  2. 这步我们要通过methodSignatureForSelector:方法签名,如果返回nil,则消息无法处理。如果返回methodSignature,则进入下一步;
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

  3. 这步调用forwardInvocation:方法,我们可以通过anInvocation对象做很多处理,比如修改实现方法,修改响应对象等,如果方法调用成功,则结束。如果失败,则进入doesNotRecognizeSelector方法,若我们没有实现这个方法,那么就会crash。
    - (void)forwardInvocation:(NSInvocation *)anInvocation

第12条:消息转发机制

消息转发分为两大阶段:第一阶段:征询接受者,动态方法解析。
第二阶段:完整的消息转发机制,请接收者看看有没有其他对象能处理这条消息,若有,则把消息转给那个对象。若没有,则启动完整的消息转发机制,会把消息有关的细节全都封装到NSInvocation中去。

  1. 动态方法解析:对象收到无法解读的消息后,调用类方法:
    +(BOOL)resolveInstanceMethod:(SEL)selector
    要访问CoreData框架中的NSManagedObjects对象时,用resolveInstanceMethod来实现@dynamic属性。
  2. 备援接收者:能不能把这条消息传给其他接收者来处理
    -(id)forwardingTargetForSelector:(SEL)selctor;
  3. 完整的消息转发:首先,创建NSInvocation对象,把选择子,目标(target)及参数都封装于其中,在触发NSInvocation对象时,“消息派发系统”(message-dispatch system)将亲自出马,把消息指派给目标对象。
    -(void)forwardInvocation:(NSInvocation *)invocation;
  4. 以完整的例子演示动态方法解析
    由开发者来添加属性定义,并将其声明为@dynamic,而类则会自动处理相关属性值的存放于获取操作。

第14条:理解“类对象”的用意

  1. Objective-c对象的本质:都是指向某块内存数据的指针,所以声明变量时,类型后面要加“”字符。`NSString point= @“string”;id类型本身就是指针。可以改写为id point= @“string”;`
    描述objective-c对象所用的数据结构定义在runtime.h文件中,对于id,Class,还有is a 指针,涉及到runtime的机制.
  2. 在类继承体系中查询类型信息
    “isMemberOfClass”:能够判断出对象是否为某个特定类的实例。
    “isKindOfClass”:能够判断出对象是否为某类或某派生类的实例。
    通过使用isa指针获取对象所属的类,然后通过super_class指针在继承体系中游走。

    每个实例都有一个指向Class对象的指针,用以表明其类型,而这些Class对象则构成了类的继承体系。
    如果对象类型无法在编译期确定,那么就应该使用类型信息查询方法来探知。
    尽量使用类型信息查询方法来确定对象类型,而不要直接比较类对象,因为某些对象可能实现了消息转发功能。
    

应用

isa swizzling 的应用

KVO的实现

利用runtime动态产生一个类。
PS:isa指针不总是指向实例对象所属的类,不能依靠它来确定类型,而是应该用class方法来确定实例对象的类。因为KVO的实现机理就是将被观察对象的isa指针指向一个中间类而不是真实的类,这是一种叫做 isa-swizzling 的技术,详见官方文档

据此,我们可以手动实现一个KVO。http://tech.glowing.com/cn/implement-kvo/

Objective-C中的KVC和KVOhttp://yulingtianxia.com/blog/2014/05/12/objective-czhong-de-kvche-kvo/

Method Swizzling 应用

Objective-C 提供了以下 API 来动态替换类方法或实例方法的实现:
class_replaceMethod 替换类方法的定义
method_exchangeImplementations 交换 2 个方法的实现
method_setImplementation 设置 1 个方法的实现

使用 method swizlling 来修改原有的方法时, 就是在分类 load 中实现的.不在 initialize 方法中改变方法实现的原因是 initialize 可能会被子类所继承并重新执行最终导致无限递归, 而 load 并不会被继承.

第13条:用method swizzling调试黑盒方法

method swizzling:与给定的选择子名称相对应得方法也可以在运行期改变。(不需要源码,也不需要通过继承子类来覆写方法就能改变这个类本身的功能)可用在本类的所有实例中。使用另一份实现来替换原有的方法实现
IMP指针:id(*IMP)(id,SEL,….)类的方法列表会吧选择子的名称映射到相关方法实现之上,使得“动态消息派发系统”能够据此找到应该调用的方法。这些方法均以函数指针的形式来表示。
互换两个方法的实现:

  1. 互换两个已经写好方法实现:(两个参数为待交换的两个方法实现)
    void method_exchangeImplementations(Method m1,Method m2)
  2. 获得方法实现:
    Method class_getInstanceMethod(Class aClass,SEL aselector)

用途:

- 来为既有的方法添加新功能,新方法添加在NSString的一个“分类”中,在方法中实现所需的附加功能,并调用原有的实现。
- 为完全不知道其具体实现的黑盒方法增加日志记录,有助于程序调试。不宜乱用

给分类添加属性

Category实现的原理,同样解释了Category不能添加属性的原因
在objc_class结构体中:ivars是objc_ivar_list指针;methodLists是指向objc_method_list指针的指针。也就是说可以动态修改*methodLists的值来添加成员方法,

最新版的 Runtime 源码对这一块的描述已经有很大变化,可以参考下美团技术团队的深入理解Objective-C:Categoryhttp://tech.meituan.com/DiveIntoCategory.html

PS:任性的话可以在Category中添加@dynamic的属性,并利用运行期动态提供存取方法或干脆动态转发;或者干脆使用关联度对象(AssociatedObject)

因为对象在内存中的排布可以看成一个结构体,该结构体的大小并不能动态变化。所以无法在运行时动态给对象增加成员变量
相对的,对象的方法定义都保存在类的可变区域中。Objective-C 2.0 并未在头文件中将实现暴露出来,但在 Objective-C 1.0 中,我们可以看到方法的定义列表是一个名为 methodLists的指针的指针。通过修改该指针指向的指针的值,就可以实现动态地为某一个类增加成员方法。这也是Category实现的原理。同时也说明了为什么Category只可为对象增加成员方法,却不能增加成员变量。
iOS 关联引用为分类添加属性:
主要代码:(runtime.h)

1
2
void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)  
id objc_getAssociatedObject(id object, void *key)

字典转模型

要将JSON转换为Model啦!
利用runtime遍历模型对象的所有属性,根据属性名从字典中取出对应的值,设置到模型的属性上。

利用runtime遍历模型对象的所有属性来归档和解档



面试题

  1. 讲讲Runtime,以及消息转发过程;

  2. 讲讲runtime,以及hook,以及如何进行方法交换;

  3. 详解runtime,OC为什么是动态的语言,内部机制;
    在OC中,如果向某对象传递消息,那就会使用动态绑定机制来决定需要调用的方法。在底层,所有方法都是普通的C语言函数,然而对象收到消息之后,究竟该调用哪个方法则完全在runtime决定,甚至可以在程序运行时改变,这些特性使得Objective-C成为一门真正的动态语言。

  4. runtime 的运行机制?

  5. KVO如何实现,内部机制;
    http://zhangbuhuai.com/understanding-kvo/

  6. kvo的底层实现?

  7. 如何去手动触发KVO,如何让KVO去监听一个方法;

  1. [※※]objc中向一个nil对象发送消息将会发生什么?
    在 Objective-C 中向 nil 发送消息是完全有效的——只是在运行时不会有任何作用。

为啥可以向nil发送消息而不崩溃?OC的函数调用都是通过objc_msgSend进行消息发送来实现的,相对于C和C++来说,对于空指针的操作会引起Crash的问题,而objc_msgSend会通过判断self来决定是否发送消息,如果self为nil,那么selector也会为空,直接返回,所以不会出现问题。视方法返回值,向nil发消息可能会返回nil(返回值为对象)、0(返回值为一些基础数据类型)或0X0(返回值为id)等。但是对[NSNull null]对象发送消息时,是会crash的,因为这个NSNull类只有一个null方法。 这个是个概念,一定要熟记。

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

objc是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector)。

  1. [※※※]什么时候会报unrecognized selector的异常?
    objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,如果,在最顶层的父类中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX 。但是在这之前,objc的运行时会给出三次拯救程序崩溃的机会:
    见上图。
  1. [※※※※]一个objc对象如何进行内存布局?(考虑有父类的情况)
  • 所有父类的成员变量和自己的成员变量都会存放在该对象所对应的存储空间中.
  • 每一个对象内部都有一个isa指针,指向他的类对象,类对象中存放着本对象的对象方法列表成员变量的列表,属性列表。它内部也有一个isa指针指向元对象(meta class),元对象内部存放的是类方法列表,类对象内部还有一个superclass的指针,指向他的父类对象。
  • 根对象就是NSObject,它的superclass指针指向nil。
  • 类对象既然称为对象,那它也是一个实例。类对象中也有一个isa指针指向它的元类(meta class),即类对象是元类的实例。元类内部存放的是类方法列表,根元类的isa指针指向自己,superclass指针指向NSObject类。
  1. [※※※※]一个objc对象的isa的指针指向什么?有什么作用?

    isa是对象中的隐藏指针,指向创建这个对象的类。
    对象会顺着内部的isa指针找到存储于类中的方法并执行。
    
  2. [※※※※]runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)
    selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现.
  3. [※※※※]使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?

    无论在MRC下还是ARC下均不需要。

  4. [※※※※※]objc中的类方法和实例方法有什么本质区别和联系?

  5. [※※※※※]_objc_msgForward函数是做什么的,直接调用它将会发生什么?

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

首先在 Class 中的缓存查找 IMP (没缓存则初始化缓存),如果没找到,则向父类的 Class 查找。如果一直查找到根类仍旧没有实现,则用_objc_msgForward函数指针代替 IMP 。最后,执行这个 IMP 。
_objc_msgForward在进行消息转发的过程中会涉及以下这几个方法:

resolveInstanceMethod:方法 (或 resolveClassMethod:)。
forwardingTargetForSelector:方法
methodSignatureForSelector:方法
forwardInvocation:方法
doesNotRecognizeSelector: 方法

直接调用调用_objc_msgForward,将跳过查找 IMP 的过程,直接触发“消息转发”,

如果调用了_objc_msgForward,即使这个对象确实已经实现了这个方法,你也会告诉objc_msgSend:“我没有在这个对象里找到这个方法的实现”

哪些场景需要直接调用_objc_msgForward?最常见的场景是:你想获取某方法所对应的NSInvocation对象。举例说明:

  • JSPatch (Github 链接)就是直接调用_objc_msgForward来实现其核心功能的:JSPatch 以小巧的体积做到了让JS调用/替换任意OC方法,让iOS APP具备热更新的能力。
    作者的博文《JSPatch实现原理详解》详细记录了实现原理,有兴趣可以看下。
  • RAC(ReactiveCocoa) 源码中也用到了该方法。
  1. [※※※※※]runtime如何实现weak变量的自动置nil?

    runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。

  2. [※※※※※]能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?

  • 不能向编译后得到的类中增加实例变量;
  • 能向运行时创建的类中添加实例变量;
  1. 下面的代码分别输出什么?
1
2
3
4
5
6
7
8
9
10
@implementation Son : Father
- (id)init {
self = [super init];
if (self) {
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end

答案:都输出”Son”
解释:objc中super是编译器标示符,并不像self一样是一个对象,遇到向super发的方法时会转译成objc_msgSendSuper(…),而参数中的对象还是self,于是从父类开始沿继承链寻找 - class这个方法,最后在NSObject中找到(若无override),此时,[self class]和[super class]已经等价了。

参考:
Objective-C 消息发送与转发机制原理
http://yulingtianxia.com/blog/2016/06/15/Objective-C-Message-Sending-and-Forwarding/

Objective-C Runtime 1小时入门教程:
https://www.ianisme.com/ios/2019.html

Objective-C对象模型及应用http://blog.devtang.com/2013/10/15/objective-c-object-model/

Method Swizzling 和 AOP 实践http://tech.glowing.com/cn/method-swizzling-aop/

Method Swizzling
http://nshipster.com/method-swizzling/

手Runtime(附MJExtension的底层实现)
http://www.devstore.cn/essay/essayInfo/3270.html?utm_source=tuicool&utm_medium=referral

iOS面试题sunnyhttp://blog.sunnyxx.com/2015/07/04/ios-interview/
答案

补充:苹果开源了runtime的实现,在网站
https://opensource.apple.com/source/objc4/
中可以找到各个版本的runtime源码。
但提供的是一个个文件,不方便打包下载,网站
https://opensource.apple.com/tarballs/objc4/中提供了压缩包的下载。

文章目录
  1. 1. Objective-C的元素
    1. 1.1. 1. isa:
      1. 1.1.1. 2. super_class:
      2. 1.1.2. 3. SEL:
      3. 1.1.3. 4. IMP:
      4. 1.1.4. 5. Method:
      5. 1.1.5. 6. Ivar:
      6. 1.1.6. 7. Category :
      7. 1.1.7. 8. objc_property_t:
      8. 1.1.8. 9. Cache
      9. 1.1.9. 10.Module
  2. 2. Objective-C的消息传递
    1. 2.1. objc_msgSend的作用
      1. 2.1.1. 第11条:理解objc_msgSend的作用
    2. 2.2. 动态方法解析和转发
      1. 2.2.1. 第12条:消息转发机制
      2. 2.2.2. 第14条:理解“类对象”的用意
  3. 3. 应用
    1. 3.1. isa swizzling 的应用
      1. 3.1.1. KVO的实现
    2. 3.2. Method Swizzling 应用
      1. 3.2.1. 第13条:用method swizzling调试黑盒方法
    3. 3.3. 给分类添加属性
    4. 3.4. 字典转模型
    5. 3.5. 利用runtime遍历模型对象的所有属性来归档和解档
  4. 4. 面试题
本站总访问量 本站访客数人次 ,