iOS runtime2
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自身,所以我们应该对其“真身”进行操作。

#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