概述 Objc Runtime使得C具有了面向对象能力,在程序运行时创建,检查,修改类、对象和它们的方法。Runtime是C和汇编编写的,这里http://www.opensource.apple.com/source/objc4/ 可以下到苹果维护的开源代码,GNU也有一个开源的runtime版本,他们都努力的保持一致。苹果官方的Runtime编程指南
Runtime函数 Runtime系统是由一系列的函数和数据结构组成的公共接口动态共享库,在/usr/include/objc目录下可以看到头文件,可以用其中一些函数通过C语言实现objectivec中一样的功能。苹果官方文档https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ObjCRuntimeRef/index.html 里有详细的Runtime函数文档。
Class和Object基础数据结构 Class objc/runtime.h中objc_class结构体的定义如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE ;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE ;
struct objc_cache *cache OBJC2_UNAVAILABLE ;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE ;
#endif
} OBJC2_UNAVAILABLE;
objc_ivar_list和objc_method_list的定义1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct objc_ivar_list {
int ivar_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
struct objc_ivar ivar_list[1 ] OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
struct objc_method_list {
struct objc_method_list *obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
struct objc_method method_list[1 ] OBJC2_UNAVAILABLE;
}
objc_object与id objc_object是一个类的实例结构体,objc/objc.h中objc_object是一个类的实例结构体定义如下:1
2
3
4
5
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
typedef struct objc_object *id ;
向object发送消息时,Runtime库会根据object的isa指针找到这个实例object所属于的类,然后在类的方法列表以及父类方法列表寻找对应的方法运行。id是一个objc_object结构类型的指针,这个类型的对象能够转换成任何一种对象。
objc_cache objc_class结构体中的cache字段用于缓存调用过的method。cache指针指向objc_cache结构体,这个结构体的定义如下1
2
3
4
5
struct objc_cache {
unsigned int mask OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1 ] OBJC2_UNAVAILABLE;
};
meta class是一个类对象的类,当向对象发消息,runtime会在这个对象所属类方法列表中查找发送消息对应的方法,但当向类发送消息时,runtime就会在这个类的meta class方法列表里查找。所有的meta class,包括Root class,Superclass,Subclass的isa都指向Root class的meta class,这样能够形成一个闭环。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
void TestMetaClass(id self , SEL _cmd) {
NSLog (@"This objcet is %p" , self );
NSLog (@"Class is %@, super class is %@" , [self class ], [self superclass]);
Class currentClass = [self class ];
for (int i = 0 ; i < 4 ; i++) {
NSLog (@"Following the isa pointer %d times gives %p" , i, currentClass);
currentClass = objc_getClass((__bridge void *)currentClass);
}
NSLog (@"NSObject's class is %p" , [NSObject class ]);
NSLog (@"NSObject's meta class is %p" , objc_getClass((__bridge void *)[NSObject class ]));
}
@implementation Test
- (void )ex_registerClassPair {
Class newClass = objc_allocateClassPair([NSError class ], "TestClass" , 0 );
class_addMethod(newClass, @selector (testMetaClass), (IMP)TestMetaClass, "v@:" );
objc_registerClassPair(newClass);
id instance = [[newClass alloc] initWithDomain:@"some domain" code:0 userInfo:nil ];
[instance performSelector:@selector (testMetaClass)];
}
@end
运行结果1
2
3
4
5
6
7
8
2014-10-20 22:57:07.352 mountain[1303:41490] This objcet is 0x7a6e22b0
2014-10-20 22:57:07.353 mountain[1303:41490] Class is TestStringClass, super class is NSError
2014-10-20 22:57:07.353 mountain[1303:41490] Following the isa pointer 0 times gives 0x7a6e21b0
2014-10-20 22:57:07.353 mountain[1303:41490] Following the isa pointer 1 times gives 0x0
2014-10-20 22:57:07.353 mountain[1303:41490] Following the isa pointer 2 times gives 0x0
2014-10-20 22:57:07.353 mountain[1303:41490] Following the isa pointer 3 times gives 0x0
2014-10-20 22:57:07.353 mountain[1303:41490] NSObject's class is 0xe10000
2014-10-20 22:57:07.354 mountain[1303:41490] NSObject's meta class is 0x0
举个例子1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@interface Sark : NSObject
@end
@implementation Sark
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
BOOL res1 = [(id )[NSObject class ] isKindOfClass:[NSObject class ]];
BOOL res2 = [(id )[NSObject class ] isMemberOfClass:[NSObject class ]];
BOOL res3 = [(id )[Sark class ] isKindOfClass:[Sark class ]];
BOOL res4 = [(id )[Sark class ] isMemberOfClass:[Sark class ]];
NSLog (@"%d %d %d %d" , res1, res2, res3, res4);
}
return 0 ;
}
2014 -11 -05 14 :45 :08.474 Test[9412 :721945 ] 1 0 0 0
先看看isKindOfClass和isMemberOfClass在Object.mm中的实现1
2
3
4
5
6
7
8
9
10
11
12
13
- (BOOL )isKindOf:aClass
{
Class cls;
for (cls = isa; cls; cls = cls->superclass)
if (cls == (Class)aClass)
return YES ;
return NO ;
}
- (BOOL )isMemberOf:aClass
{
return isa == (Class)aClass;
}
res1中,可以从isKindOfClass看出NSObject class的isa第一次会指向NSObject的Meta Class,接着Super class时会NSObject的Meta Class根据前面讲的闭环可以知道是会指到NSObject class,这样res1的bool值就是真了。
res2的话因为是isMemberOf就只有一次,那么是NSObject的Meta Class和NSObject class不同返回的bool值就是false了。
res3第一次是Sark Meta Class,第二次super class 后就是NSObject Meta Class了,返回也是false
res4是Sark Meta Class,所以返回也是false
类与对象操作函数 runtime有很多的函数可以操作类和对象。类相关的是class为前缀,对象相关操作是objc或object_为前缀。
类相关操作函数 name 1
2
const char * class_getName ( Class cls );
1
2
3
4
5
Class class_getSuperclass ( Class cls );
BOOL class_isMetaClass ( Class cls );
instance_size 1
2
size_t class_getInstanceSize ( Class cls );
成员变量(ivars)及属性 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
Ivar class_getInstanceVariable ( Class cls, const char *name );
Ivar class_getClassVariable ( Class cls, const char *name );
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
Ivar class_getInstanceVariable ( Class cls, const char *name );
Ivar class_getClassVariable ( Class cls, const char *name );
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
methodLists 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );
Method class_getInstanceMethod ( Class cls, SEL name );
Method class_getClassMethod ( Class cls, SEL name );
Method * class_copyMethodList ( Class cls, unsigned int *outCount );
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );
BOOL class_respondsToSelector ( Class cls, SEL sel );
objc_protocol_list 1
2
3
4
5
6
7
8
BOOL class_addProtocol ( Class cls, Protocol *protocol );
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );
version 1
2
3
4
5
int class_getVersion ( Class cls );
void class_setVersion ( Class cls, int version );
实例 通过实例来消化下上面的那些函数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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
@interface MyClass : NSObject <NSCopying , NSCoding >
@property (nonatomic , strong ) NSArray *array;
@property (nonatomic , copy ) NSString *string;
- (void )method1;
- (void )method2;
+ (void )classMethod1;
@end
#import "MyClass.h"
@interface MyClass () {
NSInteger _instance1;
NSString * _instance2;
}
@property (nonatomic , assign ) NSUInteger integer;
- (void )method3WithArg1:(NSInteger )arg1 arg2:(NSString *)arg2;
@end
@implementation MyClass
+ (void )classMethod1 {
}
- (void )method1 {
NSLog (@"call method method1" );
}
- (void )method2 {
}
- (void )method3WithArg1:(NSInteger )arg1 arg2:(NSString *)arg2 {
NSLog (@"arg1 : %ld, arg2 : %@" , arg1, arg2);
}
@end
#import "MyClass.h"
#import "MySubClass.h"
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyClass *myClass = [[MyClass alloc] init];
unsigned int outCount = 0 ;
Class cls = myClass.class;
NSLog (@"class name: %s" , class_getName(cls));
NSLog (@"==========================================================" );
NSLog (@"super class name: %s" , class_getName(class_getSuperclass(cls)));
NSLog (@"==========================================================" );
NSLog (@"MyClass is %@ a meta-class" , (class_isMetaClass(cls) ? @"" : @"not" ));
NSLog (@"==========================================================" );
Class meta_class = objc_getMetaClass(class_getName(cls));
NSLog (@"%s's meta-class is %s" , class_getName(cls), class_getName(meta_class));
NSLog (@"==========================================================" );
NSLog (@"instance size: %zu" , class_getInstanceSize(cls));
NSLog (@"==========================================================" );
Ivar *ivars = class_copyIvarList(cls, &outCount);
for (int i = 0 ; i < outCount; i++) {
Ivar ivar = ivars[i];
NSLog (@"instance variable's name: %s at index: %d" , ivar_getName(ivar), i);
}
free(ivars);
Ivar string = class_getInstanceVariable(cls, "_string" );
if (string != NULL ) {
NSLog (@"instace variable %s" , ivar_getName(string));
}
NSLog (@"==========================================================" );
objc_property_t * properties = class_copyPropertyList(cls, &outCount);
for (int i = 0 ; i < outCount; i++) {
objc_property_t property = properties[i];
NSLog (@"property's name: %s" , property_getName(property));
}
free(properties);
objc_property_t array = class_getProperty(cls, "array" );
if (array != NULL ) {
NSLog (@"property %s" , property_getName(array));
}
NSLog (@"==========================================================" );
Method *methods = class_copyMethodList(cls, &outCount);
for (int i = 0 ; i < outCount; i++) {
Method method = methods[i];
NSLog (@"method's signature: %s" , method_getName(method));
}
free(methods);
Method method1 = class_getInstanceMethod(cls, @selector (method1));
if (method1 != NULL ) {
NSLog (@"method %s" , method_getName(method1));
}
Method classMethod = class_getClassMethod(cls, @selector (classMethod1));
if (classMethod != NULL ) {
NSLog (@"class method : %s" , method_getName(classMethod));
}
NSLog (@"MyClass is%@ responsd to selector: method3WithArg1:arg2:" , class_respondsToSelector(cls, @selector (method3WithArg1:arg2:)) ? @"" : @" not" );
IMP imp = class_getMethodImplementation(cls, @selector (method1));
imp();
NSLog (@"==========================================================" );
Protocol * __unsafe_unretained * protocols = class_copyProtocolList(cls, &outCount);
Protocol * protocol;
for (int i = 0 ; i < outCount; i++) {
protocol = protocols[i];
NSLog (@"protocol name: %s" , protocol_getName(protocol));
}
NSLog (@"MyClass is%@ responsed to protocol %s" , class_conformsToProtocol(cls, protocol) ? @"" : @" not" , protocol_getName(protocol));
NSLog (@"==========================================================" );
}
return 0 ;
}
输出结果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
2014-10-22 19:41:37.452 RuntimeTest[3189:156810] class name: MyClass
2014-10-22 19:41:37.453 RuntimeTest[3189:156810] ==========================================================
2014-10-22 19:41:37.454 RuntimeTest[3189:156810] super class name: NSObject
2014-10-22 19:41:37.454 RuntimeTest[3189:156810] ==========================================================
2014-10-22 19:41:37.454 RuntimeTest[3189:156810] MyClass is not a meta-class
2014-10-22 19:41:37.454 RuntimeTest[3189:156810] ==========================================================
2014-10-22 19:41:37.454 RuntimeTest[3189:156810] MyClass's meta-class is MyClass
2014-10-22 19:41:37.455 RuntimeTest[3189:156810] ==========================================================
2014-10-22 19:41:37.455 RuntimeTest[3189:156810] instance size: 48
2014-10-22 19:41:37.455 RuntimeTest[3189:156810] ==========================================================
2014-10-22 19:41:37.455 RuntimeTest[3189:156810] instance variable's name: _instance1 at index: 0
2014-10-22 19:41:37.455 RuntimeTest[3189:156810] instance variable's name: _instance2 at index: 1
2014-10-22 19:41:37.455 RuntimeTest[3189:156810] instance variable's name: _array at index: 2
2014-10-22 19:41:37.455 RuntimeTest[3189:156810] instance variable's name: _string at index: 3
2014-10-22 19:41:37.463 RuntimeTest[3189:156810] instance variable's name: _integer at index: 4
2014-10-22 19:41:37.463 RuntimeTest[3189:156810] instace variable _string
2014-10-22 19:41:37.463 RuntimeTest[3189:156810] ==========================================================
2014-10-22 19:41:37.463 RuntimeTest[3189:156810] property's name: array
2014-10-22 19:41:37.463 RuntimeTest[3189:156810] property's name: string
2014-10-22 19:41:37.464 RuntimeTest[3189:156810] property's name: integer
2014-10-22 19:41:37.464 RuntimeTest[3189:156810] property array
2014-10-22 19:41:37.464 RuntimeTest[3189:156810] ==========================================================
2014-10-22 19:41:37.464 RuntimeTest[3189:156810] method's signature: method1
2014-10-22 19:41:37.464 RuntimeTest[3189:156810] method's signature: method2
2014-10-22 19:41:37.464 RuntimeTest[3189:156810] method's signature: method3WithArg1:arg2:
2014-10-22 19:41:37.465 RuntimeTest[3189:156810] method's signature: integer
2014-10-22 19:41:37.465 RuntimeTest[3189:156810] method's signature: setInteger:
2014-10-22 19:41:37.465 RuntimeTest[3189:156810] method's signature: array
2014-10-22 19:41:37.465 RuntimeTest[3189:156810] method's signature: string
2014-10-22 19:41:37.465 RuntimeTest[3189:156810] method's signature: setString:
2014-10-22 19:41:37.465 RuntimeTest[3189:156810] method's signature: setArray:
2014-10-22 19:41:37.466 RuntimeTest[3189:156810] method's signature: .cxx_destruct
2014-10-22 19:41:37.466 RuntimeTest[3189:156810] method method1
2014-10-22 19:41:37.466 RuntimeTest[3189:156810] class method : classMethod1
2014-10-22 19:41:37.466 RuntimeTest[3189:156810] MyClass is responsd to selector: method3WithArg1:arg2:
2014-10-22 19:41:37.467 RuntimeTest[3189:156810] call method method1
2014-10-22 19:41:37.467 RuntimeTest[3189:156810] ==========================================================
2014-10-22 19:41:37.467 RuntimeTest[3189:156810] protocol name: NSCopying
2014-10-22 19:41:37.467 RuntimeTest[3189:156810] protocol name: NSCoding
2014-10-22 19:41:37.467 RuntimeTest[3189:156810] MyClass is responsed to protocol NSCoding
2014-10-22 19:41:37.468 RuntimeTest[3189:156810] ======================================
动态创建类和对象 动态创建类 1
2
3
4
5
6
7
8
Class objc_allocateClassPair ( Class superclass, const char *name, size_t extraBytes );
void objc_disposeClassPair ( Class cls );
void objc_registerClassPair ( Class cls );
使用实例1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Class cls = objc_allocateClassPair(MyClass.class, "MySubClass" , 0 );
class_addMethod(cls, @selector (submethod1), (IMP)imp_submethod1, "v@:" );
class_replaceMethod(cls, @selector (method1), (IMP)imp_submethod1, "v@:" );
class_addIvar(cls, "_ivar1" , sizeof (NSString *), log(sizeof (NSString *)), "i" );
objc_property_attribute_t type = {"T" , "@\"NSString\"" };
objc_property_attribute_t ownership = { "C" , "" };
objc_property_attribute_t backingivar = { "V" , "_ivar1" };
objc_property_attribute_t attrs[] = {type, ownership, backingivar};
class_addProperty(cls, "property2" , attrs, 3 );
objc_registerClassPair(cls);
id instance = [[cls alloc] init];
[instance performSelector:@selector (submethod1)];
[instance performSelector:@selector (method1)];
输出1
2
2014 -10 -23 11 :35 :31.006 RuntimeTest[3800 :66152 ] run sub method 1
2014 -10 -23 11 :35 :31.006 RuntimeTest[3800 :66152 ] run sub method 1
动态创建对象 1
2
3
4
5
6
7
8
id class_createInstance ( Class cls, size_t extraBytes );
id objc_constructInstance ( Class cls, void *bytes );
void * objc_destructInstance ( id obj );
测试下效果1
2
3
4
5
6
id theObject = class_createInstance(NSString .class, sizeof (unsigned ));
id str1 = [theObject init];
NSLog (@"%@" , [str1 class ]);
id str2 = [[NSString alloc] initWithString:@"test" ];
NSLog (@"%@" , [str2 class ]);
输出结果1
2
2014 -10 -23 12 :46 :50.781 RuntimeTest[4039 :89088 ] NSString
2014 -10 -23 12 :46 :50.781 RuntimeTest[4039 :89088 ] __NSCFConstantString
实例操作函数 这些函数是针对创建的实例对象的一系列操作函数。
整个对象操作的函数 1
2
3
4
id object_copy ( id obj, size_t size );
id object_dispose ( id obj );
应用场景1
2
3
4
5
NSObject *a = [[NSObject alloc] init];
id newB = object_copy(a, class_getInstanceSize(MyClass.class));
object_setClass(newB, MyClass.class);
object_dispose(a);
对象实例变量进行操作的函数 1
2
3
4
5
6
7
8
9
10
Ivar object_setInstanceVariable ( id obj, const char *name, void *value );
Ivar object_getInstanceVariable ( id obj, const char *name, void **outValue );
void * object_getIndexedIvars ( id obj );
id object_getIvar ( id obj, Ivar ivar );
void object_setIvar ( id obj, Ivar ivar, id value );
对对象类操作 1
2
3
4
5
6
const char * object_getClassName ( id obj );
Class object_getClass ( id obj );
Class object_setClass ( id obj, Class cls );
获取类定义 1
2
3
4
5
6
7
8
9
10
11
12
13
int objc_getClassList ( Class *buffer, int bufferCount );
Class * objc_copyClassList ( unsigned int *outCount );
Class objc_lookUpClass ( const char *name );
Class objc_getClass ( const char *name );
Class objc_getRequiredClass ( const char *name );
Class objc_getMetaClass ( const char *name );
演示如何使用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int numClasses;
Class * classes = NULL ;
numClasses = objc_getClassList(NULL , 0 );
if (numClasses > 0 ) {
classes = malloc(sizeof (Class) * numClasses);
numClasses = objc_getClassList(classes, numClasses);
NSLog (@"number of classes: %d" , numClasses);
for (int i = 0 ; i < numClasses; i++) {
Class cls = classes[i];
NSLog (@"class name: %s" , class_getName(cls));
}
free(classes);
}
结果如下:1
2
3
4
5
6
7
8
9
10
11
12
2014 -10 -23 16 :20 :52.589 RuntimeTest[8437 :188589 ] number of classes: 1282
2014 -10 -23 16 :20 :52.589 RuntimeTest[8437 :188589 ] class name: DDTokenRegexp
2014 -10 -23 16 :20 :52.590 RuntimeTest[8437 :188589 ] class name: _NSMostCommonKoreanCharsKeySet
2014 -10 -23 16 :20 :52.590 RuntimeTest[8437 :188589 ] class name: OS_xpc_dictionary
2014 -10 -23 16 :20 :52.590 RuntimeTest[8437 :188589 ] class name: NSFileCoordinator
2014 -10 -23 16 :20 :52.590 RuntimeTest[8437 :188589 ] class name: NSAssertionHandler
2014 -10 -23 16 :20 :52.590 RuntimeTest[8437 :188589 ] class name: PFUbiquityTransactionLogMigrator
2014 -10 -23 16 :20 :52.591 RuntimeTest[8437 :188589 ] class name: NSNotification
2014 -10 -23 16 :20 :52.591 RuntimeTest[8437 :188589 ] class name: NSKeyValueNilSetEnumerator
2014 -10 -23 16 :20 :52.591 RuntimeTest[8437 :188589 ] class name: OS_tcp_connection_tls_session
2014 -10 -23 16 :20 :52.591 RuntimeTest[8437 :188589 ] class name: _PFRoutines
......还有大量输出
成员变量与属性 基础数据类型 Ivar 实例变量类型,指向objc_ivar结构体的指针,ivar指针地址是根据class结构体的地址加上基地址偏移字节得到的。1
2
3
4
5
6
7
8
9
10
typedef struct objc_ivar *Ivar;
struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE;
char *ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
objc_property_t 属性类型,指向objc_property结构体1
typedef struct objc_property *objc_property_t;
通过class_copyPropertyList和protocol_copyPropertyList方法获取类和协议的属性1
2
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)
示例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
@interface Lender : NSObject {
float alone;
}
@property float alone;
@end
id LenderClass = objc_getClass("Lender" );
unsigned int outCount;
objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
const char *property_getName(objc_property_t property)
objc_property_t class_getProperty(Class cls, const char *name)
objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty)
const char *property_getAttributes(objc_property_t property)
id LenderClass = objc_getClass("Lender" );
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
for (i = 0 ; i < outCount; i++) {
objc_property_t property = properties[i];
fprintf(stdout, "%s %s\n" , property_getName(property), property_getAttributes(property));
}
objc_property_attribute_t 也是结构体,定义属性的attribute1
2
3
4
typedef struct {
const char *name;
const char *value;
} objc_property_attribute_t;
示例 下面代码会编译出错,Runtime Crash还是正常输出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
@interface Sark : NSObject
@property (nonatomic , copy ) NSString *name;
@end
@implementation Sark
- (void )speak
{
NSLog (@"my name is %@" , self .name);
}
@end
@interface Test : NSObject
@end
@implementation Test
- (instancetype )init
{
self = [super init];
if (self ) {
id cls = [Sark class ];
void *obj = &cls;
[(__bridge id )obj speak];
}
return self ;
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
[[Test alloc] init];
}
return 0 ;
}
2014 -11 -07 14 :08 :25.698 Test[1097 :57255 ] my name is
obj为指向Sark Class的指针,相当于Sark的实例对象但是还是不一样,根据objc_msgSend流程,obj指针能够在方法列表中找到speak方法,所以运行正常。
为了得到self.name能够输出的原因,可以加入调试代码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
- (void )speak
{
unsigned int numberOfIvars = 0 ;
Ivar *ivars = class_copyIvarList([self class ], &numberOfIvars);
for (const Ivar *p = ivars; p < ivars+numberOfIvars; p++) {
Ivar const ivar = *p;
ptrdiff_t offset = ivar_getOffset(ivar);
const char *name = ivar_getName(ivar);
NSLog (@"Sark ivar name = %s, offset = %td" , name, offset);
}
NSLog (@"my name is %p" , &_name);
NSLog (@"my name is %@" , *(&_name));
}
@implementation Test
- (instancetype )init
{
self = [super init];
if (self ) {
NSLog (@"Test instance = %@" , self );
void *self2 = (__bridge void *)self ;
NSLog (@"Test instance pointer = %p" , &self2);
id cls = [Sark class ];
NSLog (@"Class instance address = %p" , cls);
void *obj = &cls;
NSLog (@"Void *obj = %@" , obj);
[(__bridge id )obj speak];
}
return self ;
}
@end
2014 -11 -11 00 :56 :02.464 Test[10475 :1071029 ] Test instance = 2014 -11 -11 00 :56 :02.464 Test[10475 :1071029 ] Test instance pointer = 0x7fff5fbff7c8
2014 -11 -11 00 :56 :02.465 Test[10475 :1071029 ] Class instance address = 0x1000023c8
2014 -11 -11 00 :56 :02.465 Test[10475 :1071029 ] Void *obj = 2014 -11 -11 00 :56 :02.465 Test[10475 :1071029 ] Sark ivar name = _name, offset = 8
2014 -11 -11 00 :56 :02.465 Test[10475 :1071029 ] my name is 0x7fff5fbff7c8
2014 -11 -11 00 :56 :02.465 Test[10475 :1071029 ] my name is
Sark中Propertyname会被转换成ivar到类的结构里,runtime会计算ivar的地址偏移来找ivar的最终地址,根据输出可以看出Sark class的指针地址加上ivar的偏移量正好跟Test对象指针地址。
关联对象 关联对象是在运行时添加的类似成员。1
2
3
4
5
6
static char myKey;
objc_setAssociatedObject(self , &myKey, anObject, OBJC_ASSOCIATION_RETAIN);
id anObject = objc_getAssociatedObject(self , &myKey);
实例演示关联对象使用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
- (void )setTapActionWithBlock:(void (^)(void ))block
{
UITapGestureRecognizer *gesture = objc_getAssociatedObject(self , &kDTActionHandlerTapGestureKey);
if (!gesture)
{
gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector (__handleActionForTapGesture:)];
[self addGestureRecognizer:gesture];
objc_setAssociatedObject(self , &kDTActionHandlerTapGestureKey, gesture, OBJC_ASSOCIATION_RETAIN);
}
objc_setAssociatedObject(self , &kDTActionHandlerTapBlockKey, block, OBJC_ASSOCIATION_COPY);
}
- (void )__handleActionForTapGesture:(UITapGestureRecognizer *)gesture
{
if (gesture.state == UIGestureRecognizerStateRecognized )
{
void (^action)(void ) = objc_getAssociatedObject(self , &kDTActionHandlerTapBlockKey);
if (action)
{
action();
}
}
}
成员变量和属性的操作方法 成员变量 1
2
3
4
5
6
const char * ivar_getName ( Ivar v );
const char * ivar_getTypeEncoding ( Ivar v );
ptrdiff_t ivar_getOffset ( Ivar v );
Associated Objects 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void objc_setAssociatedObject ( id object, const void *key, id value, objc_AssociationPolicy policy );
id objc_getAssociatedObject ( id object, const void *key );
void objc_removeAssociatedObjects ( id object );
enum {
OBJC_ASSOCIATION_ASSIGN = 0 ,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1 ,
OBJC_ASSOCIATION_COPY_NONATOMIC = 3 ,
OBJC_ASSOCIATION_RETAIN = 01401 ,
OBJC_ASSOCIATION_COPY = 01403
};
属性 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 );
实例 两个接口同样数据不同的字段名处理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
42
43
44
@interface MyObject : NSObject
@property (nonatomic , copy ) NSString * name;
@property (nonatomic , copy ) NSString * status;
@end
@{@"name1" : "张三" , @"status1" : @"start" }
@{@"name2" : "张三" , @"status2" : @"end" }
static NSMutableDictionary *map = nil ;
@implementation MyObject
+ (void )load
{
map = [NSMutableDictionary dictionary];
map[@"name1" ] = @"name" ;
map[@"status1" ] = @"status" ;
map[@"name2" ] = @"name" ;
map[@"status2" ] = @"status" ;
}
@end
- (void )setDataWithDic:(NSDictionary *)dic
{
[dic enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
NSString *propertyKey = [self propertyForKey:key];
if (propertyKey)
{
objc_property_t property = class_getProperty([self class ], [propertyKey UTF8String]);
NSString *attributeString = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding ];
...
[self setValue:obj forKey:propertyKey];
}
}];
}
Method和消息 Method和消息的基础数据类型 SEL 选择器表示一个方法的selector的指针,可以理解为Method中的ID类型1
2
3
4
5
6
typedef struct objc_selector *SEL;
SEL sel1 = @selector (method1);
NSLog (@"sel : %p" , sel1);
输出
2014 -10 -30 18 :40 :07.518 RuntimeTest[52734 :466626 ] sel : 0x100002d72
获取SEL的三个方法:
sel_registerName函数
objectivec编译器提供的@selector()
NSSelectorFromString()方法
IMP 是函数指针,指向方法的首地址,通过SEL快速得到对应IMP,这时可以跳过Runtime消息传递机制直接执行函数,比直接向对象发消息高效。定义如下
Method 用于表示类定义中的方法1
2
3
4
5
6
7
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
Method相关操作函数 Method 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
id method_invoke ( id receiver, Method m, ... );
void method_invoke_stret ( id receiver, Method m, ... );
SEL method_getName ( Method m );
IMP method_getImplementation ( Method m );
const char * method_getTypeEncoding ( Method m );
char * method_copyReturnType ( Method m );
char * method_copyArgumentType ( Method m, unsigned int index );
void method_getReturnType ( Method m, char *dst, size_t dst_len );
unsigned int method_getNumberOfArguments ( Method m );
void method_getArgumentType ( Method m, unsigned int index, char *dst, size_t dst_len );
struct objc_method_description * method_getDescription ( Method m );
IMP method_setImplementation ( Method m, IMP imp );
void method_exchangeImplementations ( Method m1, Method m2 );
Method的SEL 1
2
3
4
5
6
7
8
const char * sel_getName ( SEL sel );
SEL sel_registerName ( const char *str );
SEL sel_getUid ( const char *str );
BOOL sel_isEqual ( SEL lhs, SEL rhs );
Method调用流程 消息函数,Objc中发送消息是用中括号把接收者和消息括起来,只到运行时才会把消息和方法实现绑定。1
2
objc_msgSend(receiver, selector, arg1, arg2, ...)
编译器会根据情况在objc_msgSend,objc_msgSend_stret,objc_msgSendSuper,或objc_msgSendSuper_stret四个方法中选一个调用。如果是传递给超类就会调用带super的函数,如果返回是数据结构而不是一个值就会调用带stret的函数。在i386平台返回类型为浮点消息会调用objc_msgSend_fpret函数。
举个例子,NSStringFromClass([self class])和NSStringFromClass([super class])输出都是self的类名。原因如下
调用[self class]的时候先调用objc_msgSend,发现self没有class这个方法,然后用objc_msgSendSuper就去父类找,还是没有,继续用objc_msgSendSuper到NSObject里找,结果找到了,查找NSObject中class方法的runtime源码会发现它会返回self,所以[self class]会返回self本身。同理[super class]相对前者就是少了objc_msgSend这一步,最后也会找到NSObject根类里的class方法,自然结果也是返回了self。
Method中的接收消息对象参数和方法选择器参数 在Method中使用self关键字来引用实例本身,self的内容即接收消息的对象是在Method运行时被传入的同时还有方法选择器。
获取Method地址 使用NSObject提供的methodForSelector:方法可以获得Method的指针,通过指针调用实现代码。1
2
3
4
5
6
void (*setter )(id , SEL, BOOL );
int i;
setter = (void (*)(id , SEL, BOOL ))[target
methodForSelector:@selector (setFilled:)];
for ( i = 0 ; i < 1000 ; i++ )
setter (targetList[i], @selector (setFilled:), YES );
Method转发 如果使用[object message]调用方法,object无法响应message时就会报错。用performSelector…调用就要等到运行时才确定是否能接受,不能才崩溃。1
2
3
4
if ([self respondsToSelector:@selector (method)]) {
[self performSelector:@selector (method)];
}
Method转发机制分为三步:
动态方法解析 1
2
3
4
5
6
7
8
9
10
11
void functionForMethod1(id self , SEL _cmd) {
NSLog (@"%@, %p" , self , _cmd);
}
+ (BOOL )resolveInstanceMethod:(SEL)sel {
NSString *selectorString = NSStringFromSelector (sel);
if ([selectorString isEqualToString:@"method1" ]) {
class_addMethod(self .class, @selector (method1), (IMP)functionForMethod1, "@:" );
}
return [super resolveInstanceMethod:sel];
}
可以动态的提供一个方法的实现。例如可以用@dynamic关键字在类的实现文件中写个属性1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@dynamic propertyName;
void dynamicMethodIMP(id self , SEL _cmd) {
}
@implementation MyClass
+ (BOOL )resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector (resolveThisMethodDynamically)) {
class_addMethod([self class ], aSEL, (IMP) dynamicMethodIMP, "v@:" );
return YES ;
}
return [super resolveInstanceMethod:aSEL];
}
@end
重定向接收者 如果无法处理消息会继续调用下面的方法,同时在这里Runtime系统实际上是给了一个替换消息接收者的机会,但是替换的对象千万不要是self,那样会进入死循环。1
2
3
4
5
6
7
- (id )forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector (mysteriousMethod:)){
return alternateObject;
}
return [super forwardingTargetForSelector:aSelector];
}
使用这个方法通常在对象内部,如下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
42
43
44
@interface SUTRuntimeMethodHelper : NSObject
- (void )method2;
@end
@implementation SUTRuntimeMethodHelper
- (void )method2 {
NSLog (@"%@, %p" , self , _cmd);
}
@end
#pragma mark -
@interface SUTRuntimeMethod () {
SUTRuntimeMethodHelper *_helper;
}
@end
@implementation SUTRuntimeMethod
+ (instancetype )object {
return [[self alloc] init];
}
- (instancetype )init {
self = [super init];
if (self != nil ) {
_helper = [[SUTRuntimeMethodHelper alloc] init];
}
return self ;
}
- (void )test {
[self performSelector:@selector (method2)];
}
- (id )forwardingTargetForSelector:(SEL)aSelector {
NSLog (@"forwardingTargetForSelector" );
NSString *selectorString = NSStringFromSelector (aSelector);
if ([selectorString isEqualToString:@"method2" ]) {
return _helper;
}
return [super forwardingTargetForSelector:aSelector];
}
@end
最后进行转发 如果以上两种都没法处理未知消息就需要完整消息转发了。调用如下方法1
2
3
4
- (void )forwardInvocation:(NSInvocation *)anInvocation
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
范例1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
if ([SUTRuntimeMethodHelper instancesRespondToSelector:aSelector]) {
signature = [SUTRuntimeMethodHelper instanceMethodSignatureForSelector:aSelector];
}
}
return signature;
}
- (void )forwardInvocation:(NSInvocation *)anInvocation {
if ([SUTRuntimeMethodHelper instancesRespondToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:_helper];
}
}
转发和多继承 转发和继承相似,一个Object把消息转发出去就好像它继承了另一个Object的方法一样。
Message消息的参考文章
Method Swizzling 是改变一个selector实际实现的技术,可以在运行时修改selector对应的函数来修改Method的实现。前面的消息转发很强大,但是需要能够修改对应类的源码,但是对于有些类无法修改其源码时又要更改其方法实现时可以使用Method Swizzling,通过重新映射方法来达到目的,但是跟消息转发比起来调试会困难。
使用method swizzling需要注意的问题
Swizzling应该总在+load中执行:objectivec在运行时会自动调用类的两个方法+load和+initialize。+load会在类初始加载时调用,和+initialize比较+load能保证在类的初始化过程中被加载
Swizzling应该总是在dispatch_once中执行:swizzling会改变全局状态,所以在运行时采取一些预防措施,使用dispatch_once就能够确保代码不管有多少线程都只被执行一次。这将成为method swizzling的最佳实践。
Selector,Method和Implementation:这几个之间关系可以这样理解,一个类维护一个运行时可接收的消息分发表,分发表中每个入口是一个Method,其中key是一个特定的名称,及SEL,与其对应的实现是IMP即指向底层C函数的指针。
举例说明如何使用Method Swizzling对一个类中注入一些我们的新的操作。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
42
#import <objc/runtime.h>
@implementation UIViewController (Tracking )
+ (void )load {
static dispatch_once_t onceToken;
dispatch_once (&onceToken, ^{
Class class = [self class ];
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);
}
});
}
#pragma mark - Method Swizzling
- (void )xxx_viewWillAppear:(BOOL )animated {
[self xxx_viewWillAppear:animated];
NSLog (@"viewWillAppear: %@" , self );
}
@end
method_exchangeImplementations做的事情和如下代码是一样的1
2
3
4
IMP imp1 = method_getImplementation(m1);
IMP imp2 = method_getImplementation(m2);
method_setImplementation(m1, imp2);
method_setImplementation(m2, imp1);
另一种Method Swizzling的实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void )replacementReceiveMessage:(const struct BInstantMessage *)arg1 {
NSLog (@"arg1 is %@" , arg1);
[self replacementReceiveMessage:arg1];
}
+ (void )load {
SEL originalSelector = @selector (ReceiveMessage:);
SEL overrideSelector = @selector (replacementReceiveMessage:);
Method originalMethod = class_getInstanceMethod(self , originalSelector);
Method overrideMethod = class_getInstanceMethod(self , overrideSelector);
if (class_addMethod(self , originalSelector, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod))) {
class_replaceMethod(self , overrideSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, overrideMethod);
}
}
这里有几个关于Method Swizzling的资源可以参考
Protocol和Category 基础数据类型 Category 指向分类的结构体的指针1
2
3
4
5
6
7
8
9
typedef struct objc_category *Category;
struct objc_category {
char *category_name OBJC2_UNAVAILABLE;
char *class_name OBJC2_UNAVAILABLE;
struct objc_method_list *instance_methods OBJC2_UNAVAILABLE;
struct objc_method_list *class_methods OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
}
Category里面的方法加载过程,objc源码中找到objc-os.mm,函数_objc_init就是runtime的加载入口由libSystem调用,开始初始化,之后objc-runtime-new.mm里的map_images会加载map到内存,_read_images开始初始化这个map,这时会load所有Class,Protocol和Category,NSObject的+load方法就是这个时候调用的。下面是加载代码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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
for (EACH_HEADER) {
category_t **catlist = _getObjc2CategoryList(hi, &count);
for (i = 0 ; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
if (!cls) {
catlist[i] = nil ;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class" ,
cat->name, cat);
}
continue ;
}
BOOL classExists = NO ;
if (cat->instanceMethods || cat->protocols || cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES ;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s" ,
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "" );
}
}
if (cat->classMethods || cat->protocols )
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)" ,
cls->nameForLogging(), cat->name);
}
}
}
}
static void
attachCategoryMethods(Class cls, category_list *cats, bool flushCaches)
{
if (!cats) return ;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
method_list_t **mlists = (method_list_t **)
_malloc_internal(cats->count * sizeof (*mlists));
int mcount = 0 ;
int i = cats->count;
BOOL fromBundle = NO ;
while (i--) {
method_list_t *mlist = cat_method_list(cats->list[i].cat, isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= cats->list[i].fromBundle;
}
}
attachMethodLists(cls, mlists, mcount, NO , fromBundle, flushCaches);
_free_internal(mlists);
}
示例,下面的代码会编译错误,Runtime Crash还是会正常输出1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@interface NSObject (Sark )
+ (void )foo;
@end
@implementation NSObject (Sark )
- (void )foo
{
NSLog (@"IMP: -[NSObject(Sark) foo]" );
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
[NSObject foo];
[[NSObject new] foo];
}
return 0 ;
}
2014 -11 -06 13 :11 :46.694 Test[14872 :1110786 ] IMP: -[NSObject (Sark) foo]
2014 -11 -06 13 :11 :46.695 Test[14872 :1110786 ] IMP: -[NSObject (Sark) foo]
objc runtime加载后NSObject的Sark Category被加载,头文件+(void)foo没有IMP,只会出现一个warning。被加到Class的Method list里的方法只有-(void)foo,Meta Class的方法列表里没有。
执行[NSObject foo]时,会在Meta Class的Method list里找,找不着就继续往super class里找,NSObject Meta Clas的super class是NSObject本身,这时在NSObject的Method list里就有foo这个方法了,能够正常输出。
执行[[NSObject new] foo]就简单的多了,[NSObject new]生成一个实例,实例的Method list是有foo方法的,于是正常输出。
Protocol Protocol其实就是一个对象结构体1
typedef struct objc_object Protocol;
操作函数 Category操作函数信息都包含在objc_class中,我们可以通过objc_class的操作函数来获取分类的操作函数信息。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
42
@interface RuntimeCategoryClass : NSObject
- (void)method1;
@end
@interface RuntimeCategoryClass (Category)
- (void)method2;
@end
@implementation RuntimeCategoryClass
- (void)method1 {
}
@end
@implementation RuntimeCategoryClass (Category)
- (void)method2 {
}
@end
#pragma mark -
NSLog(@"测试objc_class中的方法列表是否包含分类中的方法");
unsigned int outCount = 0;
Method *methodList = class_copyMethodList(RuntimeCategoryClass.class, &outCount);
for (int i = 0; i < outCount; i++) {
Method method = methodList[i];
const char *name = sel_getName(method_getName(method));
NSLog(@"RuntimeCategoryClass's method: %s", name);
if (strcmp(name, sel_getName(@selector(method2)))) {
NSLog(@"分类方法method2在objc_class的方法列表中");
}
}
//输出
2014-11-08 10:36:39.213 [561:151847] 测试objc_class中的方法列表是否包含分类中的方法
2014-11-08 10:36:39.215 [561:151847] RuntimeCategoryClass's method: method2
2014-11-08 10:36:39.215 [561:151847] RuntimeCategoryClass's method: method1
2014-11-08 10:36:39.215 [561:151847] 分类方法method2在objc_class的方法列表中
Runtime提供了Protocol的一系列函数操作,函数包括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
Protocol * objc_getProtocol ( const char *name );
Protocol ** objc_copyProtocolList ( unsigned int *outCount );
Protocol * objc_allocateProtocol ( const char *name );
void objc_registerProtocol ( Protocol *proto );
void protocol_addMethodDescription ( Protocol *proto, SEL name, const char *types, BOOL isRequiredMethod, BOOL isInstanceMethod );
void protocol_addProtocol ( Protocol *proto, Protocol *addition );
void protocol_addProperty ( Protocol *proto, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount, BOOL isRequiredProperty, BOOL isInstanceProperty );
const char * protocol_getName ( Protocol *p );
BOOL protocol_isEqual ( Protocol *proto, Protocol *other );
struct objc_method_description * protocol_copyMethodDescriptionList ( Protocol *p, BOOL isRequiredMethod, BOOL isInstanceMethod, unsigned int *outCount );
struct objc_method_description protocol_getMethodDescription ( Protocol *p, SEL aSel, BOOL isRequiredMethod, BOOL isInstanceMethod );
objc_property_t * protocol_copyPropertyList ( Protocol *proto, unsigned int *outCount );
objc_property_t protocol_getProperty ( Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty );
Protocol ** protocol_copyProtocolList ( Protocol *proto, unsigned int *outCount );
BOOL protocol_conformsToProtocol ( Protocol *proto, Protocol *other );
Block runtime中一些支持block操作的函数1
2
3
4
5
6
7
8
IMP imp_implementationWithBlock ( id block );
id imp_getBlock ( IMP anImp );
BOOL imp_removeBlock ( IMP anImp );
测试代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@interface MyRuntimeBlock : NSObject
@end
@implementation MyRuntimeBlock
@end
IMP imp = imp_implementationWithBlock(^(id obj, NSString *str) {
NSLog (@"%@" , str);
});
class_addMethod(MyRuntimeBlock.class, @selector (testBlock:), imp, "v@:@" );
MyRuntimeBlock *runtime = [[MyRuntimeBlock alloc] init];
[runtime performSelector:@selector (testBlock:) withObject:@"hello world!" ];
2014 -11 -09 14 :03 :19.779 [1172 :395446 ] hello world!
Runtime的应用 获取系统提供的库相关信息 主要函数1
2
3
4
5
6
7
8
const char ** objc_copyImageNames ( unsigned int *outCount );
const char * class_getImageName ( Class cls );
const char ** objc_copyClassNamesForImage ( const char *image, unsigned int *outCount );
通过这些函数就能够获取某个类所有的库,以及某个库中包含哪些类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
NSLog(@"获取指定类所在动态库");
NSLog(@"UIView's Framework: %s", class_getImageName(NSClassFromString(@"UIView")));
NSLog(@"获取指定库或框架中所有类的类名");
const char ** classes = objc_copyClassNamesForImage(class_getImageName(NSClassFromString(@"UIView")), &outCount);
for (int i = 0; i < outCount; i++) {
NSLog(@"class name: %s", classes[i]);
}
//结果
2014-11-08 12:57:32.689 [747:184013] 获取指定类所在动态库
2014-11-08 12:57:32.690 [747:184013] UIView's Framework: /System/Library/Frameworks/UIKit.framework/UIKit
2014-11-08 12:57:32.690 [747:184013] 获取指定库或框架中所有类的类名
2014-11-08 12:57:32.691 [747:184013] class name: UIKeyboardPredictiveSettings
2014-11-08 12:57:32.691 [747:184013] class name: _UIPickerViewTopFrame
2014-11-08 12:57:32.691 [747:184013] class name: _UIOnePartImageView
2014-11-08 12:57:32.692 [747:184013] class name: _UIPickerViewSelectionBar
2014-11-08 12:57:32.692 [747:184013] class name: _UIPickerWheelView
2014-11-08 12:57:32.692 [747:184013] class name: _UIPickerViewTestParameters
......
对App的用户行为进行追踪 就是用户点击时把事件记录下来。一般比较做法就是在viewDidAppear里记录事件,这样会让这样记录事件的代码遍布整个项目中。继承或类别也会有问题。这时利用Method Swizzling把一个方法的实现和另一个方法的实现进行替换。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
@implementation UIViewController (Logging )- (void )swizzled_viewDidAppear :(BOOL )animated
{
[self swizzled_viewDidAppear:animated];
[Logging logWithEventName:NSStringFromClass ([self class ])];
}
@implementation UIViewController (Logging )void swizzleMethod (Class class , SEL originalSelector , SEL swizzledSelector ) {
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);
}
}
@implementation UIViewController (Logging )+ (void )load
{
swizzleMethod([self class ], @selector (viewDidAppear:), @selector (swizzled_viewDidAppear:));
}
void (gOriginalViewDidAppear)(id , SEL, BOOL );void newViewDidAppear(UIViewController *self , SEL _cmd, BOOL animated)
{
gOriginalViewDidAppear(self , _cmd, animated);
[Logging logWithEventName:NSStringFromClass ([self class ])];
}
+ (void )load
{
Method originalMethod = class_getInstanceMethod(self , @selector (viewDidAppear:));
gOriginalViewDidAppear = (void *)method_getImplementation(originalMethod); if (!class_addMethod(self , @selector (viewDidAppear:), (IMP) newViewDidAppear, method_getTypeEncoding(originalMethod))) {
method_setImplementation(originalMethod, (IMP) newViewDidAppear);
}
}
通过Method Swizzling可以把事件代码或Logging,Authentication,Caching等跟主要业务逻辑代码解耦。这种处理方式叫做Cross Cutting Concernshttp://en.wikipedia.org/wiki/Cross-cutting_concern 用Method Swizzling动态给指定的方法添加代码解决Cross Cutting Concerns的编程方式叫Aspect Oriented Programming http://en.wikipedia.org/wiki/Aspect-oriented_programming 目前有些第三方库可以很方便的使用AOP,比如Aspects https://github.com/steipete/Aspects 这里是使用Aspects的范例https://github.com/okcomp/AspectsDemo