iOS - runtime的一些運(yùn)用場(chǎng)景

首先遏餐,歸納下Runtime的幾個(gè)使用場(chǎng)景伦腐。

  1. 做用戶埋點(diǎn)統(tǒng)計(jì)
  2. 處理異常崩潰(NSDictionary, NSMutableDictionary, NSArray, NSMutableArray 的處理)
  3. 按鈕最小點(diǎn)擊區(qū)設(shè)置
  4. 按鈕重復(fù)點(diǎn)擊設(shè)置
  5. 手勢(shì)的重復(fù)點(diǎn)擊處理
  6. UIButton點(diǎn)擊事件帶多參數(shù)
  7. MJRefresh封裝
  8. 服務(wù)端控制頁(yè)面跳轉(zhuǎn)
  9. 字典轉(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ì)的信息


image.png

此時(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ù)就可以了


image.png

利用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核心代碼摘錄


20180503143111683.png

YYModel核心代碼摘錄


20180503143407891.png

JsonModel json字典轉(zhuǎn)model 摘錄


20180503143454842.png

基本上主流的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)模型的核心方法,

參考:
https://blog.csdn.net/SandyLoo/article/details/80174890

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末念脯,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子弯淘,更是在濱河造成了極大的恐慌绿店,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件庐橙,死亡現(xiàn)場(chǎng)離奇詭異假勿,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)态鳖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門转培,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人浆竭,你說我怎么就攤上這事堡距。” “怎么了兆蕉?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵羽戒,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我虎韵,道長(zhǎng)易稠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任包蓝,我火速辦了婚禮驶社,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘测萎。我一直安慰自己亡电,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布硅瞧。 她就那樣靜靜地躺著份乒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪腕唧。 梳的紋絲不亂的頭發(fā)上或辖,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音枣接,去河邊找鬼颂暇。 笑死,一個(gè)胖子當(dāng)著我的面吹牛但惶,可吹牛的內(nèi)容都是我干的耳鸯。 我是一名探鬼主播湿蛔,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼县爬!你這毒婦竟也來了煌集?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤捌省,失蹤者是張志新(化名)和其女友劉穎苫纤,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體纲缓,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡卷拘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了祝高。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片栗弟。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖工闺,靈堂內(nèi)的尸體忽然破棺而出乍赫,到底是詐尸還是另有隱情,我是刑警寧澤陆蟆,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布雷厂,位于F島的核電站,受9級(jí)特大地震影響叠殷,放射性物質(zhì)發(fā)生泄漏改鲫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一林束、第九天 我趴在偏房一處隱蔽的房頂上張望像棘。 院中可真熱鬧,春花似錦壶冒、人聲如沸缕题。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)烟零。三九已至,卻和暖如春胸嘁,著一層夾襖步出監(jiān)牢的瞬間瓶摆,已是汗流浹背凉逛。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工性宏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人状飞。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓毫胜,卻偏偏與公主長(zhǎng)得像书斜,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子酵使,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345