IOS 开发— Runtime 机制

IOS 开发— Runtime 机制

Runtime 底层原理与应用

Runtime 又叫运行时,是一套IOS底层的C语言API。Objective-C在运行时被转化为C语言的API。Objective-C时一门动态语言,在代码运行才进行处理和编译。因此需要一个运行时系统(Runtime System)来处理编译后的Code。

Runtime 基本是用C和汇编实现的,苹果和GNU各自维护一套Runtime版本,两个版本之间Apple也在努力保持一致。Runtime 源码下载

Runtime源码分析

源码中提供了Runtime的一些参数和接口和一些专业的术语,他们对应着的数据结构如下:

SEL

他是Objective-C 中selector方法选择器在 Runtime中的表示。PS:Objective-C在相同的类中不会有命名相同的两个方法,Selector对方法名进行包装方便找到对应的方法。

/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

映射到方法的名的字符串,通过Objective-C 编译器命令@selector()或者Runtime 系统的sel_registerName 函数来获取一个SEL类型的方法选择器。PS:不同类中的相同方法所对应的 SEL是相同的,由于变量的类型不同,所以回不导致方法实现混乱。

id

Id 是一个参数类型,指向某个类的实力指针:

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

/// A pointer to an instance of a class.
typedef struct objc_object *id;

可以根据objc_object结构体中的isa 指针来找到对象所属的类。PS:isa 指针在代码运行时并不总指向实例对象所属的类型,所以不能依靠它来确定类型,要想确定类型还是需要用对象的 -class 方法。KVO 的实现机制就是将被观察者对象的isa指针指向一个中间类而非一个真实的类型。

Calss

指向objc_class结构体指针。

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

可以看出objc_class中关联类 父类的指针、类名、成员变量、方法、缓存和协议。

objc_ivar_list 对应成员变量列表

// 成员变量列表
struct objc_ivar_list {
    int ivar_count                                           OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_ivar ivar_list[1]                            OBJC2_UNAVAILABLE;
}  

objc_method_list 方法列表

// 方法列表
struct objc_method_list {
    struct objc_method_list * _Nullable 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;
Method

Method 代表类中某个方法的类型

struct objc_method {
    SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;
    char * _Nullable method_types                            OBJC2_UNAVAILABLE;
    IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;
}    

objc_method 存储了方法名,方法类型和方法实现:

  • 方法名类型为 SEL
  • 方法类型 method_types 是个 char 指针,存储方法的参数类型和返回值类型
  • method_imp 指向了方法的实现,本质是一个函数指针
Ivar

Ivar 是表示成员变量的类型。

struct objc_ivar {
    char * _Nullable ivar_name                               OBJC2_UNAVAILABLE;
    char * _Nullable ivar_type                               OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}  

其中ivar——offset是基地址偏移字节。

IMP
/// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
#endif

它就是一个函数指针,这是由编译器生成的。当你发起一个 ObjC 消息之后,最终它会执行的那段代码,就是由这个函数指针指定的。而 IMP 这个函数指针就指向了这个方法的实现。

如果得到了执行某个实例某个方法的入口,我们就可以绕开消息传递阶段,直接执行方法,这在后面 Cache 中会提到。

你会发现 IMP 指向的方法与 objc_msgSend 函数类型相同,参数都包含 idSEL 类型。每个方法名都对应一个 SEL 类型的方法选择器,而每个实例对象中的 SEL 对应的方法实现肯定是唯一的,通过一组 idSEL 参数就能确定唯一的方法实现地址。

而一个确定的方法也只有唯一的一组 idSEL 参数。

Cache
typedef struct objc_cache *Cache                             OBJC2_UNAVAILABLE;

struct objc_cache {
    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
    Method _Nullable buckets[1]                              OBJC2_UNAVAILABLE;
};

Cache 为方法调用的性能进行优化,每当实例对象接收到一个消息时,它不会直接在 isa 指针指向的类的方法列表中遍历查找能够响应的方法,因为每次都要查找效率太低了,而是优先在 Cache 中查找。

Runtime 系统会把被调用的方法存到 Cache 中,如果一个方法被调用,那么它有可能今后还会被调用,下次查找的时候就会效率更高。就像计算机组成原理中 CPU 绕过主存先访问 Cache 一样。

Property
typedef struct objc_property *Property;
typedef struct objc_property *objc_property_t;//这个更常用
可以通过class_copyPropertyList 和 protocol_copyPropertyList 方法获取类和协议中的属性:

objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)

Ps: 返回的是属性列表,列表中每个元素都是一个 objc_property_t 指针。

@interface CPerson : NSObject
@property(strong,nonatomic) NSString *m_Name;
@property(assign,nonatomic) unsigned int n_Age;
@property(assign,nonatomic) double d_Weight;
@end


-(void)Runtime
{
    unsigned int outCount = 0;
    objc_property_t *properties = class_copyPropertyList([CPerson class], &outCount);
    NSLog(@"OutCount: %d",outCount);
    for (unsigned int i =0; i<outCount; i++)
    {
        NSString *name = @(property_getName(properties[i]));
        NSString *attributes = @(property_getAttributes(properties[i]));
        NSLog(@"%@ : ----- :%@",name,attributes);
    }
    NSLog(@"Class Name: %@",NSStringFromClass([super class]));
}

Log 输出结果:

2019-01-22 17:04:06 ObjcDemo[6758:322755] OutCount: 3
2019-01-22 17:04:06 ObjcDemo[6758:322755] m_Name : ----- :T@"NSString",&,N,V_m_Name
2019-01-22 17:04:06 ObjcDemo[6758:322755] n_Age : ----- :TI,N,V_n_Age
2019-01-22 17:04:06 ObjcDemo[6758:322755] d_Weight : ----- :Td,N,V_d_Weight
OC方法底层调用与探索

Objective-C在三个层面上与 Runtime 系统进行交互

1、通过Objective-C源代码。

2、通过Foundation框架的NSObject 类定义的方法。

3、通过对Runtime库函数直接调用

Objective-C 源代码

我们所编写的Objective-C代码,在运行时,系统会自动转化为运行时代码,在运行时确定运行数据和函数。

NSObject类定义的方法

我们所写的类大部分是NSObject 类的子类(NSProxy 类时个例外,它是个抽象超类)。

NSObject 的一些方法可以从Runtime 系统获取信息 例如:

-class 返回对象类。

-isKindOfClass-isMemberOfClass 检查对象是否存在指定类的继承体系中。

-respondsToSelector 检查能否响应指定的消息。

-conformsToProtocol 检查是否实现了指定协议的方法。

-methodForSelector 返回指定方法的实现地址。

Runtime库函数调用

Runtime 头文件位置/usr/include/objc 目录下面,因此我们可以直接引入objc/Runtime.h头文件来直接调用。

OC消息发送机制

苹果官方文档中的 messages aren’t bound to method implementations until Runtime。消息直到运行时才会与方法实现进行绑定。

objc_msgSend 方法看清来好像返回了数据,其实objc_msgSend 从不返回数据,而是你的方法在运行时实现被调用后才会返回数据。下面详细叙述消息发送的步骤(如下图):

流程:

1、先检查这个selector 是不是要忽略。比如Mac OS X开发,有了垃圾回收旧不会理会 retain、release等函数。

2、检测这个selector 的target 是不是nil,Objective-C允许对一个nil对象执行任何方法而不会Crash,因为运行时会被忽略掉。

3、如果上面两个都通过了,那么就开始检查这个类的实现IMP,先从cache里面查找,如果找到啦就会运行对应的函数区执行相应的代码。

4、如果cache找不到就找类的方法列表中是否有对应的方法。

5、如果类的方法中找不到就到父类的方法列表中去查找,一直到NSObject类为止。

6、如果找不到,就要开始进行动态方法解析

在消息传递的过程中,编译器会根据objc_msgSend、objc_msgSend_stret、objc_msgSendSuper、objc_msgSendSuper_stret这四个方法中一个调用,如果方法传递给父类,那么就会调用夫类的Super函数,如果消息返回值是结构体而不是简单的值时,会调用名称带有stret的函数。PS:方法中隐藏参数self(接收消息的对象)、方法选择器(SEL指针)。

获取方法地址

NSObject 类中有一个实例方法:methodForSelector,你可以用它来获取某个方法选择器对应的 IMP

OC动态方法解析

你可以动态提供一个方法实现。如果我们使用关键字 @dynamic 在类的实现文件中修饰一个属性,表明我们会为这个属性动态提供存取方法,编译器不会再默认为我们生成这个属性的 setter 和 getter 方法了,需要我们自己提供。

@dynamic propertyName;

当 Runtime 系统在 Cache 和类的方法列表(包括父类)中找不到要执行的方法时,Runtime 会调用 resolveInstanceMethod:resolveClassMethod: 来给我们一次动态添加方法实现的机会。我们需要用 class_addMethod 函数完成向特定类添加特定方法实现的操作:

void dynamicMethodIMP(id self, SEL _cmd) {
    // implementation ....
}
@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

上面的例子为 resolveThisMethodDynamically 方法添加了实现内容,就是 dynamicMethodIMP 方法中的代码。其中 "v@:" 表示返回值和参数,这个符号表示的含义见:Type Encoding

OC消息转发机制

重定向

消息转发机制执行前,Runtime 系统允许我们替换消息的接收者为其他对象。通过 - (id)forwardingTargetForSelector:(SEL)aSelector 方法。

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if(aSelector == @selector(mysteriousMethod:)){
        return alternateObject;
    }
    return [super forwardingTargetForSelector:aSelector];
}

如果此方法返回 nil 或者 self,则会计入消息转发机制(forwardInvocation:),否则将向返回的对象重新发送消息。

转发

当动态方法解析不做处理返回 NO 时,则会触发消息转发机制。这时 forwardInvocation: 方法会被执行,我们可以重写这个方法来自定义我们的转发逻辑:

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

唯一参数是个 NSInvocation 类型的对象,该对象封装了原始的消息和消息的参数。我们可以实现 forwardInvocation: 方法来对不能处理的消息做一些处理。也可以将消息转发给其他对象处理,而不抛出错误。PS: 在 forwardInvocation: 消息发送前,Runtime 系统会向对象发送methodSignatureForSelector: 消息,并取到返回的方法签名用于生成 NSInvocation 对象。所以重写 forwardInvocation: 的同时也要重写 methodSignatureForSelector: 方法,否则会抛异常。

Category
#import "UIImage+Image.h"
#include <objc/runtime.h>

@implementation UIImage (Image)

+(void)load
{
    Method imageNameMethod = class_getClassMethod(self, @selector(imageNamed:));
    Method xmg_ImageNameMethod = class_getClassMethod(self, @selector(xmg_ImageName:));
    method_exchangeImplementations(imageNameMethod, xmg_ImageNameMethod);
}

+(UIImage *)xmg_ImageName:(NSString *)name
{
    UIImage *image = [UIImage xmg_ImageName:name];
    if(image)
        NSLog(@"Success !");
    else
        NSLog(@"Faile  !");
    return image;
}
@end
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值