iOS runtime實(shí)戰(zhàn)應(yīng)用
iOS runtime 進(jìn)行添加屬性,并支持KVO監(jiān)聽(tīng)
iOS 中category和runtime的AssociatedObject是兩大非常重要的工具:
- category可以給既有類直接添加方法
- associateObject可以給既有類添加屬性(類似成員變量)
結(jié)合這兩個(gè)工具, 那么通過(guò)category添加property方法.然后結(jié)合associateObject增加關(guān)聯(lián)對(duì)象,完成屬性存取.
@interface UIViewController (Extension)
@property (nonatomic, copy) NSString * categoryString;
@end
@implementation UIViewController (Extension)
-(NSString *)categoryString{
return objc_getAssociatedObject(self, @selector(categoryString));
}
-(void)setCategoryString:(NSString *)categoryString{
objc_setAssociatedObject(self, @selector(categoryString), categoryString, OBJC_ASSOCIATION_COPY);
}
@end
并且這種方法也支持KVO的監(jiān)聽(tīng):
-(void)test{
self.categoryString = @"Runtime生成的屬性";
[self addObserver:self forKeyPath:@"categoryString" options:NSKeyValueObservingOptionNew context:nil];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"<接收到通知: object:%@ keyPath:%@ change:%@>", object, keyPath,change);
}
iOS 方法交換Method Swizzling
這里是喲個(gè)呢 method swizzling劫持 UIViewController 的viewWillAppear:方法, 周全起見(jiàn),有兩種情況要考慮一下(需要明確一下,它的目的是為了使用一個(gè)重寫的方法替換掉原來(lái)的方法。但重寫的方法可能是在父類中重寫的,也可能是在子類中重寫的):
- 復(fù)寫的方法(overridden)并沒(méi)有在目標(biāo)類中實(shí)現(xiàn)(notimplemented),而是在其父類中實(shí)現(xiàn)了。
- 這個(gè)方法已經(jīng)存在于目標(biāo)類中(does existing the class itself)。這兩種情況要區(qū)別對(duì)待。
對(duì)于第一種情況,應(yīng)當(dāng)先在目標(biāo)類增加一個(gè)新的實(shí)現(xiàn)方法(override),然后將復(fù)寫的方法替換為原先(的實(shí)現(xiàn)(original one)。 對(duì)于第二情況(在目標(biāo)類重寫的方法)。這時(shí)可以通過(guò)method_exchangeImplementations來(lái)完成交換.
+(void)load{
NSString *className = NSStringFromClass(self.class);
NSLog(@"classname %@", className);
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//要特別注意你替換的方法到底是哪個(gè)性質(zhì)的方法
// When swizzling a Instance method, use the following:
// Class class = [self class];
// When swizzling a class method, use the following:
Class class = object_getClass((id)self);
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
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
/*
我們通過(guò)method swizzling修改了UIViewController的@selector(viewWillAppear:)對(duì)應(yīng)的函數(shù)指針,使其實(shí)現(xiàn)指向了我們自定義的xxx_viewWillAppear的實(shí)現(xiàn)。這樣,當(dāng)UIViewController及其子類的對(duì)象調(diào)用viewWillAppear時(shí),都會(huì)打印一條日志信息。
*/
- (void)xxx_viewWillAppear:(BOOL)animated {
// 由于 系統(tǒng)在調(diào)用viewWillAppear時(shí)候 會(huì)調(diào)用到這里. 然后.調(diào)用這個(gè)以后. 繼續(xù)調(diào)用xxx_viewWillAppear方法, 實(shí)際是調(diào)用系統(tǒng)的 viewWillAppear方法(因?yàn)閮蓚€(gè)交換過(guò))
[self xxx_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@", self);
}
使用Runtime進(jìn)行 json/dict -> model
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, copy) NSString *city;
@property (nonatomic, copy) NSString *job;
- (instancetype)initWithNSDictionary:(NSDictionary *)dict;
@end
@implementation Person
-(instancetype)initWithNSDictionary:(NSDictionary *)dict{
self = [super init];
if (self) {
[self processDict:dict];
}
return self;
}
-(void)processDict:(NSDictionary *)dict{
NSMutableArray *keys = [[NSMutableArray alloc] init];
unsigned int count = 0;
objc_property_t *props = class_copyPropertyList([self class], &count);
for (int i = 0; i < count; i++) {
objc_property_t prop = props[i];
const char *propCStr = property_getName(prop);
NSString *propName = [NSString stringWithCString:propCStr encoding:NSUTF8StringEncoding];
[keys addObject:propName];
}
free(props);
for (NSString *key in keys) {
if ([dict valueForKey:key]) {
[self setValue:[dict valueForKey:key] forKey:key];
}
}
}
@end
使用runtime進(jìn)行簡(jiǎn)單的json -> model, 分成以下步驟:
- json string -> NSDictionary/NSArray<Dict>
- NSDictionary -> model property
主要在第二步中, 使用runtime遍歷model中的所有property, 然后根據(jù)Dict中的key去設(shè)置model中的property對(duì)應(yīng)的值.
還有一個(gè)比較常用的是 model -> dict, 在實(shí)際中需要自己根據(jù)實(shí)際項(xiàng)目情況進(jìn)行調(diào)整. 這里只是最簡(jiǎn)單的model -> dict的方法,很多情況沒(méi)有考慮到.
-(NSDictionary *)toDictionary{
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
Class cls = [self class];
while (cls != [NSObject class]) {
unsigned int count = 0;
objc_property_t *props = class_copyPropertyList(cls, &count);
for (int i = 0; i < count; i++) {
objc_property_t prop = props[i];
NSString *propName = [NSString stringWithUTF8String:property_getName(prop)];
if ([propName length] == 0) {
continue;
}
id value = [self valueForKey:propName];
if (value) {
[parameters setValue:value forKey:propName];
if (![NSJSONSerialization isValidJSONObject:parameters]) {
[parameters removeObjectForKey:propName];
}
}
}
if (props) {
free(props);
}
cls = class_getSuperclass(cls);
}
return [parameters copy];
}
runtime進(jìn)行model <-> json/dict 有許多非常棒的框架進(jìn)行了很多優(yōu)化, 例如 MJExtension, YYModel. 可以參考一下他們的源碼. 當(dāng)然這種比較簡(jiǎn)單的json/dict <-> model方式, 我們可以自己寫.
iOS Runtime 對(duì)對(duì)象進(jìn)行序列化與反序列化
序列化和反序列化的目的是將對(duì)象存儲(chǔ)到文件的方法.
- 序列化: 將數(shù)據(jù)結(jié)構(gòu)/對(duì)象轉(zhuǎn)化成二進(jìn)制數(shù)據(jù)
- 反序列化: 將二進(jìn)制數(shù)據(jù)恢復(fù)成數(shù)據(jù)結(jié)構(gòu)/對(duì)象
需要實(shí)現(xiàn)上面的功能, 需要具有序列化能力的類必須實(shí)現(xiàn)NSCoding協(xié)議的兩個(gè)函數(shù):
-(void) encodeWithCoder:(NSCoder *)encoder;
-(id) initWithCoder:(NSCoder *)decoder;
下面有一個(gè)實(shí)例:
-(instancetype)initWithCoder:(NSCoder *)aDecoder{
NSLog(@"%s",__func__);
Class cls = [self class];
while (cls != [NSObject class]) {
/*判斷是自身類還是父類*/
BOOL bIsSelfClass = (cls == [self class]);
unsigned int iVarCount = 0;
unsigned int propVarCount = 0;
unsigned int sharedVarCount = 0;
Ivar *ivarList = bIsSelfClass ? class_copyIvarList([cls class], &iVarCount) : NULL;/*變量列表,含屬性以及私有變量*/
objc_property_t *propList = bIsSelfClass ? NULL : class_copyPropertyList(cls, &propVarCount);/*屬性列表*/
sharedVarCount = bIsSelfClass ? iVarCount : propVarCount;
for (int i = 0; i < sharedVarCount; i++) {
const char *varName = bIsSelfClass ? ivar_getName(*(ivarList + i)) : property_getName(*(propList + i));
NSString *key = [NSString stringWithUTF8String:varName];
id varValue = [aDecoder decodeObjectForKey:key];
NSArray *filters = @[@"superclass", @"description", @"debugDescription", @"hash"];
if (varValue && [filters containsObject:key] == NO) {
[self setValue:varValue forKey:key];
}
}
free(ivarList);
free(propList);
cls = class_getSuperclass(cls);
}
return self;
}
/*
這里需要特別注意的是:
編解碼的范圍不能僅僅是自身類的變量,還應(yīng)當(dāng)把除NSObject類外的所有層級(jí)父類的屬性變量也進(jìn)行編解碼!
由此可見(jiàn),這幾乎是個(gè)純體力活。而runtime在遍歷變量這件事情上能為我們提供什么幫助呢?
我們可以通過(guò)runtime在運(yùn)行時(shí)獲取自身類的所有變量進(jìn)行編解碼;然后對(duì)父類進(jìn)行遞歸,獲取除NSObject外每個(gè)層級(jí)父類的屬性(非私有變量),進(jìn)行編解碼。
*/
- (void)encodeWithCoder:(NSCoder *)coder
{
NSLog(@"%s",__func__);
Class cls = [self class];
while (cls != [NSObject class]) {
/*判斷是自身類還是父類*/
BOOL bIsSelfClass = (cls == [self class]);
unsigned int iVarCount = 0;
unsigned int propVarCount = 0;
unsigned int sharedVarCount = 0;
Ivar *ivarList = bIsSelfClass ? class_copyIvarList([cls class], &iVarCount) : NULL;/*變量列表,含屬性以及私有變量*/
objc_property_t *propList = bIsSelfClass ? NULL : class_copyPropertyList(cls, &propVarCount);/*屬性列表*/
sharedVarCount = bIsSelfClass ? iVarCount : propVarCount;
for (int i = 0; i < sharedVarCount; i++) {
const char *varName = bIsSelfClass ? ivar_getName(*(ivarList + i)) : property_getName(*(propList + i));
NSString *key = [NSString stringWithUTF8String:varName];
/*valueForKey只能獲取本類所有變量以及所有層級(jí)父類的屬性,不包含任何父類的私有變量(會(huì)崩潰)*/
id varValue = [self valueForKey:key];
NSArray *filters = @[@"superclass", @"description", @"debugDescription", @"hash"];
if (varValue && [filters containsObject:key] == NO) {
[coder encodeObject:varValue forKey:key];
}
}
free(ivarList);
free(propList);
cls = class_getSuperclass(cls);
}
}
相關(guān)Demo: https://github.com/brownfeng/RuntimeDemo.git
參考資料
http://www.cocoachina.com/ios/20170424/19102.html
http://www.itdecent.cn/p/07b6c4a40a90
http://www.itdecent.cn/p/fed1dcb1ac9f
http://www.itdecent.cn/p/0497afdad36d