iOS Runtime學(xué)習(xí)筆記 (二) - 實(shí)戰(zhàn)應(yīng)用

iOS runtime實(shí)戰(zhàn)應(yīng)用

iOS runtime 進(jìn)行添加屬性,并支持KVO監(jiān)聽(tīng)

iOS 中category和runtime的AssociatedObject是兩大非常重要的工具:

  1. category可以給既有類直接添加方法
  2. 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)的方法。但重寫的方法可能是在父類中重寫的,也可能是在子類中重寫的):

  1. 復(fù)寫的方法(overridden)并沒(méi)有在目標(biāo)類中實(shí)現(xiàn)(notimplemented),而是在其父類中實(shí)現(xiàn)了。
  2. 這個(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, 分成以下步驟:

  1. json string -> NSDictionary/NSArray<Dict>
  2. 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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 用到的組件 1、通過(guò)CocoaPods安裝 2、第三方類庫(kù)安裝 3、第三方服務(wù) 友盟社會(huì)化分享組件 友盟用戶反饋 ...
    SunnyLeong閱讀 15,171評(píng)論 1 180
  • 又被批評(píng),患者病情加重,希望她沒(méi)事,而我自己再一次證明了自己能力之弱,懷疑還能不能當(dāng)醫(yī)生。 今天最開心的事大概是洗...
    sakurakang1993閱讀 132評(píng)論 0 0
  • 對(duì)android 中網(wǎng)絡(luò)請(qǐng)求的分層改造 目的:做到業(yè)務(wù)數(shù)據(jù)相分離1、http的請(qǐng)求層(數(shù)據(jù)通信層) 主要功能是對(duì)w...
    flyman_namylf閱讀 592評(píng)論 0 1
  • 今日反思 1.去廁所的時(shí)候不帶著手機(jī),要不你就玩啦,就20min沒(méi)了 2.效率,掌握大于一切
    cleddie閱讀 141評(píng)論 0 0
  • #白馬聲慢,我自手書# 總想用文字來(lái)記述我和你的故事,每次提筆卻都不知道如何下手,這次也一樣。也許比起別人華麗的詞...
    果凍橙太好吃了閱讀 796評(píng)論 0 11

友情鏈接更多精彩內(nèi)容