首先遏餐,歸納下Runtime的幾個(gè)使用場(chǎng)景伦腐。
- 做用戶埋點(diǎn)統(tǒng)計(jì)
- 處理異常崩潰(NSDictionary, NSMutableDictionary, NSArray, NSMutableArray 的處理)
- 按鈕最小點(diǎn)擊區(qū)設(shè)置
- 按鈕重復(fù)點(diǎn)擊設(shè)置
- 手勢(shì)的重復(fù)點(diǎn)擊處理
- UIButton點(diǎn)擊事件帶多參數(shù)
- MJRefresh封裝
- 服務(wù)端控制頁(yè)面跳轉(zhuǎn)
- 字典轉(zhuǎn)模型
一 用戶埋點(diǎn)
在做app運(yùn)營(yíng)的時(shí)候, 我們經(jīng)常會(huì)需要接入一些第三方做統(tǒng)計(jì)失都, 例如友盟統(tǒng)計(jì)柏蘑,google統(tǒng)計(jì)等。 例如外面需要統(tǒng)計(jì)某個(gè)頁(yè)面用戶停留的時(shí)長(zhǎng)粹庞, 統(tǒng)計(jì)某個(gè)頁(yè)面的展示次數(shù)咳焚。 通常我們的做法是 : 需要統(tǒng)計(jì)A頁(yè)面停留時(shí)長(zhǎng)的時(shí)候,我們?cè)貯頁(yè)面出現(xiàn)(appear)的時(shí)候記錄一個(gè)時(shí)間戳庞溜,頁(yè)面消失(dispear)的時(shí)候用當(dāng)前時(shí)間戳與之前的時(shí)間戳求出時(shí)間間隔革半,然后上報(bào)到分析平臺(tái)。 如果統(tǒng)計(jì)頁(yè)面展示次數(shù)流码, 就在每次頁(yè)面出現(xiàn)時(shí)調(diào)用統(tǒng)計(jì)方法又官。 這樣做的壞處是 代碼侵入性太強(qiáng),維護(hù)性與易讀性都不太好旅掂。 假設(shè)以后要改需求赏胚, 就要進(jìn)入到代碼所在處進(jìn)行修改访娶。 又或者別人接手你的代碼商虐, 根本不知道已經(jīng)做了哪些埋點(diǎn), 需求改來改去崖疤,時(shí)間久了秘车, 項(xiàng)目中全都是垃圾代碼。
此時(shí)劫哼,為了優(yōu)化統(tǒng)計(jì)叮趴, 我們使用 Hook (鉤子)的思想, 例如Runtime的 Method sweezing(方法交換)去攔截系統(tǒng)方法來實(shí)現(xiàn)共計(jì)权烧。
首先眯亦,我們寫一個(gè)集成NSObject的工具類,實(shí)現(xiàn)方法交換
#import "HookTool.h"
#import <objc/runtime.h>
@implementation HookTool
+(void)swizzingForClass:(Class)cls originalSel:(SEL)originalSelector swizzingSel:(SEL)swizzingSelector
{
Class class = cls;
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzingMethod = class_getInstanceMethod(class, swizzingSelector);
BOOL addMethod = class_addMethod(class,
originalSelector,
method_getImplementation(swizzingMethod),
method_getTypeEncoding(swizzingMethod));
if (addMethod) {
class_replaceMethod(class,
swizzingSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
}else{
method_exchangeImplementations(originalMethod, swizzingMethod);
}
}
@end
接著般码,我們寫一個(gè)UIViewController的分類妻率, 在Load方法中把系統(tǒng)方法替換掉:
#import "UIViewController+actionAnalysis.h"
#import "HookTool.h"
#import "NSDate+Convenience.h"
@implementation UIViewController (actionAnalysis)
+(void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL originalAppearSelector = @selector(viewWillAppear:);
SEL swizzingAppearSelector = @selector(user_viewWillAppear:);
[HookTool swizzingForClass:[self class] originalSel:originalAppearSelector swizzingSel:swizzingAppearSelector];
SEL originalDisappearSelector = @selector(viewWillDisappear:);
SEL swizzingDisappearSelector = @selector(user_viewWillDisappear:);
[HookTool swizzingForClass:[self class] originalSel:originalDisappearSelector swizzingSel:swizzingDisappearSelector];
});
}
-(void)user_viewWillAppear:(BOOL)animated
{
//頁(yè)面出現(xiàn)
[self user_viewWillAppear:animated];
}
-(void)user_viewWillDisappear:(BOOL)animated
{
//頁(yè)面消失
[self user_viewWillDisappear:animated];
}
@end
此時(shí)還有個(gè)問題, 首先你可能并不想對(duì)每個(gè)頁(yè)面進(jìn)行統(tǒng)計(jì)板祝, 但是又不想每次添加一個(gè)統(tǒng)計(jì)就加一個(gè)if判斷宫静。 這個(gè)時(shí)候我們就在Xcode中加入一張plist表, plist表里面記錄我們所需統(tǒng)計(jì)的信息
此時(shí),我們只需要在hook的方法中去實(shí)現(xiàn)統(tǒng)計(jì)邏輯
-(void)user_viewWillAppear:(BOOL)animated
{
NSDictionary * pageenter = [[HookTool getConfig] objectForKey:@"page_enter_anysis"];
if ([pageenter.allKeys containsObject:NSStringFromClass([self class])]) {
NSLog(@"%@ 頁(yè)面展示", NSStringFromClass([self class]));
}
NSDictionary * pagetime = [[HookTool getConfig] objectForKey:@"page_time_anysis"];
if ([pagetime.allKeys containsObject:NSStringFromClass([self class])]) {
//此處用Userdefault存儲(chǔ)只是因?yàn)榉奖銜鴮懀?實(shí)際用可以用一個(gè)單例去存儲(chǔ)中間值
[[NSUserDefaults standardUserDefaults] setDouble:[[NSDate date] timeIntervalSince1970] * 1000 forKey:@"appeartime"];
}
[self user_viewWillAppear:animated];
}
-(void)user_viewWillDisappear:(BOOL)animated
{
//頁(yè)面停留時(shí)間統(tǒng)計(jì)
NSDictionary * pagetime = [[HookTool getConfig] objectForKey:@"page_time_anysis"];
if ([pagetime.allKeys containsObject:NSStringFromClass([self class])]) {
double leaveTime = NSDate.currenMillisecondTimestamp - [[NSUserDefaults standardUserDefaults] doubleForKey:@"appeartime"];
NSLog(@"%@ 頁(yè)面的停留時(shí)間為 %lf ms", [self class], leaveTime);
}
[self user_viewWillDisappear:animated];
}
這樣的話孤里,以后做頁(yè)面時(shí)長(zhǎng)或者頁(yè)面展示的統(tǒng)計(jì)伏伯,就只需要維護(hù)這個(gè)plist表就行了,不需要具體改動(dòng)代碼捌袜。
點(diǎn)擊事件統(tǒng)計(jì):
與VC的統(tǒng)計(jì)類似说搅, 也是利用catagory + hook的思想來實(shí)現(xiàn), 我們可以添加一個(gè)UIControl的分類琢蛤。但是具體需要hook UIControl的哪個(gè)方法那 蜓堕? 點(diǎn)擊進(jìn)入U(xiǎn)IControl的api, 我們很容易發(fā)現(xiàn)需要Hook的方法
- (void)sendAction:(SEL)action to:(nullable id)target forEvent:(nullable UIEvent *)event;
接著我們?cè)赨IControl的分類中實(shí)現(xiàn)方法的交互
@implementation UIControl (actionAnalysis)
+(void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL originalSelector = @selector(sendAction:to:forEvent:);
SEL swizzingSelector = @selector(user_sendAction:to:forEvent:);
[HookTool swizzingForClass:[self class] originalSel:originalSelector swizzingSel:swizzingSelector];
});
}
-(void)user_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
{
NSLog(@"\n***hook success.\n[1]action:%@\n[2]target:%@ \n[3]event:%ld", NSStringFromSelector(action), target, (long)event);
[self user_sendAction:action to:target forEvent:event];
}
同樣的博其, 我們只需要在plist中添加click的統(tǒng)計(jì)所需的參數(shù)就可以了
利用Runtime做用戶埋點(diǎn)的就說這么多套才, 文章只提供思路, 具體plist的結(jié)構(gòu)慕淡,或者代碼細(xì)節(jié)根據(jù)情況自己做實(shí)現(xiàn)就行了背伴。另外, 由于需求變動(dòng)的原因峰髓,造成代碼與配置表不匹配(例如可能會(huì)出現(xiàn)某個(gè)method名字被改變 )從而造成埋點(diǎn)統(tǒng)計(jì)失敗傻寂, 建議寫一個(gè)單元測(cè)試對(duì)Plist進(jìn)行測(cè)試,思路: 在單元測(cè)試中我們首先讀取plist配置文件携兵,遍歷所有的頁(yè)面疾掰。在一個(gè)頁(yè)面內(nèi)遍歷所有的ControlEventIDs,對(duì)每個(gè)響應(yīng)函數(shù)名進(jìn)行respondsToSelector:判斷徐紧。 這樣可以有效減少埋點(diǎn)失效問題静檬。
二 處理異常崩潰(NSDictionary, NSMutableDictionary, NSArray, NSMutableArray 的處理)
在開發(fā)過程中, 有時(shí)候會(huì)出現(xiàn)set object for key的時(shí)候 object為Nil或者Key為Nil并级, 又或者初始化array, dic的時(shí)候由于數(shù)據(jù)個(gè)數(shù)與指定的長(zhǎng)度不一致造成崩潰拂檩。 此時(shí)利用runtime對(duì)異常情況進(jìn)行捕捉,提前return或者拋棄多余的長(zhǎng)度嘲碧。
Dic:
#import "NSDictionary+Safe.h"
#import <objc/runtime.h>
@implementation NSDictionary (Safe)
+ (void)load {
Method originalMethod = class_getClassMethod(self, @selector(dictionaryWithObjects:forKeys:count:));
Method swizzledMethod = class_getClassMethod(self, @selector(na_dictionaryWithObjects:forKeys:count:));
method_exchangeImplementations(originalMethod, swizzledMethod);
}
+ (instancetype)na_dictionaryWithObjects:(const id [])objects forKeys:(const id <NSCopying> [])keys count:(NSUInteger)cnt {
id nObjects[cnt];
id nKeys[cnt];
int i=0, j=0;
for (; i<cnt && j<cnt; i++) {
if (objects[i] && keys[i]) {
nObjects[j] = objects[i];
nKeys[j] = keys[i];
j++;
}
}
return [self na_dictionaryWithObjects:nObjects forKeys:nKeys count:j];
}
@end
@implementation NSMutableDictionary (Safe)
+ (void)load {
Class dictCls = NSClassFromString(@"__NSDictionaryM");
Method originalMethod = class_getInstanceMethod(dictCls, @selector(setObject:forKey:));
Method swizzledMethod = class_getInstanceMethod(dictCls, @selector(na_setObject:forKey:));
method_exchangeImplementations(originalMethod, swizzledMethod);
}
- (void)na_setObject:(id)anObject forKey:(id<NSCopying>)aKey {
if (!anObject || !aKey)
return;
[self na_setObject:anObject forKey:aKey];
}
@end
array:
#import "NSArray+Safe.h"
#import <objc/runtime.h>
@implementation NSArray (Safe)
+ (void)load {
Method originalMethod = class_getClassMethod(self, @selector(arrayWithObjects:count:));
Method swizzledMethod = class_getClassMethod(self, @selector(na_arrayWithObjects:count:));
method_exchangeImplementations(originalMethod, swizzledMethod);
}
+ (instancetype)na_arrayWithObjects:(const id [])objects count:(NSUInteger)cnt {
id nObjects[cnt];
int i=0, j=0;
for (; i<cnt && j<cnt; i++) {
if (objects[i]) {
nObjects[j] = objects[i];
j++;
}
}
return [self na_arrayWithObjects:nObjects count:j];
}
@end
@implementation NSMutableArray (Safe)
+ (void)load {
Class arrayCls = NSClassFromString(@"__NSArrayM");
Method originalMethod1 = class_getInstanceMethod(arrayCls, @selector(insertObject:atIndex:));
Method swizzledMethod1 = class_getInstanceMethod(arrayCls, @selector(na_insertObject:atIndex:));
method_exchangeImplementations(originalMethod1, swizzledMethod1);
Method originalMethod2 = class_getInstanceMethod(arrayCls, @selector(setObject:atIndex:));
Method swizzledMethod2 = class_getInstanceMethod(arrayCls, @selector(na_setObject:atIndex:));
method_exchangeImplementations(originalMethod2, swizzledMethod2);
}
- (void)na_insertObject:(id)anObject atIndex:(NSUInteger)index {
if (!anObject)
return;
[self na_insertObject:anObject atIndex:index];
}
- (void)na_setObject:(id)anObject atIndex:(NSUInteger)index {
if (!anObject)
return;
[self na_setObject:anObject atIndex:index];
}
@end
三 按鈕最小點(diǎn)擊區(qū)設(shè)置
按鈕太不好點(diǎn)中了稻励,點(diǎn)擊好幾次才點(diǎn)擊到”, 測(cè)試經(jīng)常會(huì)有這樣的抱怨愈涩, 但是此時(shí)按鈕圖片本身設(shè)計(jì)就很小望抽。 此時(shí),例如Runtime進(jìn)行點(diǎn)擊區(qū)放大履婉, 是個(gè)挺好的解決版本
static const void *topNameKey = @"topNameKey";
static const void *rightNameKey = @"rightNameKey";
static const void *bottomNameKey = @"bottomNameKey";
static const void *leftNameKey = @"leftNameKey";
- (void)setEnlargeEdgeWithTop:(CGFloat)top right:(CGFloat)right bottom:(CGFloat)bottom left:(CGFloat)left{
objc_setAssociatedObject(self, &topNameKey, [NSNumber numberWithFloat:top], OBJC_ASSOCIATION_COPY_NONATOMIC);
objc_setAssociatedObject(self, &rightNameKey, [NSNumber numberWithFloat:right], OBJC_ASSOCIATION_COPY_NONATOMIC);
objc_setAssociatedObject(self, &bottomNameKey, [NSNumber numberWithFloat:bottom], OBJC_ASSOCIATION_COPY_NONATOMIC);
objc_setAssociatedObject(self, &leftNameKey, [NSNumber numberWithFloat:left], OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (CGRect)enlargedRect
{
NSNumber *topEdge = objc_getAssociatedObject(self, &topNameKey);
NSNumber *rightEdge = objc_getAssociatedObject(self, &rightNameKey);
NSNumber *bottomEdge = objc_getAssociatedObject(self, &bottomNameKey);
NSNumber *leftEdge = objc_getAssociatedObject(self, &leftNameKey);
if (topEdge && rightEdge && bottomEdge && leftEdge) {
return CGRectMake(self.bounds.origin.x - leftEdge.floatValue,
self.bounds.origin.y - topEdge.floatValue,
self.bounds.size.width + leftEdge.floatValue + rightEdge.floatValue,
self.bounds.size.height + topEdge.floatValue + bottomEdge.floatValue);
}
else
{
return self.bounds;
}
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
CGRect rect = [self enlargedRect];
if (CGRectEqualToRect(rect, self.bounds)) {
return [super hitTest:point withEvent:event];
}
return CGRectContainsPoint(rect, point) ? self : nil;
}
四 按鈕的重復(fù)點(diǎn)擊
這個(gè)就不多說了煤篙,詳細(xì)大部分程序員都遇到過, 直接上代碼
+ (void)load{
Method originalMethod = class_getInstanceMethod([self class], @selector(sendAction:to:forEvent:));
Method swizzledMethod = class_getInstanceMethod([self class], @selector(User_SendAction:to:forEvent:));
method_exchangeImplementations(originalMethod, swizzledMethod);
}
#pragma mark -- 時(shí)間間隔 --
static const void *ButtonDurationTime = @"ButtonDurationTime";
- (NSTimeInterval)durationTime{
NSNumber *number = objc_getAssociatedObject(self, &ButtonDurationTime);
return number.doubleValue;
}
- (void)setDurationTime:(NSTimeInterval)durationTime{
NSNumber *number = [NSNumber numberWithDouble:durationTime];
objc_setAssociatedObject(self, &ButtonDurationTime, number, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (void)User_SendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
self.userInteractionEnabled = NO;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.durationTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.userInteractionEnabled = YES;
});
[self User_SendAction:action to:target forEvent:event];
}
五 手勢(shì)的重復(fù)點(diǎn)擊處理
手勢(shì)重復(fù)點(diǎn)擊有個(gè)誤區(qū): 不能通過攔截 addTarget:(id)target action:(SEL)action 這個(gè)方法來實(shí)現(xiàn)谐鼎,因?yàn)檫@個(gè)方法是是添加方法舰蟆,即使我們交換了趣惠,在執(zhí)行的時(shí)候并沒有什么變化的。正確的做法是添加一個(gè)timeInterval身害,然后在代理里面根據(jù)timeInterval設(shè)置UITapGestureRecognizer的enable屬性
#import "UITapGestureRecognizer+LOOExtension.h"
#import <objc/runtime.h>
@interface UITapGestureRecognizer ()
///時(shí)間間隔
@property (nonatomic,assign) NSTimeInterval duration;
@end
static const void *UITapGestureRecognizerduration = @"GestureRecognizerduration";
@implementation UITapGestureRecognizer (LOOExtension)
#pragma mark - Getter Setter
- (NSTimeInterval)duration{
NSNumber *number = objc_getAssociatedObject(self, &UITapGestureRecognizerduration);
return number.doubleValue;
}
- (void)setDuration:(NSTimeInterval)duration{
NSNumber *number = [NSNumber numberWithDouble:duration];
objc_setAssociatedObject(self, &UITapGestureRecognizerduration, number, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
/**
添加點(diǎn)擊事件
@param target taeget
@param action action
@param duration 時(shí)間間隔
*/
- (instancetype)initWithTarget:(id)target action:(SEL)action withDuration:(NSTimeInterval)duration{
self = [super init];
if (self) {
self.duration = duration;
self.delegate = self;
[self addTarget:target action:action];
}
return self;
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
self.enabled = NO;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.enabled = YES;
});
return YES;
}
@end
六 UIButton點(diǎn)擊帶多參數(shù)
UIButton *btn = // create the button
objc_setAssociatedObject(btn, "firstObject", someObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC); //實(shí)際上就是KVC
objc_setAssociatedObject(btn, "secondObject", otherObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[btn addTarget:self action:@selector(click:) forControlEvents:UIControlEventTouchUpInside];
- (void)click:(UIButton *)sender
{
id first = objc_getAssociatedObject(btn, "firstObject"); //取參
id second = objc_setAssociatedObject(btn, "secondObject");
// etc.
}
這么使用runtime感覺有點(diǎn)雞肋味悄,至少在自己的iOS生涯中,沒有必須需要這么做的時(shí)候塌鸯。 其實(shí)寫個(gè)子類侍瑟,添加個(gè)Parameter屬性豈不是更簡(jiǎn)單。
七 MJRefresh的封裝
大部分程序員應(yīng)該都用過MJRefresh這個(gè)工具丙猬,大部分用法都每次出現(xiàn)tabview初始化后涨颜, 都初始化出來一個(gè) mj_header, mj_footer, 并且設(shè)置 header與footer后茧球, 把mj_header與mj_footer復(fù)制給tableview.mj_header, tableview.mj_footer. 每次去重復(fù)創(chuàng)建Header庭瑰, Footer, 這個(gè)是不能容忍的抢埋。 我們知道tableview和collectionView都是繼承自scrollView弹灭,那么我們可以在 scrollView的分類里面添加一些方法,那么我們?cè)谝院笫褂玫臅r(shí)候揪垄,就不需要一遍一遍的重復(fù)寫無(wú)用代碼了穷吮,只需要調(diào)用scrollView分類方法就可以了。
#import "UIScrollView+JHRefresh.h"
#import <MJRefresh.h>
@implementation UIScrollView (JHRefresh)
/**
添加刷新事件
@param headerBlock 頭部刷新
@param footerBlock 底部刷新
*/
- (void)setRefreshWithHeaderBlock:(void(^)(void))headerBlock
footerBlock:(void(^)(void))footerBlock{
if (headerBlock) {
MJRefreshNormalHeader *header= [MJRefreshNormalHeader headerWithRefreshingBlock:^{
if (headerBlock) {
headerBlock();
}
}];
header.stateLabel.font = [UIFont systemFontOfSize:13];
header.lastUpdatedTimeLabel.font = [UIFont systemFontOfSize:13];
self.mj_header = header;
}
if (footerBlock) {
MJRefreshBackNormalFooter *footer = [MJRefreshBackNormalFooter footerWithRefreshingBlock:^{
footerBlock();
}];
footer.stateLabel.font = [UIFont systemFontOfSize:13];
[footer setTitle:@"暫無(wú)更多數(shù)據(jù)" forState:MJRefreshStateNoMoreData];
[footer setTitle:@"" forState:MJRefreshStateIdle];
self.mj_footer.ignoredScrollViewContentInsetBottom = 44;
self.mj_footer = footer;
}
}
/**
開啟頭部刷新
*/
- (void)headerBeginRefreshing{
[self.mj_header beginRefreshing];
}
/**
沒有更多數(shù)據(jù)
*/
- (void)footerNoMoreData{
[self.mj_footer setState:MJRefreshStateNoMoreData];
}
/**
結(jié)束刷新
*/
- (void)endRefresh{
if (self.mj_header) {
[self.mj_header endRefreshing];
}
if (self.mj_footer) {
[self.mj_footer endRefreshing];
}
}
八 服務(wù)端控制頁(yè)面跳轉(zhuǎn)
項(xiàng)目開發(fā)中饥努,我們可能會(huì)有這樣的需求: 根據(jù)服務(wù)端推送過來的數(shù)據(jù)規(guī)則捡鱼,跳轉(zhuǎn)到對(duì)應(yīng)的控制器。 之前我們的做法是這樣的: 前端與服務(wù)端定義好規(guī)則酷愧, 例如服務(wù)端推送 Push/Live/WatchLive/12, Push: push方式跳轉(zhuǎn) 驾诈, Live指的直播模塊, WatchLive指的看直播的功能伟墙, 12指的房間號(hào)翘鸭, 也就是跳轉(zhuǎn)到12號(hào)主播間滴铅。 但是這么做壞處就是戳葵,必須提前與服務(wù)端約定好協(xié)議, 每次運(yùn)營(yíng)如果加一個(gè)新的跳轉(zhuǎn)汉匙, 移動(dòng)端需要改代碼拱烁,重新上線。擴(kuò)展性很低噩翠。
其實(shí)利用Runtime完全可以寫成通用的方式來實(shí)現(xiàn)跳轉(zhuǎn)戏自。例如外面與服務(wù)端定義好推送規(guī)則后,服務(wù)端推送過來的數(shù)據(jù)如下:
// 這個(gè)規(guī)則肯定事先跟服務(wù)端溝通好伤锚,跳轉(zhuǎn)對(duì)應(yīng)的界面需要對(duì)應(yīng)的參數(shù)
NSDictionary *userInfo = @{
@"class": @"LiveViewController", //VC的名字
@"property": @{
@"ID": @"123", //參數(shù)名字為 ID , value為 123
@"type": @"12" //type為附加信息擅笔, 根據(jù)實(shí)際情況定義
}
};
接著我們利用Runtime進(jìn)行跳轉(zhuǎn)
// 類名
NSString *class =[NSString stringWithFormat:@"%@", params[@"class"]];
const char *className = [class cStringUsingEncoding:NSASCIIStringEncoding];
// 從一個(gè)字串返回一個(gè)類
Class newClass = objc_getClass(className);
if (!newClass)
{
return; //推送的class不存在
}
// 創(chuàng)建對(duì)象
id instance = [[newClass alloc] init];
// 對(duì)該對(duì)象賦值屬性
NSDictionary * propertys = params[@"property"];
[propertys enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
// 檢測(cè)這個(gè)對(duì)象是否存在該屬性
if ([self checkIsExistPropertyWithInstance:instance verifyPropertyName:key]) {
// 利用kvc賦值
[instance setValue:obj forKey:key];
}
}];
// 獲取導(dǎo)航控制器
UITabBarController *tabVC = (UITabBarController *)self.window.rootViewController;
UINavigationController *pushClassStance = (UINavigationController *)tabVC.viewControllers[tabVC.selectedIndex];
// 跳轉(zhuǎn)到對(duì)應(yīng)的控制器
[pushClassStance pushViewController:instance animated:YES];
檢測(cè)屬性是否存在
- (BOOL)checkIsExistPropertyWithInstance:(id)instance verifyPropertyName:(NSString *)verifyPropertyName
{
unsigned int outCount, i;
// 獲取對(duì)象里的屬性列表
objc_property_t * properties = class_copyPropertyList([instance
class], &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property =properties[i];
// 屬性名轉(zhuǎn)成字符串
NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
// 判斷該屬性是否存在
if ([propertyName isEqualToString:verifyPropertyName]) {
free(properties);
return YES;
}
}
free(properties);
return NO;
}
九 字典轉(zhuǎn)模型
獲取屬性的列表的方法是字典轉(zhuǎn)模型的比較核心的方法。常見的字典轉(zhuǎn)模型的三方有 MJExtension, YYModel, JsonModel等, 翻看其源碼猛们, 都會(huì)發(fā)現(xiàn) Ivar *class_copyIvarList(Class cls, unsigned int *outCount)的使用
MJExtension核心代碼摘錄
YYModel核心代碼摘錄
JsonModel json字典轉(zhuǎn)model 摘錄
基本上主流的json 轉(zhuǎn)model 都少不了,使用運(yùn)行時(shí)動(dòng)態(tài)獲取屬性的屬性名的方法,來進(jìn)行字典轉(zhuǎn)模型替換,字典轉(zhuǎn)模型效率最高的(耗時(shí)最短的)的是KVC,其他的字典轉(zhuǎn)模型是在KVC 的key 和Value 做處理,動(dòng)態(tài)的獲取json 中的key 和value ,當(dāng)然轉(zhuǎn)換的過程中,第三方框架需要做一些判空啊,鑲嵌的邏輯處理, 再進(jìn)行KVC 轉(zhuǎn)模型.這句代碼 [xx setValue:value forKey:key];無(wú)論JsonModle,YYKIt,MJextension 都少不了[xx setValue:value forKey:key];這句代碼的,不信可以去搜,這是字典轉(zhuǎn)模型的核心方法,