理解Objective-C:Runtime

本文只是整理Runtime中,成员变量、属性、关联对象、方法交换使用实例。不会很细致的讲解Runtime的内容,如果想了解Runtime更多内容,可以移步这里,查看大神们关于Runtime博文。

成员变量和属性

成员变量(Ivar)

定义

1、Ivar:实例变量类型, 其实是一个指向objc_ivar结构体的指针

1
typedef struct objc_ivar *Ivar;
操作方法
1
2
3
4
5
6
7
// 获取成员变量名字
const char * ivar_getName(Ivar v);
// 获取成员变量类型编码
const char * ivar_getTypeEncoding(Ivar v);
//获取成员变量的偏移量
ptrdiff_t ivar_getOffset(Ivar v);
//注:对于id类型或其他类型对象的实例变量,可以调用object_getIvar和object_setIvar直接访问成员变量,而不使用偏移量。
使用实例

User.h

1
2
3
4
5
6
7
8
9
10
11
12
@interface User : NSObject {
NSString *_name;
}
@property NSString *sex;
@property (nonatomic, copy) NSDictionary *dict;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, assign) double tall;
@property (nonatomic, assign) NSUInteger height;
- (void)getIvar;
- (void)getProperty;
@end

User.m

1
2
3
4
5
6
7
8
9
10
11
12
- (void)getIvar{
unsigned int count = 0;
//获取所有的成员变量
Ivar *ivars = class_copyIvarList([User class], &count);
for (unsigned i = 0; i < count; i ++) {
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar); //成员变量名称
const char *type = ivar_getTypeEncoding(ivar); //成员变量类型
NSLog(@"类型 %s 的 %s", type, name);
}
free(ivars); //手动释放
}

注: class_copyIvarList()方法,请移步在另一篇文章:Objective-C Runtime:类和对象

输出结果:

1
2
3
4
5
6
LearnRuntime[836:9888] 类型 @"NSString" 的 _name
LearnRuntime[836:9888] 类型 @"NSString" 的 _sex
LearnRuntime[836:9888] 类型 @"NSDictionary" 的 _dict
LearnRuntime[836:9888] 类型 q 的 _age
LearnRuntime[836:9888] 类型 d 的 _tall
LearnRuntime[836:9888] 类型 Q 的 _height

注:关于输出结果为什么是_name, 请移步《招聘一个靠谱的 iOS》—参考答案(上)- 14题

属性(property)

定义

1、objc_property_t:是表示Objective-C声明的属性的类型,其实是指向objc_property结构体的指针。

1
typedef struct objc_property *objc_property_t;

2、objc_property_attribute_t: 定义了属性的特性,结构体如下:

1
2
3
4
typedef struct {
const char *name; /**< The name of the attribute */
const char *value; /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;
操作方法
1
2
3
4
5
6
7
8
// 获取属性名
const char * property_getName(objc_property_t property);
// 获取属性特性描述字符串
const char * property_getAttributes(objc_property_t property);
// 获取属性中指定的特性
char * property_copyAttributeValue ( objc_property_t property, const char *attributeName );
// 获取属性的特性列表
objc_property_attribute_t * property_copyAttributeList ( objc_property_t property, unsigned int *outCount );

注:property_copyAttributeValue和property_copyAttributeList, 返回值在使用后需要手动释放free();

使用实例

User.m 添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (void)getProperty {
unsigned int count = 0;
// 获取属性列表
objc_property_t * properties = class_copyPropertyList([User class], &count);
for (unsigned i = 0; i < count; i ++) {
objc_property_t property = properties[i];
const char *name = property_getName(property);
const char *propertyAttr = property_getAttributes(property);
NSLog(@"属性描述为%s的%s", propertyAttr, name);
unsigned int attrCount = 0;
//属性的特性列表
objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount);
for (unsigned i = 0; i < attrCount; i ++) {
objc_property_attribute_t attr = attrs[i];
const char *name = attr.name;
const char *value = attr.value;
NSLog(@"属性的特性描述:%s值:%s", name, value);
}
free(attrs);
NSLog(@"\n");
}
free(properties);
}

输出结果

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
LearnRuntime[1190:51044] 属性描述为T@"NSString",&,V_sex的sex
LearnRuntime[1190:51044] 属性的特性描述:T值:@"NSString"
LearnRuntime[1190:51044] 属性的特性描述:&值:
LearnRuntime[1190:51044] 属性的特性描述:V值:_sex
LearnRuntime[1190:51044]
LearnRuntime[1190:51044] 属性描述为T@"NSDictionary",C,N,V_dict的dict
LearnRuntime[1190:51044] 属性的特性描述:T值:@"NSDictionary"
LearnRuntime[1190:51044] 属性的特性描述:C值:
LearnRuntime[1190:51044] 属性的特性描述:N值:
LearnRuntime[1190:51044] 属性的特性描述:V值:_dict
LearnRuntime[1190:51044]
LearnRuntime[1190:51044] 属性描述为Tq,N,V_age的age
LearnRuntime[1190:51044] 属性的特性描述:T值:q
LearnRuntime[1190:51044] 属性的特性描述:N值:
LearnRuntime[1190:51044] 属性的特性描述:V值:_age
LearnRuntime[1190:51044]
LearnRuntime[1190:51044] 属性描述为Td,N,V_tall的tall
LearnRuntime[1190:51044] 属性的特性描述:T值:d
LearnRuntime[1190:51044] 属性的特性描述:N值:
LearnRuntime[1190:51044] 属性的特性描述:V值:_tall
LearnRuntime[1190:51044]
LearnRuntime[1190:51044] 属性描述为TQ,N,V_height的height
LearnRuntime[1190:51044] 属性的特性描述:T值:Q
LearnRuntime[1190:51044] 属性的特性描述:N值:
LearnRuntime[1190:51044] 属性的特性描述:V值:_height

注:objc_property_getAttribute_t结构体包含name和value,属性如下:

1
2
3
4
属性类型  name值:T  										value:变化
编码类型 name值:C(copy) &(strong) W(weak) 空(assign) 等 value:无
非/原子性 name值:空(atomic) N(Nonatomic) value:无
变量名称 name值:V value:变化

关联对象(Associated objects)

定义

Associated objects是Objective-C 2.0运行时一个特性。<objc/runtime.h>中定义三个允许将任何键值在运行时关联到对象上的函数:

1
2
3
objc_setAssociatedObject
objc_getAssociatedObject
objc_removeAssociatedObjects

使用三个函数,开发者可以对已经存在的类扩展中添加自定义的属性

删除属性

如果你尝试使用objc_removeAssociatedObjects()进行删除操作,但官方文档告诉我们不应该手动调用这个函数,通常使用objc_setAssocatedObject方法传入nil值清除关联。关于AssociatedObjects相关知识,请看NShipster上的文章Associated Objects

使用实例

NSObject+AssociatedObject.h

1
2
3
@interface NSObject (AssociatedObject)
@property (nonatomic, strong) id associatedObject;
@end

NSObject+AssociatedObject.m

1
2
3
4
5
6
7
8
@implementation NSObject (AssociatedObject)
@dynamic associatedObject;
- (void)setAssociatedObject:(id)object {
objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)associatedObject {
return objc_getAssociatedObject(self, @selector(associatedObject));
}

方法交换(Method swizzling)

定义

Method Swizzling是改变selector的实际实现的技术,通过它可以在运行时通过修改类的分发表中selector对应的函数,来修改方法的实现。

操作方法

1
2
3
4
5
6
7
8
9
10
11
12
//获取实例方法
class_getInstanceMethod
//获取方法的实现
method_getImplementation
//获取实现的编码类型
method_getTypeEncoding
//给方法添加实现
class_addMethod
//用一个方法的实现替换另一个方法的实
class_replaceMethod
//交换两个方法的实现
method_exchangeImplementations

使用实例

将UIViewController中的ViewWillAppear:方法替换成自定义方法

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
38
39
40
41
#import <objc/runtime.h>
@implementation UIViewController (tracking)

/**
* 理解: [self class] 和 object_getClass(self)区别和联系?
*/

+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{

Class class = [self class]; //注意:如果是类方法,则需要 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);
}

});
}
/**
* 添加自定义的方法
*
* @param animated
*/
- (void)xxx_viewWillAppear: (BOOL) animated {
[self xxx_viewWillAppear:animated]; //这里不会造成死循环,为什么呢?
NSLog(@"viewWillAppear: %@", self);
}
@end

使用method swizzling 修改UIViewController的@selector(viewWillAppear:)对应的函数指针,指向自定义的xxx_viewWillAppear:的实现。具体讲解请看Method Swizzling

小结

文章作为学习笔记,目的是方便以后查看。另外,本人能力有限,如有错误欢迎指正。

参考资料

这里