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会影响到全局的状态,因此最小化竞争条件的出现变得很重要,+load方法能够确保在类的初始化时候调用,这能够保证改变应用行为的一致性,而+initialize在执行时并不提供这种保证,实际上,如果没有直接给这个类发送消息,该方法可能都不会调用到。

1.2 dispatch_once

Swizzling应该只在dispatch_once中完成

如上,由于swizzling会改变全局状态,所以我们需要在运行时采取一些预防措施。原子性就是其中的一种预防措施,因为它能保证不管有多少个线程,代码只会执行一次。GCD的dispatch_once 能够满足这种需求,因此在method swizzling应该将其作为最佳的实践方式。

1.4 调用 _cmd

看起来下面的代码可能导致无限循环:

+(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:则会导致无限循环。「别怕,调用_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阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容