iOS runtime原理以及KVO实现

2019-04-01  本文已影响0人  开心的小赵

前言

做开发做了好几年,前几年写iOS代码,去年写Web端的代码。然后发现学到的东西有限,了解的东西确实很多但是都不够深入。今年重新做iOS开发,痛定思痛,来研究或者说来熟悉下iOS中的各项技术。后面的博客可能会参照各个大佬的博客加上我自己的一些理解。

技术更新

后面更新的博客主要为iOS开发内容,当然有空的时候我会去研究下前端和后端以及数据库、大数据、AI相关的东西。
废话不多说,先看下这一篇博客要说的东西,可能内容有点多。


runtime

runtime版本

OC2.0 之后的版本为Modern版本:运行与64位系统中
OC2.0以前是 legacy版本:老的32位程序
runtime 是用c和汇编写的

原理

[obj method]被转化为objc_msgSend(obj,method)
1.通过obj的isa指针找到它的class
2.在class的methodList里面找到method
3.如果没找到,就到它的superclass里面去找
4.一旦找到这个函数,name就去执行这个函数的IMP

每次去找methodlist然后找一遍会出现效率低的问题
objc_class的objc_cache来实现对常用方法的缓存。
意思就是将常用的方法缓存下来,运行时,先去找cache中是否有方法,然后再重复上述步骤。
那么这个cache是怎么实现的呢,大致就是将一个key和函数的IMP存起来,然后在执行的时候找到key去执行IMP,然后算法更新这个方法是不是常用的。

1.objc_object
isa就是这个结构体的指针

objc_object
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

2.objc_class

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    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;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;

结构体说明:
isa -》class的isa指针
name - 》class 的名字
version - 》class 的版本
info - 》包含的信息
instance_size-》 实例大小
ivars-》属性列表
methodlists-》方法列表
cache -》方法cache
prtocols-》协议列表
3.obj_mehod

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

结构体说明:
method_name-》方法的名字
method_types-》方法的类型
method_imp-》方法的IMP

runtime应用

1.给分类关联属性
给分类添加属性主要用到下面三个方法

代码
给ViewController添加一个分类,命名为Extension
创建的分类如下

image0.png

分类的.h代码:

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface UIViewController (Extension)

@property(nonatomic, copy)NSString *contentView;

@end

NS_ASSUME_NONNULL_END

分类的.m代码

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

static const void *key = &key;

@implementation UIViewController (Extension)

- (void)setContentView:(UIView *)contentView{
    
    objc_setAssociatedObject(self, &key, contentView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}


- (UIView *)contentView{
    return objc_getAssociatedObject(self, &key);
}
@end

在UIViewController中调用

self.contentView = @"hello";
NSLog(@"%@",self.contentView);

代码以及相关说明
这个key值一定要定义全局静态变量 一个key对应一个value
声明:static void *key = "key";
用法1:&key作为参数
用法2:写成@selector(value)作为参数,其中value就是你属性的名字,这样写也获取了一个静态无返回值的地址。

2.给类动态添加方法

1.直接给当前类动态添加一个方法
传过来的方法是不是想要的方法,如果是就添加
+(BOOL)resolveInstanceMethod:(SEL)sel;
给类添加一个方法,参数说明 cls:给那个类添加 name:方法名字 IMP:指针(可以根据IMP的定义定义一个IMP的指针)types:”v@:“(编码表示(v)无返回值 (@)对象 (:)选择器)
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,
const char * _Nullable types);
添加成功 return YES;

代码
在ViewController.m中

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(work)) {
        class_addMethod([self class], sel,(IMP)work,"v@:");
    }
    return NO;
}

void work(id objct, SEL sel){
    NSLog(@"i am working");
}

调用

[self performSelector:@selector(work)];

2.转给备用接收者
第一种方案:
-(id)forwardingTargetForSelector:(SEL)aSelector;
声明一个对象,return这个对象,让另外的对象去执行这个方法
代码:

+ (BOOL)resolveInstanceMethod:(SEL)sel{
//    if (sel == @selector(work)) {
//        class_addMethod([self class], sel,(IMP)work,"v@:");
//    }
    return NO;
}
void work(id objct, SEL sel){
    NSLog(@"i am working");
}

- (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(work)) {
        Person *person = [Person new];
        return person;
    }
    return nil;
}

调用

[self performSelector:@selector(work)];

新建Person类,在Person类中添加一个work方法即可

第二种方案:
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
通过签名发送给anInvocation
-(void)forwardInvocation:(NSInvocation *)anInvocation;
[anInvocation invokeWithTarget:target];
在上面的方法中如果 target类中有anInvocation.selector,name就交给target类去执行这个方法,否则就执行[self doesNotRecognizeSelector:sel];

上面也称之为方法重定向

代码

+ (BOOL)resolveInstanceMethod:(SEL)sel{
//    if (sel == @selector(work)) {
//        class_addMethod([self class], sel,(IMP)work,"v@:");
//    }
    return NO;
}
void work(id objct, SEL sel){
    NSLog(@"i am working");
}

- (id)forwardingTargetForSelector:(SEL)aSelector{
//    if (aSelector == @selector(work)) {
//        Person *person = [Person new];
//        return person;
//    }
    return nil;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(work)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [NSMethodSignature 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];
    }
}

调用

[self performSelector:@selector(work)];

3.方法交换
1.直接方法交换
获取类的方法 返回值为Method
class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name);
交换方法
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2);

2.截取系统方法交换
在+(void)load{}中截取系统的方法并且交换 例如不想用系统的某个方法 需要自己实现,那么可以分类然后在分类中自定义一个方法 并且替换系统的方法。单例执行一次。
class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name);
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2);

代码

- (void)exchageMethod{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method m1 = class_getInstanceMethod([self class], @selector(printA));
        Method m2 = class_getInstanceMethod([self class], @selector(printB));
        method_exchangeImplementations(m1, m2);
    });
}

- (void)printA{
    NSLog(@"A");
}
- (void)printB{
    NSLog(@"B");
}

调用

[self exchageMethod];
[self printA];

结果会打印“B”;

4.获取类各种详细列表

- (void)obtainPropertyName{
   unsigned int count = 0;
    objc_property_t *propertyList = class_copyPropertyList([Person class], &count);
    for (NSInteger i = 0; i<count; i++) {
        NSString *name = @(property_getName(propertyList[i]));
        NSString *attribute = @(property_getAttributes(propertyList[i]));
        NSLog(@"%@ %@",name,attribute);
    }
}
- (void)obtainIvars{
    unsigned int outCount = 0;
    Ivar *ivarList = class_copyIvarList([Person class], &outCount);
    for (NSInteger i = 0; i < outCount; i++) {
        NSLog(@"%@",@(ivar_getName(ivarList[i])));
    }  
}
- (void)obtainMthods{
    unsigned int outCount = 0;
    Method *methods = class_copyMethodList([Person class], &outCount);
    for (NSInteger i = 0; i < outCount; i++) {
        NSLog(@"%@", NSStringFromSelector(method_getName(methods[i])));
    }
}
- (void)obtainProtocols{
    unsigned int outCount = 0;
    __unsafe_unretained Protocol **protocolList = class_copyProtocolList([Person class], &outCount);
    for (NSInteger i = 0; i < outCount; i++) {
        Protocol *pro = protocolList[i];
        NSLog(@"%@", @(protocol_getName(pro)));
    }
}

当然你如果要获取协议名字,那么你首先应该在这个类中遵守某个协议。

5.其他

+ (instancetype)modelWithDic:(NSDictionary *)dictionary{
    unsigned int outCount = 0;
    Ivar *ivars =  class_copyIvarList([self class], &outCount);
    id p = [[self alloc]init];
    for (int i = 0 ; i < outCount ; i ++ ) {
        NSString *name = @(ivar_getName(ivars[i]));
        if ([dictionary objectForKey:[name substringFromIndex:1]]) {
            [p setValue:[dictionary objectForKey:[name substringFromIndex:1]] forKey:[name substringFromIndex:1]];
        }
    }
    return p;
}

调用:

NSDictionary *dic = @{@"name":@"zhaoqian",@"sex":@"man",@"age":@"15"};
Person * nep = [Person modelWithDic:dic];
NSLog(@"%@\n%@\n%ld\n",nep.name,nep.sex,nep.age);
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);
- (void)encodeWithCoder:(nonnull NSCoder *)aCoder {
    unsigned int outCout = 0;
    Ivar *ivars = class_copyIvarList([self class], &outCout);
    for (int i = 0 ; i < outCout; i++) {
        NSString *name = @(ivar_getName(ivars[i]));
        [aCoder encodeObject:[self valueForKey:name] forKey:name];
    }
    free(ivars);
}

- (nullable instancetype)initWithCoder:(nonnull NSCoder *)aDecoder {
    self = [super init];
    if (self) {
        unsigned int outCout = 0;
        Ivar *ivars = class_copyIvarList([self class], &outCout);
        for (int i = 0 ; i < outCout; i++) {
            NSString *name = @(ivar_getName(ivars[i]));
            [aDecoder decodeObjectForKey:name];
        }
        free(ivars);
    }
    return self;
}

KVO

KVO 一对一
NSNotificationCenter 一对多
KVO可以监听单个属性的变化,也可以监听集合对象的变化

KVO原理

实际上是创建了一个 NSKVONotifying_XXX的子类,然后用runtime的isa-swizzling技术实现。

KVO实现

1.简单理解的kvo
kvo意思就是当属性的值变化的时候,监听这个变化的值,并且得到监听结果。那么可以在某个类中重写该属性的setter方法,然后回调回来接收,但是我写的不能动态的实现监听 ,要在每个属性的setter方法中去实现回调,实现了简单的kvo。
代码
Person.h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN
typedef void(^callBack)(id);

@interface Person : NSObject

@property (nonatomic, copy)NSString *name;
@property (nonatomic, assign)NSInteger age;
@property (nonatomic, copy)callBack block;

- (void)addObserverForkeyPath:(NSString *)keyPath;


@end

NS_ASSUME_NONNULL_END

Person.m

#import "Person.h"
#import <objc/runtime.h>
@interface Person(){
    NSString *_name;
    NSInteger _age;
}
@property (nonatomic, strong)NSString *keyPath;
@end

@implementation Person


- (void)addObserverForkeyPath:(NSString *)keyPath{
    if (!self.keyPath) {
        return;
    }
    self.keyPath = keyPath;
}
- (void)setName:(NSString *)name{
    if (_name!=name) {
         _name = name;
        if (self.block) {
            self.block(name);
        }
    }
}

- (void)setAge:(NSInteger)age{
    if (_age!=age) {
        _age = age;
    }
    if (self.block) {
        self.block(@(age));
    }
}

调用:

Person *p = [Person new];
    [p addObserverForkeyPath:@"name"];
    p.block = ^(id info) {
        //TODO
 };

2.利用runtime的方法实现KVO
我将代码说明全部注释在代码中了,就直接贴代码了
创建一个NSObect的分类 分类中重新添加代码
NSObject+Extension.h

#import <Foundation/Foundation.h>


NS_ASSUME_NONNULL_BEGIN

typedef void(^ValueChangeBlock)(id obeserverObject, NSString *key, id oldValue, id newValue);

@interface NSObject (Extension)

// 添加kvo并回调
-(void)addObserver:(NSObject *)observer forKey:(NSString *)key options:(NSKeyValueObservingOptions)options block:(ValueChangeBlock)callBack;

// 移除kvo
- (void)removeObserver:(NSObject *)observer forKey:(NSString *)key;


@end

NS_ASSUME_NONNULL_END

NSObject+Extension.m

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

static char const * kObservers= "ZHAOQIAN";

// ObserverInfo Model
@interface ObserverInfo : NSObject

// 监听的对象
@property (nonatomic, copy) NSString *observerName;
// 监听的健
@property (nonatomic, copy) NSString *key;

// 回调
@property (nonatomic, copy) ValueChangeBlock block;

@end

@implementation ObserverInfo

// 实例方法 
- (instancetype)initWithObserver:(NSString *)observerName key:(NSString *)key block:(ValueChangeBlock)block
{
    self = [super init];
    if (self) {
        _observerName = observerName;
        _key = key;
        _block = block;
    }
    return self;
}

@end

@implementation NSObject (Extension)


// 添加观察者
- (void)addObserver:(NSObject *)observer forKey:(NSString *)key options:(NSKeyValueObservingOptions)options block:(ValueChangeBlock)callBack{
    // 利用key 返回其setter方法的方法名,例如变为“setNmae”这样类型的
    NSString *str = private_setterForKey(key);
    // 根据方法名字获取方法
    Method setterMethod = class_getInstanceMethod([self class], NSSelectorFromString(str));
    // 获取父类的类名
    NSString *oldClassName = NSStringFromClass([self class]);
    // 创建子类的类名
    NSString *kvoClassName = [@"Notify_" stringByAppendingString:oldClassName];
    
    // 创建子类
    Class kvoClass;
    kvoClass = objc_lookUpClass(kvoClassName.UTF8String);
    if (!kvoClass) {
        kvoClass = objc_allocateClassPair([self class], kvoClassName.UTF8String, 0);
        objc_registerClassPair(kvoClass);
    }
    
    // 在子类中重新实现setter方法
    if (setterMethod) {
        class_addMethod(kvoClass, NSSelectorFromString(str), (IMP)setterIMP, "v@:@");
    }else{
        // 如果没有setter方法,那么方法交换下
        Method method1 = class_getInstanceMethod(self.class, @selector(setValue:forKey:));
        Method method2 = class_getInstanceMethod(self.class, @selector(swizz_setValue:forKey:));
        method_exchangeImplementations(method1, method2);
    }
    
    // 将self设置为它的子类
    object_setClass(self, kvoClass);
    
    // 创建模型
    ObserverInfo *info = [[ObserverInfo alloc]initWithObserver:observer.description key:key block:callBack];
    // 动态获取观察者属性
    NSMutableArray *observers = objc_getAssociatedObject(self, kObservers);
    if (!observers) {
        observers = [NSMutableArray array];
        // 添加观察者属性
        objc_setAssociatedObject(self, kObservers, observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    [observers addObject:info];
}

// 移除观察者
- (void)removeObserver:(NSObject *)observer forKey:(NSString *)key{
    NSMutableArray *observers = objc_getAssociatedObject(self, kObservers);
    ObserverInfo *info;
    for (ObserverInfo *temp in observers) {
        if ([temp.observerName isEqualToString:observer.description] && [temp.key isEqualToString:key]) {
            info = temp;
            break;
        }
    }
    if (info) {
        [observers removeObject:info];
    }else{
        @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%@ does not register observer for %@",observer.description, key] userInfo:nil];
    }
    
}

// 回调
- (void)swizz_setValue:(id)value forKey:(NSString *)key{
    id oldValue = [self valueForKey:key];
    [self swizz_setValue:value forKey:key];
    NSMutableArray *observers = objc_getAssociatedObject(self, kObservers);
    for (ObserverInfo *temp  in observers) {
        if ([temp.key isEqualToString:key]) {
            temp.block(self,key,oldValue,value);
        }
    }
}

// 指针交换的回调
void setterIMP(id self,SEL _cmd,id newValue){
    NSString *setterName = NSStringFromSelector(_cmd);
    NSString *temp = private_upperTolower([setterName substringFromIndex:@"set".length], 0);//去除set并将大写改成小写
    NSString *key = [temp substringToIndex:temp.length-1];//去除冒号
    id oldValue = [self valueForKey:key];
    // 父类结构体
    struct objc_super superClazz = {
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self))
    };
    // 方法发给父类
    ((void (*)(void *, SEL, id))objc_msgSendSuper)(&superClazz, _cmd, newValue);
    // 回调
    NSMutableArray *observers = objc_getAssociatedObject(self, kObservers);
    for (ObserverInfo *temp in observers) {
        if ([temp.key isEqualToString:key]) {
            temp.block(self, key, oldValue, newValue);
        }
    }
}

// 内联函数

// 返回方法名字,例如“setName”这样类型的
static inline NSString * private_setterForKey(NSString *key){
    key = private_lowerToUpper(key, 0);
    return [NSString stringWithFormat:@"set%@:",key];
}

// 将某个位置的字母小写转大写
static inline NSString * private_lowerToUpper(NSString *str,NSInteger location){
    NSRange range = NSMakeRange(location, 1);
    NSString *lowerLetter = [str substringWithRange:range];
    return [str stringByReplacingCharactersInRange:range withString:lowerLetter.uppercaseString];
}
// 将某个位置的字母大写转小写
static inline NSString * private_upperTolower(NSString *str,NSInteger location){
    NSRange range = NSMakeRange(location, 1);
    NSString *lowerLetter = [str substringWithRange:range];
    return [str stringByReplacingCharactersInRange:range withString:lowerLetter.lowercaseString];
}

@end

写在结尾

很多内容还是不够深入,后面如果有更深入的研究我会更新在这个博客中。
如果内容能帮到和我一样的同学,随手点个小星星也是不错的。

上一篇下一篇

猜你喜欢

热点阅读