Runtime 实战应用举例

1,732 阅读4分钟
原文链接: ppsheep.com

之前的一篇文章,我们讲解了Runtime的一些基础知识,接下来,我会讲一些怎么来运用这种Runtime机制,用到实际的编码中,有哪些情况下,我们需要用到这种机制

关联对象的应用

一般的,我们都在类声明中添加属性,但是出于某种原因,我们需要在分类中添加属性,但是分类中只能添加方法,不能添加属性,这时候我们Runtime就起到关键性作用了

Runtime提供了三个方法来设置关联对象

//设置关联对象 
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
//获取关联对象
OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
//移除关联对象
OBJC_EXPORT void objc_removeAssociatedObjects(id object)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
    
    
//参数解释
id object 被关联的对象
const void *key 关联的key 必须唯一
id value 关联的对象
objc_AssociationPolicy policy 关联策略
//其中的关联策略就相当于我们的property中的copy assign之类的
OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. 
添加公共属性

两种解决办法:

  1. 继承NSArray,在子类中添加一个属性
  2. 使用分类,利用Runtime实现添加属性

我们举例第二种:

#import 
@interface NSArray (PPS)
@property (nonatomic, copy) NSString *myString;
@end
#import "NSArray+PPS.h"
#import 
char * const MY_STRING = "my_string";
@implementation NSArray (PPS)
-(NSString *)myString{
    id myString = objc_getAssociatedObject(self, MY_STRING);
    return myString;
}
-(void)setMyString:(NSString *)myString{
    objc_setAssociatedObject(self, MY_STRING, myString, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end

这样,我们可以直接用点语法,对属性直接操作

添加私有成员变量

给按钮添加点击事件的回调,不用addtarget的方式

#import 
@interface UIButton (PPS)
//传入点击事件的回调
- (instancetype)initWithFrame:(CGRect)frame callback:(void (^)(UIButton *button))callbackBlock;
@end
#import "UIButton+PPS.h"
#import 
char * CALLBACK_BLOCK_IDENTIFER = "CALLBACK_BLOCK_IDENTIFER";
@interface UIButton()
@property (nonatomic, copy) void (^callbackBlock)(UIButton * button);
@end
@implementation UIButton (PPS)
- (void (^)(UIButton *))callbackBlock {
    return objc_getAssociatedObject(self, CALLBACK_BLOCK_IDENTIFER);
}
- (void)setCallbackBlock:(void (^)(UIButton *))callbackBlock {
    objc_setAssociatedObject(self, CALLBACK_BLOCK_IDENTIFER, callbackBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (instancetype)initWithFrame:(CGRect)frame callback:(void (^)(UIButton *))callbackBlock {
    
    if (self = [super initWithFrame:frame]) {
        self.callbackBlock = callbackBlock;
        [self addTarget:self action:@selector(didClickAction:) forControlEvents:UIControlEventTouchUpInside];
    }
    return self;
}
- (void)didClickAction:(UIButton *)button {
    //想想这里为什么需要使用weak一下
    __weak typeof(self) weakSelf = self;
    weakSelf.callbackBlock(button);
}
@end

我们在初始化button的时候,可以直接处理点击事件

self.btn = [[UIButton alloc] initWithFrame:CGRectMake(100, 100, 100, 50) callback:^(UIButton *button) {
        NSLog(@"点击事件");
    }];

成员变量和属性

这个的使用,运用最广泛的还是在json和model的转换,我们可以通过Runtime机制,将model中的所有成员属性都找出来,然后将这些成员属性的名称和返回的json字典中对比,查看有哪些匹配,然后纷纷赋值进去

json转model
- (instancetype)initWithDict:(NSDictionary *)dict {
    if (self = [self init]) {
        //(1)获取类的属性及属性对应的类型
        NSMutableArray * keys = [NSMutableArray array];
        NSMutableArray * attributes = [NSMutableArray array];
        /*
         * 例子
         * name = value3 attribute = T@"NSString",C,N,V_value3
         * name = value4 attribute = T^i,N,V_value4
         */
        unsigned int outCount;
        objc_property_t * properties = class_copyPropertyList([self class], &outCount);
        for (int i = 0; i < outCount; i ++) {
            objc_property_t property = properties[i];
            //通过property_getName函数获得属性的名字
            NSString * propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
            [keys addObject:propertyName];
            //通过property_getAttributes函数可以获得属性的名字和@encode编码
            NSString * propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
            [attributes addObject:propertyAttribute];
        }
        //立即释放properties指向的内存
        free(properties);
        //(2)根据类型给属性赋值
        for (NSString * key in keys) {
            if ([dict valueForKey:key] == nil) continue;
            [self setValue:[dict valueForKey:key] forKey:key];
        }
    }
    return self;
}

当然这上面只是最简单的转换,其中还有很多问题待解决

如何识别int等基础类型数据
如何处理nil和Null
json嵌套如何处理

访问私有变量

我们知道,如果成员变量放在了m文件中,就成了私有变量,但是我们依然可以通过Runtime获取,这时候,我们就需要知道成员变量的名称了

Ivar ivar = class_getInstanceVariable([Model class], "_str1");
NSString * str1 = object_getIvar(model, ivar);

OC没有绝对的私有变量和方法,方法当然也可以这样获取出来

抛砖引玉,到此。。。

欢迎大家关注我的公众号,我会定期分享一些我在项目中遇到问题的解决办法和一些iOS实用的技巧,现阶段主要是整理出一些基础的知识记录下来



文章也会同步更新到我的博客:
ppsheep.com