Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理。这种动态语言的优势在于:我们写代码时更具有灵活性,如我们可以把消息转发给我们想要的对象,或者随意交换一个方法的实现等。
这种特性意味着Objective-C不仅需要一个编译器,还需要一个运行时系统来执行编译的代码。对于Objective-C来说,这个运行时系统就像一个操作系统一样,让它所有的工作可以正常运行。这个运行时系统即Objc Runtime
。Objc Runtime
其实就是一个Runtime
库,它基本上是用C和汇编写的,这个库使得C语言有了面向对象的能力。
Runtime
库主要做下面几件事:
封装:在这个库中,对象可以用C语言中的结构体表示,而方法可以用C函数来实现,另外再加上了一些额外的特性。这些结构体和函数被Runtime函数封装后,我们就可以在程序运行时创建,检查,修改类、对象和它们的方法了。
找出方法的最终执行代码:当程序执行[object doSomething]
时,会向消息接收者(object)发送一条信息(doSomething),Runtime会根据消息接收者是否能响应该消息而做出不同的反应。这将在后面详细介绍。
Objective-C Runtime目前有两个版本:Modern Runtime
和Legacy Runtime
。Modern Runtime
覆盖了64位的Mac OS X Apps
还有iOS AppS
,Legacy Runtime
是早期用来给32位Mac OS X Apps
用的,也就是可以不用管就是了。
在这一系列文章中,我们将介绍Runtime的基本工作原理,以及如何利用它让我们的程序变得更加灵活。CFRuntime的代码是开源的,我们可以在这里https://opensource.apple.com/tarballs/CF/ 下载整个CoreFoundation的源码来看。 在本文中,我们先来介绍一下类和对象,这是面向对象的基础,我们看看在Runtime中,类是如何体现的。
类与对象基础数据结构 Class Objective-C类是由Class类型来表示的,它实际上是指向objc_class结构体的指针。它的定义如下:typedef struct objc_class *Class;
查看objc/runtime.h
中objc_class
结构体的定义如下: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;
在这个定义中,下面几个字段是我们感兴趣的
isa : 需要注意的是在Objective-C中,所有的类自身也是一个对象,这个对象的Class里面也有一个isa指针,它指向metaClass(元类),我们会在后面介绍它。
super_class :指向该类的父类,如果该类已经是最顶层的跟类(如NSObject或者NSProxy),则super_class为NULL。
cache : 用于缓存最近使用的方法。一个接收者对象接收到一个消息时,它会根据isa指针去查找能够响应这个消息的对象。在实际使用中,这个对象只有一部分方法是常用的,很多方法很少用或者根本用不上。这种情况下,如果每次消息来时,我们都是methodLists中遍历一遍,性能势必很差。这时,cache就派上用场了。在我们每次调用过一个方法后,这个方法就会被缓存到cache列表中,下次调用的时候runtime就会优先去cache中查找,如果cache没有,才到methodLists中查找方法。这样,对于那些经常用到的方法的调用,提高了调用的效率。
version :我们可以使用这个字段来提供类的版本信息。这对于对象的序列化非常有用,它可是让我们识别出不同类定义版本中实例变量布局的改变。
针对cache,我们用下面例子来说明其执行过程:NSArray *array = [[NSArray alloc] init];
其流程是:
[NSArray alloc]
先被执行。因为NSArray没有+alloc
方法,于是去父类NSObject去查找。
检测NSObject是否响应+alloc
方法,发现响应,于是检测NSArray类,并根据其所需的内存空间大小开始分配内存空间,然后把isa
指针指向NSArray类。同时,+alloc
也被加进cache列表里面。
接着,执行-init
方法,如果NSArray响应该方法,则直接将其加入cache
;如果不响应,则去父类查找。
在后期的操作中,如果再以[[NSArray alloc] init]
这种方式来创建数组,则会直接从cache中取出相应的方法,直接调用。
objc_object与id objc_object
是表示一个类的实例的结构体,它的定义如下(objc/objc.h
):
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
typedef struct objc_object *id ;
可以看到,这个结构体只是一个指针,即指向其类的isa指针,这样,当我们向一个Objective-C对象发送消息时,运行时库会根据实例对象的isa指针找到这个实例对象所属的类。runtime库会在类的方法列表及父类的方法列表中去寻找与消息对应的selector指向的方法。找到后即运行这个方法。
当创建一个特定类的实例对象时,分配的内存包含一个objc_object数据结构,然后是类的实例变量的数据。NSObject类的alloc和allocWithZone:方法使用函数class_createInstance来创建objc_object数据结构。
另外还有我们常见的id,它是一个objc_object结构类型的指针。它的存在可以让我们实现类似于C++中泛型的一些操作。该类型的对象可以转换为任何一种对象,有点类似于C语言中void*指针类型的作用。
objc_cache 上面提到了objc_class结构体中的cache字段,它用于缓存调用过的方法。这个字段是一个指向objc_cache结构体的指针,其定义如下:struct objc_cache {
unsigned int mask OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1 ] OBJC2_UNAVAILABLE;
};
该结构体的字段描述如下:
mask :一个整数,指定分配的缓存buket的总数。在方法查找过程中,Objective-C runtime使用这个字段来确定开始线性查找数组的索引位置。指向方法selector的指针与该字段做一个AND位操作(index = (mask & selector))。这可以作为一个简单的hash散列算法。
occupied :一个整数,指定实际占用的缓存bucket的总数。
buckets :指向Method数据结构指针的数组。这个数组可能包含不超过mask+1个元素。需要注意的是,指针可能为MULL,表示这个缓存buket没有被占用,另外被占用的bucket可能是不连续的。这个数组可能会随着时间而增长。
在上面我们提到,所有的类本身也是一个对象,我们可以向这个对象发送消息(即调用类方法)。如:NSArray *array = [NSArray array];
这个例子中,+array
消息发送给了NSArray类,而这个NSArray也是一个对象。既然是对象,那么它也是一个objc_object指针,它包含一个指向其类的一个isa指针。那么这些就有一个问题了,这个isa指针指向哪里呢?为了调用+array
方法,这个类的isa指针必须指向一个包含这些类方法的一个objc_class结构体。这就引出了meta-class的概念
当我们向一个对象发送消息时,runtime会在这个对象所属的这个类的方法列表中查找方法,而向一个类发送消息时,会在这个类的meta-class的方法列表中查找。
meta-class之所以重要,是因为它存储着一个类的所有方法。每个类都会有一个单独的meta-class,因为每个类的类方法基本不可能完全相同。
再深入一下,meta-class也是一个类,也可以向它发送一个消息,那么它的isa又是指向什么呢?为了不让这种结构无限延伸下去,Objective-C的设计者让所有的meta-class的isa指向基类的meta-class,以此作为它们的所属类。即,任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己的所属类,而基类的meta-class的isa指针是指向它自己。这样就形成了一个完美的闭环。
通过上面的描述,再加上对objc_class结构中super_class指针的分析,我们就可以描绘出类及相应meta-class类的一个继承体系了,如下图所示:
对于NSObject继承体系来说,其实例方法对体系中的所有实例、类和meta-class都是有效的;而类方法对于体系内的所有类和meta-class都是有效的。
讲了这么多,我们还是来写个例子吧: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 ]));
}
#pragma mark -
@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
这个例子是在运行时创建了一个NSError的子类TestClass,然后为这个子类添加一个方法testMetaClass,这个方法的实现是TestMetaClass函数。
运行后,打印结果是: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
我们在for循环中,我们通过objc_getClass来获取对象的isa,并将其打印出来,依此一直回溯到NSObject的meta-class。分析打印结果,可以看到最后指针指向的地址是0x0,即NSObject的meta-class的类地址。
这里需要注意的是:我们在一个类对象调用class方法是无法获取meta-class,它只是返回一个类而已。
类与对象操作函数 runtime提供了大量的函数来操作类和对象。类的操作方法大部分是以class_
为前缀的,而对象的操作方法大部分是以objc_
或object_
为前缀。下面我们将根据这些方法的用途来分类讨论这些方法的使用。
类相关操作函数 我们可以回过头去看看objc_class
的定义,runtime提供的操作类的方法主要就是针对这个结构体中的各个字段的。下面我们分别介绍这一些函数。并在最后以实例来演示这些函数的具体方法。
类名(name) 类名操作的函数主要有:
const char * class_getName ( Class cls );
对于class_getName
函数,如果传入的cls为Nil,则返回一个nil
父类和元类操作的函数主要有:
Class class_getSuperclass ( Class cls );
BOOL class_isMetaClass ( Class cls );
class_getSuperclass
函数,当cls为nil或者cls为根类时,返回nil。不过通常我们可以使用NSObject类的superclass方法来达到同样的目的。
class_isMetaClass
函数,如果cls是元类,则返回YES,如果否或者传入的cls为nil,则返回NO。
实例变量大小(instance_size) 实例变量大小操作的函数有:
size_t class_getInstanceSize ( Class cls );
成员变量(ivars)及属性 在objc_class
中,所有的成员变量、属性的信息是放在链表ivars中的。ivars是一个数组,数组中每个元素是指向Ivar(变量信息)的指针。runtime提供了丰富的函数来操作这一字段。大体上可以分为以下几类: 1.成员变量操作函数,主要包含以下函数:
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 );
class_getInstanceVariable
函数,它返回一个指向包含name指定的成员变量信息的objc_ivar结构体的指针(Ivar)。
class_getClassVariable
函数,目前没有找到关于Objective-C中类变量的信息,一般认为Objective-C不支持类变量。注意,返回的列表不包含父类的成员变量和属性。
Objective-C不支持往已经存在的类中添加实例变量,因此不管是系统库提供的类,还是我们自己定义的类,都无法动态添加成员变量。但如果我们通过运行时来创建一个类的话,又应该如何给它添加成员变量呢?这时我们就可以用class_addIvar
函数了。不过需要注意的是,这个方法只能在objc_allocateClassPair
函数与objc_registerClassPair
之间调用。另外,这个类也不能是元类。成员变量的按字节最小对其量是1<<alignment。这取决于ivar的类型和机器的架构。如果变量的类型是指针类型,则传递log2(sizeof(pointer_type))。
class_copyIvarList
函数,它返回一个指向成员变量信息的数组,数组中每个元素是指向该成员变量信息的objc_ivar结构体的指针。这个数组不包含在父类中声明的变量。outCount指针返回数组的大小。需要注意的是,我们必须使用free()来释放这个数组。
2.属性操作函数,主要包含以下函数:
objc_property_t class_getProperty ( Class cls, const char *name );
objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount );
BOOL class_addProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
void class_replaceProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
这一种方法也是针对ivars来操作,不过只操作那些属性的值。我们在后面会介绍属性时会再遇到这些函数。
3.在MAC OS X系统中,我们可以使用垃圾回收器。runtime提供了几个函数来确定一个对象的内存区域是否可以被垃圾回收器扫描,以处理strong/weak引用。这几个函数定义如下:const uint8_t * class_getIvarLayout ( Class cls );
void class_setIvarLayout ( Class cls, const uint8_t *layout );
const uint8_t * class_getWeakIvarLayout ( Class cls );
void class_setWeakIvarLayout ( Class cls, const uint8_t *layout );
但通常情况下,我们不需要主动去调用这些方法,在调用objc_registerClassPair
时,会生成合理的布局。在此不详细介绍这些函数。
方法(methodLists) 方法操作主要有以下函数:
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 );
class_addMethod
的实现会覆盖父类的方法实现,但不会取代本类中已存在的实现,如果本类中包含一个同名的实现,则函数会返回NO。如果要修改已存在实现,可以使用method_setImplementation
。一个Objective-C方法是一个简单的C函数,它至少包含两个参数self和_cmd。所以,我们的实现函数(IMP参数指向的函数)至少需要两个参数,如下所示:void myMethodIMP (id self, SEL _ cmd)
{
}
与成员变量不同的是,我们可以为类动态添加方法,不管这个类是否已经存在。另外,参数types是一个描述传递给方法的参数类型的字符数组,这就涉及到类型编码,我们将在后面介绍。
class_getInstanceMethod
、class_getClassMethod
函数,与class_copyMethodList
不同的是,这两个函数都会去搜索父类的实现。
class_copyMethodList
函数,返回包含所有实例方法的数组,如果需要获取类方法,则可以使用class_copyMethodList(object_getClass(cls),&count)
(一个类的实例方法定义在元类里面)。该列表不包含父类实现的方法。outCount
参数返回方法的个数。在获取到列表后,我们需要使用free()方法来释放它。
class_replaceMethod
函数,该函数的行为可以分为两种:如果类中不存在name指定的方法,则类似于class_addMethod
函数一样会添加方法;如果类中已经存在name指定的方法,则类似与method_setImplementation
一样替代原方法的实现。
class_getMethodImplementation
函数,该函数在向类实例发送消息时会被调用,并返回一个指向方法实现函数的指针。这个函数会比method_getImplementation(class_getInstanceMethod(cls,name))
更快。返回的函数指针可能是一个指向runtime内部的函数,而不一定是方法的实际实现。例如,如果类实例无法响应selector,则返回的函数指针将是运行时消息转发机制的一部分。
class_respondsToSelector
函数,我们通常使用NSObject类的respondsToSelector:
或instanceRespondToSelector:
方法来达到同样的目的。
协议(objc_protocol_list) 协议相关的操作包含以下函数:
BOOL class_addProtocol ( Class cls, Protocol *protocol );
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );
class_conformsToProtocol
函数可以使用NSObject类的conformsToProtocol:
方法来代替。
class_copyProtocolList
函数返回的是一个数组,在使用后我们需要使用free()手动释放。
版本(version) 版本相关的操作包含以下函数:
int class_getVersion ( Class cls );
void class_setVersion ( Class cls, int version );
其他 runtime还提供了两个函数工CoreFoundation
和tool-free bridging
使用,即:Class objc_getFutureClass ( const char *name );
void objc_setFutureClass ( Class cls, const char *name );
通常我们不直接使用这两个函数。
实例(Example) 上面列举了大量类操作的函数,下面我们写个实例,来看看这些函数的实例效果:
我们创建一个类MyClass,并且给雷添加一些实例变量和方法。
MyClass.h
#import <Foundation/Foundation.h>
@interface MyClass : NSObject <NSCopying , NSCoding >
@property (nonatomic , strong ) NSArray *array;
@property (nonatomic , copy ) NSString *string;
- (void )method1;
- (void )method2;
+ (void )classMethod1;
@end
MyClass.m
#import "MyClass.h"
@interface MyClass () {
NSInteger _instance1;
NSString *_instance2;
}
@property (nonatomic , assign ) NSInteger integer;
- (void )method3WithArg1:(NSInteger )arg1 arg2:(NSString *)arg2;
@end
@implementation MyClass
+ (void )classMethod1 {
NSLog (@"调用类方法 classMethod1" );
}
- (void )method1 {
NSLog (@"调用实例方法 method1" );
}
- (void )method2 {
NSLog (@"调用实例方法 method2" );
}
- (void )method3WithArg1:(NSInteger )arg1 arg2:(NSString *)arg2 {
NSLog (@"arg1 : %ld, arg2 : %@" ,arg1,arg2);
}
@end
在ViewController中的- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
方法中调用例子:
#import "ViewController.h"
#import <objc/runtime.h>
#import "MyClass.h"
@interface ViewController ()
@end
@implementation ViewController
- (void )touchesBegan:(NSSet <UITouch *> *)touches withEvent:(UIEvent *)event {
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 (@"instanve 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 (@"instance 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" );
NSLog (@"---------------------------------" );
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 (@"---------------------------------" );
}
@end
控制台输出如下:2016-11-23 18:07:47.583 RuntimeDemo[3598:309543] class name: MyClass
2016-11-23 18:07:47.583 RuntimeDemo[3598:309543] ---------------------------------
2016-11-23 18:07:47.583 RuntimeDemo[3598:309543] super class name: NSObject
2016-11-23 18:07:47.583 RuntimeDemo[3598:309543] ---------------------------------
2016-11-23 18:07:47.583 RuntimeDemo[3598:309543] MyClass is not a meta-class
2016-11-23 18:07:47.583 RuntimeDemo[3598:309543] ---------------------------------
2016-11-23 18:07:47.584 RuntimeDemo[3598:309543] MyClass's meta-class is MyClass
2016-11-23 18:07:47.584 RuntimeDemo[3598:309543] ---------------------------------
2016-11-23 18:07:47.584 RuntimeDemo[3598:309543] instance size: 48
2016-11-23 18:07:47.584 RuntimeDemo[3598:309543] ---------------------------------
2016-11-23 18:07:47.584 RuntimeDemo[3598:309543] instance variable's name:_instance1 at index: 0
2016-11-23 18:07:47.584 RuntimeDemo[3598:309543] instance variable's name:_instance2 at index: 1
2016-11-23 18:07:47.585 RuntimeDemo[3598:309543] instance variable's name:_array at index: 2
2016-11-23 18:07:47.585 RuntimeDemo[3598:309543] instance variable's name:_string at index: 3
2016-11-23 18:07:47.585 RuntimeDemo[3598:309543] instance variable's name:_integer at index: 4
2016-11-23 18:07:47.585 RuntimeDemo[3598:309543] instanve variable _string
2016-11-23 18:07:47.585 RuntimeDemo[3598:309543] ---------------------------------
2016-11-23 18:07:47.585 RuntimeDemo[3598:309543] property's name : integer
2016-11-23 18:07:47.586 RuntimeDemo[3598:309543] property's name : array
2016-11-23 18:07:47.586 RuntimeDemo[3598:309543] property's name : string
2016-11-23 18:07:47.586 RuntimeDemo[3598:309543] ---------------------------------
2016-11-23 18:07:47.586 RuntimeDemo[3598:309543] method's signature: method1
2016-11-23 18:07:47.586 RuntimeDemo[3598:309543] method's signature: method3WithArg1:arg2:
2016-11-23 18:07:47.586 RuntimeDemo[3598:309543] method's signature: method2
2016-11-23 18:07:47.587 RuntimeDemo[3598:309543] method's signature: integer
2016-11-23 18:07:47.587 RuntimeDemo[3598:309543] method's signature: setInteger:
2016-11-23 18:07:47.587 RuntimeDemo[3598:309543] method's signature: setArray:
2016-11-23 18:07:47.587 RuntimeDemo[3598:309543] method's signature: .cxx_destruct
2016-11-23 18:07:47.587 RuntimeDemo[3598:309543] method's signature: string
2016-11-23 18:07:47.587 RuntimeDemo[3598:309543] method's signature: setString:
2016-11-23 18:07:47.587 RuntimeDemo[3598:309543] method's signature: array
2016-11-23 18:07:47.588 RuntimeDemo[3598:309543] instance method: method1
2016-11-23 18:07:47.588 RuntimeDemo[3598:309543] MyClass is responsd to selector: method3WithArg1:arg2:
2016-11-23 18:07:47.588 RuntimeDemo[3598:309543] ---------------------------------
2016-11-23 18:07:47.588 RuntimeDemo[3598:309543] 调用实例方法 method1
2016-11-23 18:07:47.588 RuntimeDemo[3598:309543] ---------------------------------
2016-11-23 18:07:47.588 RuntimeDemo[3598:309543] protocol name:NSCopying
2016-11-23 18:07:47.588 RuntimeDemo[3598:309543] protocol name:NSCoding
2016-11-23 18:07:47.589 RuntimeDemo[3598:309543] MyClass is responsed to protocol NSCoding
动态创建类和对象 runtime的强大之处在于它能在运行时创建类和对象。
动态创建类 动态创建类涉及到以下几个函数:
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes);
void objc_disposeClassPair ( Class cls );
void objc_registerClassPair ( Class cls );
objc_allocateClassPair
函数:如果我们要创建一个根类,则superclass指定为Nil。extraBytes
通常指定为0,该参数是分配给类和元类对象尾部的索引ivars的字节数。
objc_registerClassPair
函数:为了创建一个新类,我们需要调用objc_allocateClassPair
。然后使用诸如class_addMethod
,class_addIvar
等函数来为新的类添加方法、实例变量和属性等。完成这些后,我们需要调用objc_registerClassPair
函数来注册类,之后这个类就可以在程序中使用了。实例方法和实例变量应该添加到类自身上,而类方法应该添加到类的元类上。
objc_disposeClassPair
函数:用于销毁一个类,不过需要注意的是,如果程序运行中还存在类或者其子类的实例时,则不能针对类调用该方法。
在前面介绍元类时,我们已经有接触到这几个函数了,在此我们再举个实例来看看这几个函数的使用: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)];
程序的输出如下:
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
动态创建对象 动态创建对象的函数如下:
id class_createInstance ( Class cls, size_t extraBytes );
id objc_constructInstance ( Class cls, void *bytes );
void * objc_destructInstance ( id obj );
class_createInstance
函数:创建实例时,会在默认的内存区域为类分配内存。extraBytes
参数表示分配的额外字节数。这些额外的字节可用于存储在类定义中所定义的实例变量之外的实例变量。该函数在ARC环境下无法使用。
调用class_createInstance
的效果与+alloc
方法类似。不过在使用class_createInstance
时,我们需要确切的知道我们要用它来做什么。在下面的例子中,我们用NSString来测试一下该函数的实际效果:id theObject = class_createInstance(NSString .class, sizeof (unsigned ));
id str1 = [theObject init];
NSLog (@"%@" , [str1 class ]);
id str2 = [[NSString alloc] initWithString:@"test" ];
NSLog (@"%@" , [str2 class ]);
输出的结果是:2014 -10 -23 12 :46 :50.781 RuntimeTest[4039 :89088 ] NSString
2014 -10 -23 12 :46 :50.781 RuntimeTest[4039 :89088 ] __NSCFConstantString
可以看到,使用class_createInstance
函数获取的是NSString实例,而不是类簇中的默认站位符类__NSCFConstantString
。
实例操作函数 实例操作函数主要是针对我们创建的实例对象的一系列操作函数,我们可以使用这组函数来从实例对象中获取我们想要的一些信息,如实例对象中变量的值。这组函数可以分为三小类:
1.针对整个对象进行操作的函数,这类函数包括
id object_copy ( id obj, size_t size );
id object_dispose ( id obj );
有这样一种场景,假设我们有类A和类B,且类B是类A的子类。类B通过添加一些额外的属性来扩展类A。现在我们创建了一个A类的实例对象,并希望在运行时将这个对象转换为B类的实例对象,这样可以添加数据到B类的属性中。这种情况下,我们没有办法直接转换,以为B类的实例会比A类的实例更大,没有足够的空间来放置对象。此时,我们就可以使用以上几个函数来处理这种情况,如下代码所示:NSObject *a = [[NSObject alloc] init];
id newB = object_copy(a, class_getInstanceSize(MyClass.class));
object_setClass(newB, MyClass.class);
object_dispose(a);
2.针对对象实例变量进行操作的函数,这类函数包含:
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 );
如果实例变量的Ivar已经知道,那么调用object_getIvar
会比object_getInstanceVariable
函数快,相同情况下,object_setIvar
也比object_setInstanceVariable
快。
3.针对对象的类进行操作的函数,这类函数包含:
const char * object_getClassName ( id obj );
Class object_getClass ( id obj );
Class object_setClass ( id obj, Class cls );
获取类定义 Objective-C动态运行库会自动注册我们代码中定义的所有的类。我们也可以在运行时创建类定义并使用objc_addClass
函数来注册它们。runtime提供了一系列函数来获取类定义相关的信息,这些函数主要包括:
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 );
objc_getClassList
函数:获取已注册的类定义的列表。我们不能假设从该函数中获取的类对象是继承自NSObject体系的,所以在这些类上调用方法是,都应该先检测一下这个方法是否在这个类中实现。
下面代码演示了该函数的用法: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);
}
输出结果如下: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
......还有大量输出
获取类定义的方法有三个:objc_lookUpClass
,objc_getClass
和objc_getRequiredClass
。如果类在运行时未注册,则objc_lookUpClass
会返回nil,而objc_getClass
会调用类处理回调,并再次确认该类是否注册,如果确认未注册,则返回nil。而objc_getRequiredClass
函数的操作与objc_getClass
相同,只不过如果没有找到类,则会杀死进程。
objc_getMetaClass
函数:如果指定的类没有注册,则该函数会调用类处理回调,并再次确认类是否注册,如果确认未注册,再返回nil。不过,每个类都必须有一个有效的元类定义,所以这个函数总是会返回一个元类定义,不管它是否有效。
小结 在这一章中我们介绍了Runtime运行时中与类和对象相关的数据结构,通过这些数据函数,我们可以管窥Objective-C底层面向对象实现的一些信息。另外,通过丰富的操作函数,可以灵活地对这些数据进行操作。