什么是Runtime

  • OC语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理。运行时系统就像一个操作系统一样:它让所有的工作可以正常的运行。这个运行时系统即Objc Runtime。Objc Runtime基本上是用C和汇编写的,这个库使得C语言有了面向对象的能力。
  • Runtime其实有两个版本: “modern” 和 “legacy”。我们现在用的 Objective-C 2.0 采用的是现行 (Modern) 版的 Runtime 系统,只能运行在 iOS 和 macOS 10.5 之后的 64 位程序中。而 macOS 较老的32位程序仍采用 Objective-C 1 中的(早期)Legacy 版本的 Runtime 系统。这两个版本最大的区别在于当你更改一个类的实例变量的布局时,在早期版本中你需要重新编译它的子类,而现行版就不需要。
  1. runtime(简称运行时),是一套纯C(C和汇编)编写的API。而OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消息机制 objc_msgSend
  2. 对于 C 语言,函数的调用在编译的时候会决定调用哪个函数。
  3. 运行时机制原理:OC的函数调用称为消息发送,属于动态调用过程。在编译的时候 并不能决定真正调用哪个函数,只有在真 正运行的时候才会根据函数的名称找到对应的函数来调用。Runtime将数据类型的确定由编译时推迟到了运行时。
  4. 事实证明:在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错,只有当运行的时候才会报错,这是因为OC是运行时动态调用的。而C语言调用未实现的函数就会报错。

Runtime的组成

//实例对象
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY; //实例对象的isa指针指向他的类对象
};
//类对象
struct objc_class {
	Class isa 		 OBJC_ISA_AVAILABILITY;//isa指针指向Meta Class
#if !__OBJC2__
	Class super_class		 OBJC2_UNAVAILABLE; // 父类
	const char *name 		 OBJC2_UNAVAILABLE; // 类名
	long version 			 OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
	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; 
	// 方法缓存,对象接到一个消息会根据isa指针查找消息对象,这时会在method Lists中遍历,如果cache了,常用的方法调用时就能够提高调用的效率。
	struct objc_protocol_list *protocols 		OBJC2_UNAVAILABLE; // 协议链表
#endif
  } 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;
}                                                            OBJC2_UNAVAILABLE;

//方法
struct objc_method {
    SEL method_name				OBJC2_UNAVAILABLE;//方法名
    char *method_types			OBJC2_UNAVAILABLE;//方法类型
	IMP method_imp 				OBJC2_UNAVAILABLE;//指向方法实现的指针
}

//Category
struct category_t { 
    const char *name; 	//是指 class_name 而不是 category_name。
    classref_t cls;  	//要扩展的类对象,编译期间是不会定义的,而是在Runtime阶段通过name对 应到对应的类对象.
    struct method_list_t *instanceMethods; 	//category中所有给类添加的实例方法的列表。
    struct method_list_t *classMethods;		//category中所有添加的类方法的列表。
    struct protocol_list_t *protocols;		//category实现的所有协议的列表。
    struct property_list_t *instanceProperties;  //表示Category里所有的properties,这就是我们可以通过objc_setAssociatedObject和objc_getAssociatedObject增加实例变量的原因
};



ios什么是runtime runtime ios_iOS


Runtime消息传递

  1. 实例对象调用方法后,底层调用[objc performSelector:@selector(SEL)];方法,编译器将代码转化为objc_msgSend(receiver, selector)
  2. objc_msgSend函数中,首先通过objcisa指针找到objc对应的class,在class中先去cache中通过SEL查找对应函数的 method,如果找到则通过 method中的函数指针跳转到对应的函数中去执行。
  3. 如果在cacha中未找到,再去methodList中查找,如果能找到,则将method加入到cache中,以方便下次查找,并通过method中的函数指针跳转到对应的函数中去执行。
  4. 如果在methodlist中未找到,则去superClass中去查找,如果能找到,则将method加入到cache中,以方便下次查找,并通过method中的函数指针跳转到对应的函数中去执行。


ios什么是runtime runtime ios_iOS_02


  1. objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,即:objc_msgSend(receiver, selector)。如果,在最顶层的父类中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX 。但是在这之前,objc的运行时会给出三次拯救程序崩溃的机会:
  2. objc运行时会调用+resolveInstanceMethod:或者 +resolveClassMethod:(实例方法和类方法),让你有机会提供一个函数实现。如果你添加了函数,那运行时系统就会重新启动一次消息发送的过程,否则 ,运行时就会移到下一步,消息转发(Message Forwarding)。
  3. 如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。 只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会继续Normal Fowarding。 这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但下一步转发会创建一个NSInvocation对象,所以相对更快点。
  4. 这一步是Runtime最后一次给你挽救的机会。首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。

一个完整转发的例子:

#import "ViewController.h"
#import "objc/runtime.h"

@interface Person: NSObject

@end

@implementation Person

- (void)foo {
    NSLog(@"Doing foo");//Person的foo函数
}

@end

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //执行foo函数
    [self performSelector:@selector(foo)];
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return YES;//返回YES,进入下一步转发
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    return nil;//返回nil,进入下一步转发
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if ([NSStringFromSelector(aSelector) isEqualToString:@"foo"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];//签名,进入forwardInvocation
    }
    
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL sel = anInvocation.selector;

    Person *p = [Person new];
    if([p respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:p];
    }
    else {
        [self doesNotRecognizeSelector:sel];
    }

}

@end

Objective-C type encodings

  Objective-C type encodings

Code

Meaning

c

A char

i

An int

s

A short

l

A long

l is treated as a 32-bit quantity on 64-bit programs.

q

A long long

C

An unsigned char

I

An unsigned int

S

An unsigned short

L

An unsigned long

Q

An unsigned long long

f

A float

d

A double

B

A C++ bool or a C99 _Bool

v

A void

*

A character string (char *)

@

An object (whether statically typed or typed id)

#

A class object (Class)

:

A method selector (SEL)

[array type]

An array

{name=type...}

A structure

(name=type...)

A union

bnum

A bit field of num bits

^type

A pointer to type

?

An unknown type (among other things, this code is used for function pointers)