iOS Runtime part2:應用

本文代碼Demo

1.方法交換

根據不同的系統版本給予不同的圖

@implementation UIImage (exchangeAct)

+(void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method one = class_getClassMethod([self class], @selector(imageNamed:));
        Method two = class_getClassMethod([self class], @selector(pg_imageNamed:));
        method_exchangeImplementations(one, two);
    });
}

+(UIImage *)pg_imageNamed:(NSString *)imageName
{
    double version = [[UIDevice currentDevice].systemVersion doubleValue];
    if (version >= 7.0) {
        imageName = [imageName stringByAppendingString:@"_aft7"];
    }
    return [UIImage pg_imageNamed:imageName];
}

@end

下面是使用method swizzling應該注意的點:

1.1 +load vs. +initialize

Swizzling應該只在load方法中使用

oc會在運行時自動調用每個類的兩個方法,+load 會在類初始化加載的時候調用勾怒;+initialize方法會在程序調用類的第一個實例或者類方法的時候調用晦炊。這兩個方法都是可選的赡茸,只會在實現的時候才去調用拙已。由于method swizzling會影響到全局的狀態(tài)迂卢,因此最小化競爭條件的出現變得很重要恢着,+load方法能夠確保在類的初始化時候調用,這能夠保證改變應用行為的一致性危纫,而+initialize在執(zhí)行時并不提供這種保證宗挥,實際上,如果沒有直接給這個類發(fā)送消息种蝶,該方法可能都不會調用到契耿。

1.2 dispatch_once

Swizzling應該只在dispatch_once中完成

如上,由于swizzling會改變全局狀態(tài)螃征,所以我們需要在運行時采取一些預防措施搪桂。原子性就是其中的一種預防措施,因為它能保證不管有多少個線程盯滚,代碼只會執(zhí)行一次踢械。GCD的dispatch_once 能夠滿足這種需求,因此在method swizzling應該將其作為最佳的實踐方式魄藕。

1.4 調用 _cmd

看起來下面的代碼可能導致無限循環(huán):

+(UIImage *)pg_imageNamed:(NSString *)imageName
{
    double version = [[UIDevice currentDevice].systemVersion doubleValue];
    if (version >= 7.0) {
        imageName = [imageName stringByAppendingString:@"_aft7"];
    }
    return [UIImage pg_imageNamed:imageName];
}

可奇怪的是内列,它并不會。在swizzling的過程中泼疑,pg_imageNamed:已經被重新指向UIImage的原始實現-imageNamed:德绿,但是如果我們在這個方法中調用imageNamed:則會導致無限循環(huán)。「別怕,調用_cmd,其實是調用原函數」

2.關聯屬性

@implementation UITextField (limitLength)

static const void * PGLimitLengthKey = @"PGLimitLengthKey";

-(void)setLimitLength:(NSInteger)limitLength
{
    objc_setAssociatedObject(self, &PGLimitLengthKey, @(limitLength), OBJC_ASSOCIATION_ASSIGN);
}

-(NSInteger)limitLength
{
    return [objc_getAssociatedObject(self, &PGLimitLengthKey) integerValue];
}

@end

這里涉及到了3個函數:

OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);

OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);

OBJC_EXPORT void objc_removeAssociatedObjects(id object)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
Behavior @property Equivalent Description
OBJC_ASSOCIATION_ASSIGN @property (assign) / @property (unsafe_unretained) 弱引用關聯對象
OBJC_ASSOCIATION_RETAIN_NONATOMIC @property (nonatomic, strong) 強引用關聯對象退渗,且為非原子操
OBJC_ASSOCIATION_COPY_NONATOMIC @property (nonatomic, copy) 復制關聯對象移稳,且為非原子操作
OBJC_ASSOCIATION_RETAIN @property (atomic, strong) 強引用關聯對象,且為原子操作
OBJC_ASSOCIATION_COPY @property (atomic, copy) 復制關聯對象会油,且為原子操作

3.字典轉模型

@implementation NSObject (createByDic)

static NSSet * _foundationClasses;

+(NSDictionary *)pg_customKeyDic
{
    return nil;
}
+(NSDictionary *)pg_modelInArray;
{
    return nil;
}

+ (void)load
{
    _foundationClasses = [NSSet setWithObjects:
                          [NSObject class],
                          [NSURL class],
                          [NSDate class],
                          [NSNumber class],
                          [NSDecimalNumber class],
                          [NSData class],
                          [NSMutableData class],
                          [NSArray class],
                          [NSMutableArray class],
                          [NSDictionary class],
                          [NSMutableDictionary class],
                          [NSString class],
                          [NSMutableString class], nil];
}

+ (BOOL)isClassFromFoundation:(Class)c
{
    return [_foundationClasses containsObject:c];
}

+(id)pg_objectWithDic:(NSDictionary *)dic
{
    id thing = [self new];
    Class class = self;
    while (class && [NSObject isClassFromFoundation:class] == NO) {
        unsigned int outCount = 0;
        Ivar * ivars = class_copyIvarList(class, &outCount);
        for (int i = 0; i < outCount; i++) {
            Ivar ivar = ivars[i];
            NSString * propertyName = [[NSString stringWithUTF8String:ivar_getName(ivar)] substringFromIndex:1];
            NSDictionary * customKeyDic = [class pg_customKeyDic];
            NSString * key = @"";
            if (customKeyDic[propertyName]) {
                key = customKeyDic[propertyName];
            }else{
                key = propertyName;
            }
            id value = dic[key];
            if (value == nil) continue;
            
            NSString * type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
            NSRange range = [type rangeOfString:@"@"];
            if (range.location != NSNotFound) {
                
                NSLog(@"-%@-%@-",key,type);
                
                type = [type substringWithRange:NSMakeRange(2, type.length - 3)];
                if (![type hasPrefix:@"NS"]) {
                    Class class = NSClassFromString(type);
                    value = [class pg_objectWithDic:value];
                }else if ([type isEqualToString:@"NSArray"]) {
                    NSArray * array = (NSArray *)value;
                    NSDictionary * modelInArray = [class pg_modelInArray];
                    NSString * className = modelInArray[propertyName];
                    if (className.length <= 0) {
                        value = @[];
                    }else{
                        Class class = NSClassFromString(modelInArray[propertyName]);
                        NSMutableArray * muArr = [NSMutableArray array];
                        for (int i = 0; i < array.count; i++) {
                            [muArr addObject:[class pg_objectWithDic:value[i]]];
                        }
                        value = [muArr copy];
                    }
                }
            }
            [thing setValue:value forKeyPath:propertyName];
        }
        free(ivars);
        class = [class superclass];
    }
    return thing;
}

@end
NSString * path = [[NSBundle mainBundle] pathForResource:@"model" ofType:@"json"];
NSData * jsonData = [NSData dataWithContentsOfFile:path];  
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:NULL];
User * zc = [User pg_objectWithDic:json];

在真實應用的時候,可能出現如下狀況:
1.服務端給我的鍵名,我們并不喜歡
2.還有就是字典內有數組,數組內的內容要轉成另外一個模型的數組

這要求我們向外暴露:自定義屬性名的方法+指點數組內容轉成什么模型的數組的方法
被轉的模型也要實現這兩個方法

@implementation User

+(NSDictionary *)pg_customKeyDic
{
    return @{@"userID":@"id"};
}

+(NSDictionary *)pg_modelInArray
{
    return @{@"friends":@"Friend"};
}

@end

4.快速歸解檔

@implementation NSObject (quickINandOUT)

static NSSet * _foundationClasses;

+ (void)load
{
    _foundationClasses = [NSSet setWithObjects:
                          [NSObject class],
                          [NSURL class],
                          [NSDate class],
                          [NSNumber class],
                          [NSDecimalNumber class],
                          [NSData class],
                          [NSMutableData class],
                          [NSArray class],
                          [NSMutableArray class],
                          [NSDictionary class],
                          [NSMutableDictionary class],
                          [NSString class],
                          [NSMutableString class], nil];
}

+ (BOOL)isClassFromFoundation:(Class)c
{
    return [_foundationClasses containsObject:c];
}

-(void)quickINWithCoder:(NSCoder *)coder
{
    Class class = [self class];
    while (class && [NSObject isClassFromFoundation:class] == NO) {
        unsigned int outCount = 0;
        Ivar * ivars = class_copyIvarList(class, &outCount);
        for (int i = 0; i < outCount; i++) {
            Ivar ivar = ivars[i];
            NSString * key = [[NSString stringWithUTF8String:ivar_getName(ivar)] substringFromIndex:1];
            NSArray * ignorePropertyNameArray = [class ignorePropertyNameArray];
            if ([ignorePropertyNameArray containsObject:key]) continue;
            id value = [self valueForKeyPath:key];
            [coder encodeObject:value forKey:key];
        }
        free(ivars);
        class = [class superclass];
    }
}

-(void)quickOUTWithCoder:(NSCoder *)coder
{
    Class class = [self class];
    while (class && [NSObject isClassFromFoundation:class] == NO) {
        unsigned int outCount = 0;
        Ivar * ivars = class_copyIvarList(class, &outCount);
        for (int i = 0; i < outCount; i++) {
            Ivar ivar = ivars[i];
            NSString * key = [[NSString stringWithUTF8String:ivar_getName(ivar)] substringFromIndex:1];
            NSArray * ignorePropertyNameArray = [class ignorePropertyNameArray];
            if ([ignorePropertyNameArray containsObject:key]) continue;
            id value = [coder decodeObjectForKey:key];
            [self setValue:value forKey:key];
        }
        free(ivars);
        class = [class superclass];
    }
}

在真實應用的時候,可能出現如下狀況:
有的屬性我們并不想參與歸檔解檔

這要求我們的分類向外暴露:忽略的個別屬性名稱的方法
被操作的模型也要實現這個方法

+(NSArray *)ignorePropertyNameArray
{
    return @[@"gemo"];
}

5.KVO 元類切換

_people = [[People alloc]init];
NSLog(@"%@,%@",[_people class],object_getClass(_people));
[_people addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
NSLog(@"%@,%@",[_people class],object_getClass(_people));

打印

2017-03-17 18:04:05.698 ZCRuntimeTrain[96168:1645690] People,People
2017-03-17 18:04:05.698 ZCRuntimeTrain[96168:1645690] People,NSKVONotifying_People

這已經看出,object_getClass(_people)[_people class]是不一樣的

除了isa被切換之外,
在這個新類里面重寫被觀察的對象四個方法个粱。class,setter翻翩,dealloc都许,_isKVOA

_user = [[People alloc]init];
NSLog(@"%@",object_getClass(_people));
[self logMethods];
[_people addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];NSLog(@"%@",object_getClass(_people));
[self logMethods];
-(void)logMethods{
    unsigned int outCount = 0;
    Method * methodList = class_copyMethodList(object_getClass(_user), &outCount);
    for(int i = 0; i < outCount; i++) {
        NSLog(@"Method-%@",NSStringFromSelector(method_getName(methodList[i])));
    }
}
People
Method-.cxx_destruct
Method-name
Method-setName:

NSKVONotifying_People
Method-setName:
Method-class
Method-dealloc
 Method-_isKVOA

5.1 重寫class方法

重寫class方法是為了我們調用它的時候返回跟重寫繼承類之前同樣的內容

5.2. 重寫setter方法

在新的類中會重寫對應的set方法,是為了在set方法中增加另外兩個方法的調用:

- (void)willChangeValueForKey:(NSString *)key
- (void)didChangeValueForKey:(NSString *)key

當然就是不用set方法,而直接用KVC,willChangeValueForKey+didChangeValueForKey也是會被調用的

5.3 重寫dealloc方法

銷毀新生成的NSKVONotifying_類

5.4 重寫_isKVOA方法

這個私有方法估計可能是用來標示該類是一個KVO機制聲稱的類

這當然也給我以警醒,不要用class的方法來判斷實例是什么類,而要用->isa來判斷


文章參考:

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末嫂冻,一起剝皮案震驚了整個濱河市胶征,隨后出現的幾起案子,更是在濱河造成了極大的恐慌桨仿,老刑警劉巖睛低,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異服傍,居然都是意外死亡钱雷,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進店門吹零,熙熙樓的掌柜王于貴愁眉苦臉地迎上來罩抗,“玉大人,你說我怎么就攤上這事灿椅√椎伲” “怎么了?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵阱扬,是天一觀的道長泣懊。 經常有香客問我,道長麻惶,這世上最難降的妖魔是什么馍刮? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮窃蹋,結果婚禮上卡啰,老公的妹妹穿的比我還像新娘。我一直安慰自己警没,他們只是感情好匈辱,可當我...
    茶點故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著杀迹,像睡著了一般亡脸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天浅碾,我揣著相機與錄音大州,去河邊找鬼。 笑死垂谢,一個胖子當著我的面吹牛厦画,可吹牛的內容都是我干的。 我是一名探鬼主播滥朱,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼根暑,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了徙邻?” 一聲冷哼從身側響起排嫌,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎缰犁,沒想到半個月后躏率,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡民鼓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年薇芝,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丰嘉。...
    茶點故事閱讀 40,110評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡夯到,死狀恐怖,靈堂內的尸體忽然破棺而出饮亏,到底是詐尸還是另有隱情耍贾,我是刑警寧澤,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布路幸,位于F島的核電站荐开,受9級特大地震影響,放射性物質發(fā)生泄漏简肴。R本人自食惡果不足惜晃听,卻給世界環(huán)境...
    茶點故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望砰识。 院中可真熱鬧能扒,春花似錦、人聲如沸辫狼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽膨处。三九已至见秤,卻和暖如春砂竖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鹃答。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工晦溪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人挣跋。 一個月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像狞换,于是被迫代替她去往敵國和親避咆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,047評論 2 355

推薦閱讀更多精彩內容