Runtime简单应用

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

关联对象的应用

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//设置关联对象 
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实现添加属性

我们举例第二种:

1
2
3
4
5
6
7
#import <Foundation/Foundation.h>

@interface NSArray (PPS)

@property (nonatomic, copy) NSString *myString;

@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#import "NSArray+PPS.h"
#import <objc/runtime.h>

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的方式

1
2
3
4
5
6
7
#import <UIKit/UIKit.h>

@interface UIButton (PPS)
//传入点击事件的回调
- (instancetype)initWithFrame:(CGRect)frame callback:(void (^)(UIButton *button))callbackBlock;

@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#import "UIButton+PPS.h"
#import <objc/runtime.h>

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的时候,可以直接处理点击事件

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

成员变量和属性

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

json转model
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
- (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获取,这时候,我们就需要知道成员变量的名称了

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

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

抛砖引玉,到此。。。

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

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