美文网首页
iOS runtime2

iOS runtime2

作者: Edviin_2de8 | 来源:发表于2022-04-10 22:47 被阅读0次

Method Swizzing(方法交换)

常用的API

  • class_getInstanceMethod:获取实例方法
  • class_getClassMethod :获取类方法
  • method_getImplementation:获取一个方法的实现
  • method_setImplementation:设置一个方法的实现
  • method_getTypeEncoding:获取方法实现的编码类型
  • class_addMethod:添加方法
  • class_replaceMethod:替换方法,用一个方法的实现,替换另一个方法的实现,即aIMP 指向 bIMP,但是bIMP不一定指向aIMP
    method_exchangeImplementations:交换两个方法的实现,即 aIMP -> bIMP, bIMP -> aIMP

Method Swizzing 是在运行时通过修改类的方法列表中selector对应的函数或者设置交换方法实现,来动态修改方法。

  • 先给要替换的方法的类添加一个Category
  • 在Category中的+(void)load方法中添加Method Swizzling方法,我们用来替换的方法也写在这个Category中。

Swizzling应该总在+load中执行,load类方法是程序运行时这个类被加载到内存中就调用的一个方法,执行比较早,并且不需要我们手动调用

Swizzling应该总是在dispatch_once中执行,避免被多次执行


#import <objc/runtime.h>
@implementation UIViewController (Swizzling)
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        // When swizzling a class method, use the following:
        // Class class = object_getClass((id)self);
        // 通过class_getInstanceMethod()函数从当前对象中的method list获取method结构体,如果是类方法就使用class_getClassMethod()函数获取。
        SEL originalSelector = @selector(viewWillAppear:);
        SEL swizzledSelector = @selector(xxx_viewWillAppear:);
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
   /**
     *  我们在这里使用class_addMethod()函数对Method Swizzling做了一层验证,如果self没有实现被交换的方法,会导致失败。
     *  而且self没有交换的方法实现,但是父类有这个方法,这样就会调用父类的方法,结果就不是我们想要的结果了。
     *  所以我们在这里通过class_addMethod()的验证,如果self实现了这个方法,class_addMethod()函数将会返回NO,我们就可以对其进行交换了。
     */

        BOOL didAddMethod = class_addMethod(class,
                                            originalSelector,
                                            method_getImplementation(swizzledMethod),
                                            method_getTypeEncoding(swizzledMethod));
        if (didAddMethod) {
            class_replaceMethod(class,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}
#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
    [self xxx_viewWillAppear:animated];
    NSLog(@"viewWillAppear: %@", self);
}
@end

Method Swizzling对NSArray这些的类簇是不起作用的。因为这些类簇类,其实是一种抽象工厂的设计模式。抽象工厂内部有很多其它继承自当前类的子类,抽象工厂类会根据不同情况,创建不同的抽象对象来进行使用。例如我们调用NSArray的objectAtIndex:方法,这个类会在方法内部判断,内部创建不同抽象类进行操作。

所以也就是我们对NSArray类进行操作其实只是对父类进行了操作,在NSArray内部会创建其他子类来执行操作,真正执行操作的并不是NSArray自身,所以我们应该对其“真身”进行操作。

image.png
#import "NSArray+LXZArray.h"
#import "objc/runtime.h"
@implementation NSArray (LXZArray)
+ (void)load {
    [super load];
    Method fromMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:));
    Method toMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(lxz_objectAtIndex:));
    method_exchangeImplementations(fromMethod, toMethod);
}
 
- (id)lxz_objectAtIndex:(NSUInteger)index {
    if (self.count-1 < index) {
        // 这里做一下异常处理,不然都不知道出错了。
        @try {
            return [self lxz_objectAtIndex:index];
        }
        @catch (NSException *exception) {
            // 在崩溃后会打印崩溃信息,方便我们调试。
            NSLog(@"---------- %s Crash Because Method %s  ----------\n", class_getName(self.class), __func__);
            NSLog(@"%@", [exception callStackSymbols]);
            return nil;
    }
        @finally {}
    } else {
        return [self lxz_objectAtIndex:index];
    }
}
@end

Category

objc_category *Category;
struct objc_category {
  category_name,
  class_name,
  instance_methods,
  class_methods,
  protocols
}
  • category中只能添加方法,不能添加实例变量。类的内存大小是在编译时确定的,而category是在运行时被添加的,此时再添加实例变量会破坏内存结构。
  • 在category中添加属性,通过关联对象实现setter、getter方法。
.h文件
#import "Person.h"
@interface Person (PersonExtention)
@property (copy, nonatomic) NSString *name;
-(void)saySex;
@end

.m文件
#import "Person+PersonExtention.h"
#import <objc/runtime.h>
@implementation Person (PersonExtention)
//定义常量 必须是C语言字符串
static char *PersonNameKey = "PersonNameKey";
-(void)setName:(NSString *)name{
    /*
    OBJC_ASSOCIATION_ASSIGN;            //assign策略
    OBJC_ASSOCIATION_COPY_NONATOMIC;    //copy策略
    OBJC_ASSOCIATION_RETAIN_NONATOMIC;  // retain策略

    OBJC_ASSOCIATION_RETAIN;
    OBJC_ASSOCIATION_COPY;
     */
     /*
     * id object 给哪个对象的属性赋值
       const void *key 属性对应的key
       id value  设置属性值为value
       objc_AssociationPolicy policy  使用的策略,是一个枚举值,和copy,retain,assign是一样的,手机开发一般都选择NONATOMIC
          objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
     */

    objc_setAssociatedObject(self, PersonNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)name{
    return objc_getAssociatedObject(self, PersonNameKey);
}
-(void)saySex{
    NSLog(@"%s----%@",__func__,self);
}

@end
category
  • 运行时决议
  • 有单独的.h和.m文件
  • 可以为系统类添加分类
  • 看不到源码的类可以添加分类
  • 只能添加方法,不能添加实例变量
extension
  • 编译时决议
  • 以声明的方式存在,寄生于主类.m文件
  • 不可以为系统类添加extension
  • 没有.m源码的类不可以extension
  • 可以添加方法,可添加实例变量,默认为@private

KVC

是一种可以通过key来访问类属性的机制。而不是通过调用Setter、Getter方法访问。
可以在运行时动态访问和修改对象的属性

// 赋值
[person1 setValue:@"jack" forKey:@"name"];

// 取值
NSString *name = [person1 valueForKey:@"name"];

forKeyPath 是对更“深层”的对象进行访问。如数组的某个元素,对象的某个属性。
[myModel setValue:@"beijing" forKeyPath:@"address.city"];

// 返回所有对象的name属性值
NSArray *names = [array valueForKeyPath:@"name"];
setValue:ForKey: valueForKey
  • 按照setKey、_setKey / getKey、_getKey的顺序查找方法,找到了就传递参数,调用方法
  • 如果没找到,则查看accessInstanceVariableDirectly方法的返回值,如果为NO(默认是YES)就不再继续往下执行,直接调用- setValue:forUndefinedKey抛出NSUnknownKeyException异常
  • 如果返回值为YES,则按照_key、_isKey、key、isKey的顺序查找成员变量,找到了就直接赋值
  • 如果没找到,则调用setValue:forUndefinedKey抛出异常

kvo

流程
  • 给对象添加监听
  • 通过runtime动态创建一个子类,修改对象的isa指向子类
  • 子类重写set方法,内部执行顺序
willChangeValueForKey
[super setKey]
didChangeValueForKey
在didChangeValueForKey中调用KVO的回调方法:observeValueForKeyPath:ofObject:change:context:

  • 注册观察者
    通过addObserver:forKeyPath:options:context:方法注册观察者,观察者可以接收keyPath属性的变化事件;
[self.wkwebview addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];//进度监听

  • 监听回调
    在观察者中实现observeValueForKeyPath:ofObject:change:context:方法,当keyPath属性发生改变后,KVO会回调这个方法来通知观察者
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    if ([keyPath isEqualToString:@"estimatedProgress"]) {
      }
  }

  • 移除观察者
    调用removeObserver:forKeyPath:方法将KVO移除。
    需要注意的是,调用removeObserver需要在观察者消失之前,否则会导致Crash
[self.wkwebview removeObserver:self forKeyPath:@"estimatedProgress"];


归档解档(NSCoding)

NSCoding是把数据存储在iOS和Mac OS上的一种极其简单和方便的方式,它把模型对象直接转变成一个文件,然后再把这个文件重新加载到内存里,并不需要任何文件解析和序列化的逻辑

常规方法

//自定义Person类继承自NSObject

.h文件
@interface Person : NSObject<NSCoding>
@property(nonatomic,strong) NSString * name;//名字
@property(nonatomic,strong) NSString * gender;//性别
@property(nonatomic,strong) NSString * address;//地址
@property(nonatomic) NSUInteger age;//年龄
-(instancetype)initWithName:(NSString*)name gender:(NSString*)gender address:(NSString*)adderss age:(NSUInteger)age;
@end

.m文件
#import "Person.h"
@implementation Person
/*
 使用常规进行解档与归档。
 */
-(void)encodeWithCoder:(NSCoder *)aCoder{
    [aCoder encodeObject:_name forKey:@"name"];
    [aCoder encodeObject:_gender forKey:@"gender"];
    [aCoder encodeObject:_address forKey:@"address"];
    [aCoder encodeInteger:_age forKey:@"age"];
    
}
-(instancetype)initWithCoder:(NSCoder *)aDecoder{
    if (self = [super init]) {
        _name = [aDecoder decodeObjectForKey:@"name"];
        _gender = [aDecoder decodeObjectForKey:@"gender"];
        _address = [aDecoder decodeObjectForKey:@"address"];
        _age = [aDecoder decodeIntegerForKey:@"age"];
    
    }
    return self;
}
-(instancetype)initWithName:(NSString *)name gender:(NSString *)gender address:(NSString *)adderss age:(NSUInteger)age{
    if (self = [super init]) {
        _name = name;
        _gender = gender;
        _address = adderss;
        _age = age;
    }
    return self;

}
-(NSString*)description{
    return [NSString stringWithFormat:@"name:%@  gender:%@  age:%lu  address:%@",self.name,self.gender,(unsigned long)self.age,self.address];

}
@end

当一个类继承自自定义的类,一定要调用父类的归档和解档,调用[super initWithCoder:aDeoder]和[super encodeWithCoder:aCoder]完成父类的归档与解档。
2.使用Runtime完成归档与解档

  • 通过class_copyIvarList获得对象的属性列表

  • 通过ivar_getName(ivar)获取到属性的C字符串名称

  • NSString *key = [NSString stringWithUTF8String:name];转成对应的OC名称

  • 利用KVC进行归档 [corder encodeObject: [self valueForKey:key] forKey: key];

  • 解档 id value = [coder decodeObjectForKey];

  • 利用KVC进行赋值[self setValue:value ForKey:key];

//.h文件,与上面定义相同
#import <Foundation/Foundation.h>
@interface Person : NSObject<NSCoding>
@property(nonatomic,strong) NSString * name;
@property(nonatomic,strong) NSString * gender;
@property(nonatomic,strong) NSString * address;
@property(nonatomic) NSUInteger age;
-(instancetype)initWithName:(NSString*)name gender:(NSString*)gender address:(NSString*)adderss age:(NSUInteger)age;
@end

.m文件
#import "Person.h"
#import <objc/runtime.h>
@implementation Person
/*
 使用runtime进行解档与归档。
 */
-(void)encodeWithCoder:(NSCoder *)aCoder{
    unsigned int count = 0;
    Ivar *ivarLists = class_copyIvarList([Person class], &count);// 注意下面分析
    for (int i = 0; i < count; i++) {
        const char* name = ivar_getName(ivarLists[i]);
        NSString* strName = [NSString stringWithUTF8String:name];
        [aCoder encodeObject:[self valueForKey:strName] forKey:strName];
    }
    free(ivarLists);   //一定不要忘了,自己释放。
}
-(instancetype)initWithCoder:(NSCoder *)aDecoder{
    if (self = [super init]) {
        unsigned int count = 0;
        Ivar *ivarLists = class_copyIvarList([Person class], &count);
        for (int i = 0; i < count; i++) {
            const char* name = ivar_getName(ivarLists[i]);
            NSString* strName = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
           id value = [aDecoder decodeObjectForKey:strName];
            [self setValue:value forKey:strName];
        }
        free(ivarLists);
    }
    return self;
}
-(instancetype)initWithName:(NSString *)name gender:(NSString *)gender address:(NSString *)adderss age:(NSUInteger)age{
    if (self = [super init]) {
        _name = name;
        _gender = gender;
        _address = adderss;
        _age = age;
    }
    return self;
}
-(NSString*)description{
    return [NSString stringWithFormat:@"name:%@  gender:%@  age:%lu  address:%@",self.name,self.gender,(unsigned long)self.age,self.address];

}
@end

调用归档与解档

#import "ViewController.h"
#import "Teacher.h"
#import "Person.h"
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    Teacher* teacher1 = [Teacher factoryWithName:@"liyang" gender:@"male" address:@"shandong" age:24 course:@"math"];
    Teacher* teacher2 = [Teacher factoryWithName:@"li" gender:@"female" address:@"shanghai" age:23 course:@"english"];
    NSMutableData* data = [NSMutableData data];
    NSKeyedArchiver* archiver = [[NSKeyedArchiver alloc]initForWritingWithMutableData:data1];
    [archiver encodeObject:teacher1 forKey:@"teacher"];
    [archiver encodeObject:teacher2 forKey:@"teacher2"];
    [archiver finishEncoding];
    NSString* path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"teacher"];
    [data writeToFile:path atomically:YES];
    
    NSData* data2 = [NSData dataWithContentsOfFile:path];
    NSKeyedUnarchiver* unarchiver = [[NSKeyedUnarchiver alloc]initForReadingWithData:data2];
    Teacher* teacher3 = [unarchiver decodeObjectForKey:@"teacher"];
    Teacher* teacher4 = [unarchiver decodeObjectForKey:@"teacher2"];
    [unarchiver finishDecoding];
    NSLog(@"\nteacher1:%@\nteacher3:%@\nteacher2:%@\nteacher4:%@\n%@",teacher1,teacher3,teacher2,teacher4,path);
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

相关文章

网友评论

      本文标题:iOS runtime2

      本文链接:https://www.haomeiwen.com/subject/tbjbsrtx.html