RuntimeiOS

iOS - Objective-C Runtime

2021-03-30  本文已影响0人  ienos

Objective-C 的 Runtime 是一个运行时库(Runtime Library),为 C 添加了面向对象的能力并创造了 Objective-C。这就是说它在类信息中被加载,完成所有的方法分发,方法转发等等。Objective-C runtime 创建了所有需要的结构体,让 Objective-C 的面相对象编程变为可能


参考链接

一、 Introduction

动态 & 静态语言

NSObject 类如何与 Runtime 系统进行交互?Runtim 如何动态加载新类?如何转发消息到其他对象?

Legacy And Modern Version

二、 Messaging

在 Objective-C 中直到运行时才绑定方法实现,编译器将消息表达式转换为 objc_msgSend 方法调用

[receiver message]

有参方法 & 无参方法

objc_msgSend(receiver, selector)
objc_msgSend(receiver, selector, arg1, arg2, ...)

编译器通过编译将类和对象转换为结构体,每个结构体都有两个基本要素

SEL 与 IMP 的关系非常类似于 HashTable 中 key 与 value 的关系。OC 中不支持函数重载的原因就是因为一个类的方法列表中不能存在两个相同的 SEL 。但是多个方法却可以在不同的类中有一个相同的 SEL,不同类的实例对象执行相同的 SEL 时,会在各自的方法列表中去根据 SEL 去寻找自己对应的 IMP,这使得 OC 可以支持函数重写

每个对象的第一个变量是 isa 指向它的类结构体,isa 提供对象访问的类和所有它继承的类

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
image.png

>> 消息发送过程

如果用实例对象调用实例方法,会到实例的 isa 指针指向的对象(也就是类对象)操作
如果调用的是类方法,就会到类对象的 isa 指针指向的对象(也就是元类对象)中操作

_cmd 引用当前 selector,self 为当前接收消息的对象

- strange
{
    id  target = getTheReceiver();
    SEL method = getTheMethod();
 
    if ( target == self || method == _cmd )
        return nil;
    return [target performSelector:method];
}

Getting a Method Address

methodForSelector: 可以直接获取函数地址,规避消息传递带来的多余开销

void (*setter)(id, SEL, BOOL);
int i;
 
setter = (void (*)(id, SEL, BOOL))[target
    methodForSelector:@selector(setFilled:)];
for ( i = 0 ; i < 1000 ; i++ )
    setter(targetList[i], @selector(setFilled:), YES);

>> 消息缓存

三、 Dynamic Method Resolution

>> 动态属性方法实现使用 @dynamic 修饰;@dynamic propertyName;

>> 动态解析方法

动态提供实例方法和类方法的方法实现

eg: 动态添加方法

@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
    if (aSEL == @selector(resolveThisMethodDynamically)) {
          class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
          return YES;
    }
    return [super resolveInstanceMethod:aSEL];
}
@end

>> Dynamic Loading

Objective-C 在运行的时候会连接新的类和扩展

四、 Message Forwarding

当接收消息对象不能处理,在系统报错之前,会进行消息转发

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    if ([someOtherObject respondsToSelector:
            [anInvocation selector]]) {
        [anInvocation invokeWithTarget:someOtherObject];
    } else {
        [super forwardInvocation:anInvocation];
    }
}

通过 forwardInvocation: 消息转发的消息返回值会被转发到被转发对象

如果被转发对象没有相应方法才会调用 forwardInvocation:

关于消息转发可以查看 NSInvocation 类详细说明

>> Forwarding and Multiple Inheritance

Message Forwarding 提供了多继承的大部分功能

>> Forwarding and Inheritance

respondsToSelector:isKindOfClass: 只会走继承树,不会走转发流程

Warrior & Diplomat

Warrior 转发消息给 Diplomat,但是 [aWarrior respondsToSelector:@selector(negotiate)] 将返回 NO

如果希望通过转发来实现继承的行为,需要重写 respondsToSelector:isKindOfClass:

- (BOOL)respondsToSelector:(SEL)aSelector
{
    if ( [super respondsToSelector:aSelector] )
        return YES;
    else {
        /* Here, test whether the aSelector message can     *
         * be forwarded to another object and whether that  *
         * object can respond to it. Return YES if it can.  */
    }
    return NO;
}

如果对象接收到远程消息,需要重写 methodSignatureForSelector: 方法,返回最终响应转发消息方法的准确描述

- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
    NSMethodSignature* signature = [super methodSignatureForSelector:selector];
    if (!signature) {
       signature = [surrogate methodSignatureForSelector:selector];
    }
    return signature;
}

五、Runtime tutorial

bulidSetting -> 搜 objc 设置为 NO // 用于使用 objc_msgSend(receiver, selector);

Runtime 库函数在 usr/include/objc 目录下,我们主要关注是这两个头文件:

#import <objc/runtime.h>
#import <objc/objc.h>

>> 1. 获取类的一些信息(包括属性列表,方法列表,成员变量列表,和遵循的协议列表)

unsigned int count;

//获取属性列表
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i = 0; i < count; i++) {
    const char *propertyname =  property_getName(propertyList[i]);        
    NSLog(@"property----="">%@", [NSString stringWithUTF8String:propertyname]);
}

//获取方法列表
Method *methodList = class_copyMethodList([self class], &count);
for (unsigned int i; i<count; i++) {
    Method method = methodList[i];
    NSLog(@"method----="">%@", NSStringFromSelector(method_getName(method)));
}

//获取成员变量列表
Ivar *ivarList = class_copyIvarList([self class], &count);
for (unsigned int i; i<count; i++) {
    Ivar myivar =  ivarList[i];
    const char *ivarname = ivar_getName(myivar);
    NSLog(@"ivar----="">%@", [NSString stringWithUTF8String:ivarname]);
}

//获取协议列表
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);

for (unsigned int i; i<count; i++) {
    Protocol *myprotocal = protocolList[i];
    const char *protocolname = protocol_getName(myprotocal);        
    NSLog(@"protocol----="">%@", [NSString stringWithUTF8String:protocolname]);
}

>> 2. 遍历对象的属性

比如,看看 zhangsan 的有哪些属性(身高:180、年龄:18)

>> 3. 动态添加/修改属性,动态添加/修改/替换方法

比如,修改 zhangsan 的身高为 190、年龄为 20,替换 walkTheDog 方法(变成 walkTheBigDog),给他添加一个新方法 walkTheCat 等等

>> 4. 动态创建类/对象/协议等等

比如,创建一个新的对象:lisi

>> 5. 方法拦截调用

比如,给 zhangsan 发送一个 walkTheDog 消息,但是 zhangsan 不知道怎么 walk 啊(没实现该方法),那我们可以拦截下,给该方法动态添加一个实现,甚至可以讲该方法定向或者打包给 lisi(其他对象),让 lisi 来 walk

六、Message Forwarding Process

>> 拦截调用

在找不到调用的方法程序崩溃之前,你有机会通过重写 NSObject 的四个方法来处理

>> 关于父类

七、Associated

// objc_AssociationPolicy 关联策略,有以下几种策略:
enum {
    OBJC_ASSOCIATION_ASSIGN = 0,
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
    OBJC_ASSOCIATION_RETAIN = 01401,
    OBJC_ASSOCIATION_COPY = 01403
};

>> 使用 Selector 作为关联对象唯一的 key

//设置关联对象
- (void)setGifDelgeate:(id<GifImageViewDelegate>)gifDelgeate {
    objc_setAssociatedObject(self, @selector(gifDelgeate), gifDelgeate, OBJC_ASSOCIATION_ASSIGN); //获取关联对象
}

//获取关联对象
- (id<GifImageViewDelegate>)gifDelgeate {
    // 这里面我们把 getAssociatedObject 方法的地址作为唯一的 key,_cmd 代表当前调用方法的地址
    // return objc_getAssociatedObject(self, _cmd);
    return objc_getAssociatedObject(self, @selector(gifDelgeate));
}

>> 定义一个全局变量用它的地址,作为关联对象唯一的 key

static char associatedObjectKey;

objc_setAssociatedObject(target, &associatedObjectKey, @"添加的字符串属性", OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
NSString *string = objc_getAssociatedObject(target, &associatedObjectKey);
NSLog(@"AssociatedObject = %@", string);

八、Structure

Structure Description
Method 成员方法
Ivar 成员属性
Category 分类
objc_property_t 属性
class_copyIvarList 拷贝出一个对象的所有成员列表
class_copyMethodLIst 拷贝出一个对象的所有成员方法列表
message: 两个函数
objc_msgSend 给某个对象发送消息
objc_msgSendSuper 给某个对象的父类发送消息
上一篇下一篇

猜你喜欢

热点阅读