Runtime

Runtime

  Runtime即运行时,让Objective-C拥有了动态特性,对于实现这些特性的数据结构分析,一堆大神都已经分析得很透彻,本文就不多叙述了。

  objc_classobjc_objectobjc_selectorobjc_methodobjc_ivarobjc_property的一些在Runtime中的一些声明如下:

typedef struct objc_class *Class;
typedef struct objc_object *id;
typedef struct objc_selector *SEL;
typedef struct objc_method *Method;
typedef struct objc_ivar *Ivar;
typedef struct objc_property *objc_property_t;

备注:在runtime.h中,鉴于__OBJC2__这个宏,使得编译器clang编译代码后所有OBJC2_UNAVAILABLE的代码都不再存在,也就是废弃掉了,所以现在运行中的runtime的代码与开源出来的代码是有出入的,不过不妨碍对Runtime的理解

  本文内容不少,按照主题分为以下内容:

  1. 变量与属性
  2. 消息及其转发
  3. Method Swizzling
  4. Runtime在KVO中的实现
  5. 总结

1. 变量与属性

  在Objective-C中,变量与属性由两个结构题定义,分别是objc_ivarobjc_property,他们既有关联,也不相同。

  示例源码

1.1 objc_ivar

  objc_ivarruntime.h中声明如下:

/// An opaque type that represents an instance variable.
typedef struct objc_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
}                                                            OBJC2_UNAVAILABLE;

  可以看到,Ivar就是结构体objc_ivar,而objc_ivar内根据提供的API能证实有ivar_name ivar_type ivar_offset三个变量。

  另外,可以发现,Ivar就是变量的描述,包含名称、类型和内存偏移地址。

- (void)testIvar {
    
    Ivar publicIvar = class_getInstanceVariable([TestObject class], "_publicString");
    NSLog(@"public Ivar name: %s, type encoding: %s, offset: %ti", ivar_getName(publicIvar), ivar_getTypeEncoding(publicIvar), ivar_getOffset(publicIvar));
    
    unsigned int outCount = 0;
    Ivar *ivarList = class_copyIvarList([TestObject class], &outCount);
    for (int i = 0; i < outCount; i++) {
        Ivar ivar = ivarList[i];
        const char *name = ivar_getName(ivar);
        const char *typeEncoding = ivar_getTypeEncoding(ivar);
        long offset = ivar_getOffset(ivar);    // 可以注意到,这里并非强转,用的是long,而非int,也就是说objc_ivar结构体并非如所看到的一样。
        NSLog(@"ivar name: %s, type encoding: %s, offset: %ti", name, typeEncoding, offset);
    }
    free(ivarList);
    /*
     public Ivar name: _publicString, type encoding: @"NSString", offset: 8
     ivar name: _publicString, type encoding: @"NSString", offset: 8
     ivar name: _privateString, type encoding: @"NSString", offset: 16
     */
}

1.2 objc_property

  objc_propertyruntime.h中,声明如下:

typedef struct objc_property *objc_property_t;

  objc_property_t就是objc_property结构体,根据提供的API,可以猜测其内有nameattributename记录的是属性名,attribute记录的是按照一定格式排列的属性中已声明的特性的字符串(很拗口,其实就是Declared Properties).

  attribute显示的字符串格式如下:

T(以逗号分隔的@encode类型编码)V[属性名]

  @encode的类型编码有两种,分别为Objective-C中的类型编码和声明特性编码,分别如下:

Objective-C中的类型编码

CodeMeaning
cA char
iAn int
sA short
lA long l is treated as a 32-bit quantity on 64-bit programs.
qA long long
CAn unsigned char
IAn unsigned int
SAn unsigned short
LAn unsigned long
QAn unsigned long long
fA float
dA double
BA C++ bool or a C99 _Bool
vA 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
bnumA bit field of num bits
^typeA pointer to type
?An unknown type (among other things, this code is used for function pointers)

声明特性编码

CodeMeaning
RThe property is read-only (readonly).
CThe property is a copy of the value last assigned (copy).
&The property is a reference to the value last assigned (retain).
NThe property is non-atomic (nonatomic).
GThe property defines a custom getter selector name. The name follows the G (for example, GcustomGetter,).
SThe property defines a custom setter selector name. The name follows the S (for example, ScustomSetter:,).
DThe property is dynamic (@dynamic).
WThe property is a weak reference (__weak).
PThe property is eligible for garbage collection.
tSpecifies the type using old-style encoding.

  示例代码如下:

- (void)testProperty {
    
    unsigned int outCount = 0;
    objc_property_t *propertyList = class_copyPropertyList([TestObject class], &outCount);
    for (int i = 0; i < outCount; i++) {
        objc_property_t property = propertyList[i];
        const char *propertyName = property_getName(property);
        const char *propertyAttribute = property_getAttributes(property);
        NSLog(@"property name: %s, attribute: %s", propertyName, propertyAttribute);
        unsigned int attributeOutCount = 0;
        objc_property_attribute_t *attributeList = property_copyAttributeList(property, &attributeOutCount);
        for (int j = 0; j < attributeOutCount; j++) {
            objc_property_attribute_t attribute = attributeList[j];
            NSLog(@"attribute name: %s, value: %s", attribute.name, attribute.value);
            
        }
        free(attributeList);
    }
    free(propertyList);
    /*
     property name: privateString, attribute: T@"NSString",&,V_privateString
     attribute name: T, value: @"NSString"
     attribute name: &, value:
     attribute name: V, value: _privateString
     property name: publicString, attribute: T@"NSString",&,V_publicString
     attribute name: T, value: @"NSString"
     attribute name: &, value:
     attribute name: V, value: _publicString
     */
}

1.3 关联

  objc_ivarobjc_property两者,有着不同的分工。

  在Objective-C中,定义一个属性用到了@property编译器指令。

  @property在默认情况下就是创建ivar + getter + setter,即成员变量(也就是1.1中的Ivar)及其访问器。

  而@property在此之外还需要标示出属性是“只读”还是“可写可读”,是“强引用”还是“弱引用”等特性,objc_property就记录了这方面的信息。

2. 消息及其分发

  示例源码

  Objective-C中的消息是在运行时才与方法实现进行绑定的,,编译器通过转换方法的实现为一个发送信息函数的调用。

  即:[receiver message] -> objc_msgSend(receiver, selector, ...)

  在运行过程中,一个消息的发送会经历如下过程:

1) 找到方法的实现

2) 传递参数并调用

3) 传递实现方法的返回值

2.1 消息的发送

2.1.1 消息发送机制

  消息发送的关键在于编译器为每个类和对象构建的结构。每个类包含2种基础元素:

  • 一个指向父类的指针
  • 一个类分发表:表中记录了方法选择器(SEL)及其实现地址(IMP)的入口

  当一个对象(在objc/objc.h中定义的struct objc_object)被创建(即分配了内存,初始化成员变量)后,对象通过isa(读伊萨)指针指向它的类,并且通过类,可以访问类中实现的方法及集成的类的方法。

  在项目中,一般不允许直接调用objc_msgSend等函数(在objc/message.h中声明),可以通过clang把Objective-C代码编译成C++代码进行观察,也可以在Build Settings->Enable Strict Checking of objc_msgSend Calls中设置为No,再在代码中调用。

  在消息发送中的消息接收者,有两个需要注意的参数,一个是消息接收对象(id),一个是方法选择器(SEL)。它们是隐式的,在编译时,会被插入到objc_msgSend函数中,充当参数。对于这里的理解,有一个很特别的代码例子,如下

//
//  MessagingObject.m
//  Message
//
//  Created by lzackx on 2018/3/15.
//  Copyright © 2018年 lzackx. All rights reserved.
//

#import "MessagingObject.h"

@implementation MessagingObject

- (instancetype)init
{
    self = [super init];
    if (self) {
        
        NSLog(@"self class: %@, super class: %@", [self class], [super class]);
        // self class: MessagingObject, super class: MessagingObject
    }
    return self;
}

@end

  可以看到打印的结果无论是self还是super,都是同一个类,理解了消息发送的原理就能明白,其实消息发送的接收对象都是self,只是super调用的是objc_msgSendSuper函数而已。

  继承了NSObjectNSProxy的类因具有运行时特性,其内的所有方法都是动态绑定的,在某些情况下,如果想直接调用,而不是通过运行时的消息发送来执行,那么可以通过使用methodForSelector:方法获得消息接收者某方法的SEL(方法选择器)对应的IMP(方法实现地址),直接执行函数,绕过动态绑定。示例代码如下:

//
//  MessagingObject.m
//  Message
//
//  Created by lzackx on 2018/3/15.
//  Copyright © 2018年 lzackx. All rights reserved.
//

#import "MessagingObject.h"

@implementation MessagingObject

// 省略部分代码

- (void)methodWithParameter:(NSString *)parameter {
    
    NSLog(@"Parameter: %@", parameter);
}

@end
//
//  ViewController.m
//  Message
//
//  Created by lzackx on 2018/3/15.
//  Copyright © 2018年 lzackx. All rights reserved.
//

#import "ViewController.h"
#import <objc/message.h>
#import "MessagingObject.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    MessagingObject *messagingObject = [[MessagingObject alloc] init];
    [self messagingTest];
    
    
}

- (void)messagingTest {
    
    void (*directMethod)(id, SEL, NSString *) = (void (*)(id, SEL, NSString *))[messagingObject methodForSelector:@selector(methodWithParameter:)];
    directMethod(messagingObject, @selector(methodWithParameter:), @"directly invoke");
    // Parameter: directly invoke
}


@end

  通过clang编译成C++后的相关代码如下:

static void _I_ViewController_messagingTest(ViewController * self, SEL _cmd) {

    MessagingObject *messagingObject = ((MessagingObject *(*)(id, SEL))(void *)objc_msgSend)((id)((MessagingObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MessagingObject"), sel_registerName("alloc")), sel_registerName("init"));

    void (*directMethod)(id, SEL, NSString *) = (void (*)(id, SEL, NSString *))((IMP (*)(id, SEL, SEL))(void *)objc_msgSend)((id)messagingObject, sel_registerName("methodForSelector:"), sel_registerName("methodWithParameter:"));
    directMethod(messagingObject, sel_registerName("methodWithParameter:"), (NSString *)&__NSConstantStringImpl__var_folders_xr_qkw3kpr13vlb7w3ht11gp_4c0000gn_T_ViewController_796ded_mi_0);

}

  可以看到directMethod是直接调用的,没有通过objc_msgSend来调用。这种调用方式也适用于开发柯理化函数(function currying)。

2.1.2 动态添加方法

  既然方法是动态绑定的,那么就肯定可以动态添加方法。代码如下:

//
//  ViewController.m
//  Message
//
//  Created by lzackx on 2018/3/15.
//  Copyright © 2018年 lzackx. All rights reserved.
//

#import "ViewController.h"
#import <objc/message.h>
#import "MessagingObject.h"

@interface ViewController ()

@property MessagingObject *messagingObject;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    self.messagingObject = [[MessagingObject alloc] init];
    [self dynamicMethodTest];
    
}

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
/*
 因为MessagingObject.h中,没有dynamicMethodIMP的声明,所以会warnings。看着烦,可以这么去掉。
 */

- (void)dynamicMethodTest {
    
    objc_msgSend(self.messagingObject, @selector(dynamicMethodIMP));
    // dynamicMethodIMP called
}
#pragma clang diagnostic pop

@end
//
//  MessagingObject.m
//  Message
//
//  Created by lzackx on 2018/3/15.
//  Copyright © 2018年 lzackx. All rights reserved.
//

#import "MessagingObject.h"
#import <objc/message.h>

@implementation MessagingObject

// - MARK: dynamic method
void dynamicMethodIMP(id self, SEL _cmd) {
    
    NSLog(@"dynamicMethodIMP called");
}

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(dynamicMethodIMP)) {
        class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
#pragma clang diagnostic pop

@end

  首先看ViewController.m中的- (void)dynamicMethodTest实现,是直接用objc_msgSend的,当然也可以用performSelector:,原因是在MessagingObject.h中并没有对将要调用的方法进行声明。

  然后看MessagingObject.m中的+ (BOOL)resolveInstanceMethod:(SEL)sel实现,它是用来查找SEL对应的实现的,所以可以在找的时候,通过class_addMethod函数添加一个想要与SEL对应的函数,这个函数如所有的Objective-C方法的底层实现一样,是个自带消息接收者和方法选择器参数的函数。

  class_addMethod函数的前三个参数都很好理解,最后一个参数,是用来指定函数的相关类型的,是一个C字符串,字符串内按照如下格式填写1.2中提到过的类型编码:

[返回值类型编码][第一个参数类型编码][第二个参数类型编码][...]

  由于Objective-C中的方法都必须有idSEL参数,所以基本上所有的方法的类型编码中都包含@:这两个类型编码。

  最后需要了解的是,class_addMethod这是个添加方法的函数,但不会替换掉已经存在的方法,并且+ (BOOL)resolveInstanceMethod:(SEL)sel只会在找不到方法时才调用,所以在第一次动态添加方法后,下一次的向方法发消息时,已经能找到方法了,就不会再进入+ (BOOL)resolveInstanceMethod:(SEL)sel

2.2 消息的转发

  正常情况下,往方法发送消息时,如果方法不存在,那么就会崩溃报错,这种情况下消息的转发机制,就提供了规避错误的机会。消息转发的实现代码如下:

//
//  ViewController.m
//  Message
//
//  Created by lzackx on 2018/3/15.
//  Copyright © 2018年 lzackx. All rights reserved.
//

#import "ViewController.h"
#import <objc/message.h>
#import "MessagingObject.h"

@interface ViewController ()

@property MessagingObject *messagingObject;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    self.messagingObject = [[MessagingObject alloc] init];
    [self messageForwardingTest];
    
}

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
/*
 因为MessagingObject.h中,没有surrogateMethod的声明,所以会warnings。看着烦,可以这么去掉。
 */
- (void)messageForwardingTest {
    
    [self.messagingObject performSelector:@selector(surrogateMethod)];
    // surrogateMethod called
}
#pragma clang diagnostic pop

@end
//
//  MessagingObject.m
//  Message
//
//  Created by lzackx on 2018/3/15.
//  Copyright © 2018年 lzackx. All rights reserved.
//

#import "MessagingObject.h"
#import <objc/message.h>
#import "SurrogateObject.h"


@interface MessagingObject ()

@property SurrogateObject *surrogateObject;

@end

@implementation MessagingObject

- (instancetype)init
{
    self = [super init];
    if (self) {
        
        _surrogateObject = [SurrogateObject new];
    }
    return self;
}

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
+ (BOOL)resolveInstanceMethod:(SEL)sel {	// 1 & 4
    
    return [super resolveInstanceMethod:sel];
}
#pragma clang diagnostic pop

// - MARK: massaging forwarding
- (id)forwardingTargetForSelector:(SEL)aSelector {	// 2
    
    return [super forwardingTargetForSelector:aSelector];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {	// 3
    
    NSMethodSignature *methodSignature = [super methodSignatureForSelector: aSelector];
    if (!methodSignature) {
        methodSignature = [self.surrogateObject methodSignatureForSelector:aSelector];
    }
    return methodSignature;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {	// 5
    
    if ([self respondsToSelector: [anInvocation selector]]) {
        [anInvocation invokeWithTarget:self];
    } else if ([self.surrogateObject respondsToSelector:[anInvocation selector]]) {
        [anInvocation invokeWithTarget:self.surrogateObject];
    } else {
        [super forwardInvocation:anInvocation];
    }
    
}

@end
//
//  SurrogateObject.h
//  Message
//
//  Created by lzackx on 2018/3/15.
//  Copyright © 2018年 lzackx. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface SurrogateObject : NSObject

- (void)surrogateMethod;

@end
//
//  SurrogateObject.m
//  Message
//
//  Created by lzackx on 2018/3/15.
//  Copyright © 2018年 lzackx. All rights reserved.
//

#import "SurrogateObject.h"

@implementation SurrogateObject

- (void)surrogateMethod {
    
    NSLog(@"surrogateMethod called");
}

@end

  消息转发的实现为了方便理解,多增加了一个类,使消息转发让类在一种在单继承规则下拥有多继承功能的用途(注意,并不是真正意义上的多继承,所以反射API的结果不会被影响)。

  消息的转发过程:objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)->+ (BOOL)resolveInstanceMethod:(SEL)sel->- (id)forwardingTargetForSelector:(SEL)aSelector->- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector->+ (BOOL)resolveInstanceMethod:(SEL)sel ->- (void)forwardInvocation:(NSInvocation *)anInvocation->- (void)invokeWithTarget:(id)target

1) 调用消息发送函数objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)

2) 因找不到方法,所以第一次触发+ (BOOL)resolveInstanceMethod:(SEL)sel

3) 在父类中+ (BOOL)resolveInstanceMethod:(SEL)sel还找不到方法,就触发- (id)forwardingTargetForSelector:(SEL)aSelector

4) 若父类的消息转发还没找到方法,触发- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector ,希望获得方法签名NSMethodSignature对象

5) 当方法签名非nil时,第二次触发+ (BOOL)resolveInstanceMethod:(SEL)sel

6) 这次有了NSMethodSignature对象,就可以构成NSInvocation的对象,并触发- (void)forwardInvocation:(NSInvocation *)anInvocation

7) 在- (void)forwardInvocation:(NSInvocation *)anInvocation中通过- (void)invokeWithTarget:(id)target调用被转发的方法,至此完成整个转发流程。

3. Method Swizzling

  示例源码

  Method Swizzling通常被用于需要Hook(钩子)的场景,又或者是以AOP(Aspect Oriented Programming,面向切面编程)思想进行开发。

  前面提及过,类结构体中的其中一个元素是一个类分发列表,列表中的一个结构体objc_method内记录着方法选择器(SEL)及其实现地址(IMP)的数据。而运行时使方法的实现与方法选择器在运行中才进行绑定,所以在进行动态绑定前,就有机会改变SEL和IMP的对应关系,而这种改变,就是Method Swizzling。虽然已经被OBJC2_UNAVAILABLE;标记,但也可以做个参考,如下:

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

  使用Method Swizzling的例子如下:

//
//  AppDelegate.m
//  MethodSwizzling
//
//  Created by lzackx on 2018/3/15.
//  Copyright © 2018年 lzackx. All rights reserved.
//

#import "AppDelegate.h"
#import <objc/runtime.h>
#import "SwizzlingMethodObject.h"
#import "ViewController.h"
#import "UIViewController+MethodSwizzling.h"

@interface AppDelegate ()

@property SwizzlingMethodObject *smObject;

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    [self initMethodSwizzling];
    return YES;
}

- (void)initMethodSwizzling {
    
    // 原方法
    Method original_viewDidLoad = class_getInstanceMethod([UIViewController class], @selector(viewDidLoad));
    IMP original_viewDidLoad_imp = method_getImplementation(original_viewDidLoad);
    
    // 目标方法
    Method method_swizzling_viewDidLoad = class_getInstanceMethod([UIViewController class], @selector(ms_viewDidLoad));
    IMP ms_viewDidLoad_imp = method_getImplementation(method_swizzling_viewDidLoad);
    
    self.smObject = [[SwizzlingMethodObject alloc] initWithOriginalClass:[UIViewController class]
                                                        OriginalSelector:@selector(viewDidLoad)
                                                             OriginalIMP:original_viewDidLoad_imp
                                                                 SMClass:[UIViewController class]
                                                              SMSelector:@selector(ms_viewDidLoad)
                                                                   SMIMP:ms_viewDidLoad_imp];
    
    [self.smObject swizzle];
}


@end
//
//  ViewController.m
//  MethodSwizzling
//
//  Created by lzackx on 2018/3/15.
//  Copyright © 2018年 lzackx. All rights reserved.
//

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"original viewDidLoad");
}

@end
//
//  UIViewController+MethodSwizzling.m
//  MethodSwizzling
//
//  Created by lzackx on 2018/3/15.
//  Copyright © 2018年 lzackx. All rights reserved.
//

#import "UIViewController+MethodSwizzling.h"
#import <objc/runtime.h>

@implementation UIViewController (MethodSwizzling)

- (void)ms_viewDidLoad {
    NSLog(@"Method Swizzling viewDidLoad");
}

@end
//
//  SwizzlingMethodObject.m
//  MethodSwizzling
//
//  Created by lzackx on 2018/3/15.
//  Copyright © 2018年 lzackx. All rights reserved.
//

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

@interface SwizzlingMethodObject ()

@property (nonatomic, assign) Class original_class;
@property (nonatomic, assign) SEL original_selector;
@property (nonatomic, assign) IMP original_imp;
@property (nonatomic, assign) Class sm_class;
@property (nonatomic, assign) SEL sm_selector;
@property (nonatomic, assign) IMP sm_imp;

@end

@implementation SwizzlingMethodObject

- (instancetype)initWithOriginalClass:(Class)original_class
                     OriginalSelector:(SEL)original_selector
                          OriginalIMP:(IMP)original_imp
                              SMClass:(Class)sm_class
                           SMSelector:(SEL)sm_selector
                                SMIMP:(IMP)sm_imp
{
    self = [super init];
    if (self) {
        _original_class = original_class;
        _original_selector = original_selector;
        _original_imp = original_imp;
        _sm_class = sm_class;
        _sm_selector = sm_selector;
        _sm_imp = sm_imp;
    }
    return self;
}

- (void)swizzle {
    
    Method originalMethod = class_getInstanceMethod(self.original_class, self.original_selector);
    /*
     Method smMethod = class_getInstanceMethod(self.sm_class, self.sm_selector);
     method_exchangeImplementations(originalMethod, smMethod);
     // method_exchangeImplementations这个函数就等于下边两个函数的结合,也就是两个方法互换IMP
     method_setImplementation(originalMethod, self.sm_imp);
     method_setImplementation(smMethod, self.original_imp);
     */
    method_setImplementation(originalMethod, self.sm_imp);
}

@end

  打印结果如下:

Method Swizzling viewDidLoad
original viewDidLoad

  可以看到,Method Swizzling后的方法打印也出现了,证明IMP替换成功了。

  至于为什么连原方法的打印也出现了呢?那是因为viewDidLoadUIViewController中生命周期的其中一个方法,在ViewController中被重写了,重写内容中又往super发消息,所以会同时出现两个打印。如果是对自定义的方法进行Method Swizzling那么肯定就只是被替换后的方法被执行了。当然,可以直接注释掉viewDidLoad方法,因子类找不到这个方法,所以isa往父类找这个方法,然后找到了已被替换掉的IMP并执行,那么这种情况下看到的打印就只有第一条了。

4. Runtime在KVO中的实现

  示例源码

  KVO(Key-Value Observing)其实就是利用Runtime来实现的,实现代码如下:

//
//  ViewController.m
//  RuntimeKVO
//
//  Created by lzackx on 2018/3/15.
//  Copyright © 2018年 lzackx. All rights reserved.
//

#import "ViewController.h"
#import "NSObject+KVOImplementation.h"

@interface ViewController ()

@property int i;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.i = 0;
//    [self addObserver:self forKeyPath:@"i" options:NSKeyValueObservingOptionNew context:nil];
    [self rt_addObserver:self forKeyPath:@"i" options:NSKeyValueObservingOptionNew context:nil];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    NSLog(@"Object: %@", object);
    NSLog(@"keyPath: %@", keyPath);
    NSLog(@"change: %@", change);
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    NSLog(@"%i touched", self.i);
    self.i++;
    
}

- (void)dealloc
{
    [self removeObserver:self forKeyPath:@"i"];
}

@end
//
//  NSObject+KVOImplementation.m
//  RuntimeKVO
//
//  Created by lzackx on 2018/3/15.
//  Copyright © 2018年 lzackx. All rights reserved.
//

#import "NSObject+KVOImplementation.h"
#import <objc/message.h>
#import <objc/runtime.h>


@implementation NSObject (KVOImplementation)

static const void *kvo_observer = "kvo_observer";
static const void *kvo_key_path = "kvo_key_path";
static const void *kvo_options = "kvo_options";

- (void)rt_addObserver:(NSObject *)observer
            forKeyPath:(NSString *)keyPath
               options:(NSKeyValueObservingOptions)options
               context:(void *)context {
    
    NSString *originalClassName = NSStringFromClass([self class]);
    NSString *kvoClassName = [NSString stringWithFormat:@"kvo_%@", originalClassName];
    
    
    Class class = objc_allocateClassPair([self class], kvoClassName.UTF8String, 0);
    id value = [observer valueForKeyPath:keyPath];
    /*
     按照例子 只列出了int类型,其他基础类型就不列出来了,原理相同
     需要注意class_addMethod函数中的第一个参数该传递什么值,因为在原类中,是往setter方法发消息,所以为了能被调用,需要正确名称的方法选择器,
     又因为是把self对象设置为了其子类,所以这里是直接添加Setter方法(也就是重写了),而不是利用Method Swizzling把原类的Setter的IMP替换掉。
     */
    char type[4] = "v@:";
    if (strcmp([(NSNumber *)value objCType], @encode(int)) == 0) {
        type[3] = *[(NSNumber *)value objCType];
        class_addMethod(class, NSSelectorFromString([NSString stringWithFormat:@"set%@:", [keyPath capitalizedString]]), (IMP)setInt, type);
    } else {
        type[3] = *[value objCType];
        class_addMethod(class, NSSelectorFromString([NSString stringWithFormat:@"set%@:", [keyPath capitalizedString]]), (IMP)setObject, type);
    }
    objc_registerClassPair(class);
    object_setClass(self, class);
    // 由于category不能新增存储属性,所以用runtime的方式保存好相关信息,便于在observeValueForKeyPath:ofObject:change:context:用上。(实际的实现当然是不需要这样的)
    objc_setAssociatedObject(self, kvo_observer, observer, OBJC_ASSOCIATION_ASSIGN);
    objc_setAssociatedObject(self, kvo_key_path, keyPath, OBJC_ASSOCIATION_ASSIGN);
    objc_setAssociatedObject(self, kvo_options, [NSNumber numberWithUnsignedLong:options], OBJC_ASSOCIATION_ASSIGN);
}

void setInt(id self, SEL _cmd, int i) {
    
    struct objc_super super_struct = {self,class_getSuperclass([self class])};
    objc_msgSendSuper(&super_struct, _cmd, i);
    // 拿回需要用上的信息
    id observer = objc_getAssociatedObject(self, kvo_observer);
    NSString *keyPath = objc_getAssociatedObject(self, kvo_key_path);
    NSUInteger options = [objc_getAssociatedObject(self, kvo_options) unsignedLongValue];
    
    NSMutableDictionary *change = [NSMutableDictionary dictionary];
    // NSKeyValueChangeKindKey的值因为这里的示例只有set,所以也就只把set的类别写死,自定义KVO时,可以根据情况自定义添加更多的信息
    [change addEntriesFromDictionary:@{NSKeyValueChangeKindKey: [NSNumber numberWithInt:NSKeyValueChangeSetting]}];
    // options是以位来记录值的,只列出新值的,原理相同,其他的就不列出来了
    if (options & NSKeyValueObservingOptionNew) {
        [change addEntriesFromDictionary:@{NSKeyValueChangeNewKey: [NSNumber numberWithInt:i]}];
    }
    if (change.count > 0) {
        objc_msgSend(observer,
                     @selector(observeValueForKeyPath:ofObject:change:context:),
                     keyPath,
                     self,
                     change,
                     nil);
    }
}

void setObject(id self, SEL _cmd, id object) {
    
    struct objc_super super_struct = {self,class_getSuperclass([self class])};
    objc_msgSendSuper(&super_struct, _cmd, object);
    // 拿回需要用上的信息
    id observer = objc_getAssociatedObject(self, kvo_observer);
    NSString *keyPath = objc_getAssociatedObject(self, kvo_key_path);
    NSUInteger options = [objc_getAssociatedObject(self, kvo_options) unsignedLongValue];
    NSMutableDictionary *change = [NSMutableDictionary dictionary];
    // NSKeyValueChangeKindKey的值因为这里的示例只有set,所以也就只把set的类别写死,自定义KVO时,可以根据情况自定义添加更多的信息
    [change addEntriesFromDictionary:@{NSKeyValueChangeKindKey: [NSNumber numberWithInt:NSKeyValueChangeSetting]}];
    // options是以位来记录值的,只列出新值的,原理相同,其他的就不列出来了
    if (options & NSKeyValueObservingOptionNew) {
        [change addEntriesFromDictionary:@{NSKeyValueChangeNewKey: object}];
    }
    if (change.count > 0) {
        objc_msgSend(observer,
                     @selector(observeValueForKeyPath:ofObject:change:context:),
                     keyPath,
                     self,
                     change,
                     nil);
    }
}

@end

  KVO的实现,其实就是在添加观察者的时候,把消息接收者的类设置为其子类,并重写set方法,基于子类可以调用父类的方法以及KVC的实现机制,最后将消息发送回一个特定的方法中,从而达到观察的效果。代码中尽量做到了还原,尽量做到代码复用,但一些较为相同的写法,列了例子后就不再一一列出了,比较关键的地方也用注释进行了解释。


lZackx © 2022. All rights reserved.

Powered by Hydejack v9.1.6