读了N 遍南峰子老师的博客,对老师的Runtime的讲解虽不能说百分之百的聊熟于心,但也能做到熟练掌握,重新记录下来,留给以后的自己翻看查阅。
记录分六个部分:
- Objective-C Runtime 之一:类与对象。 点击查看
- Objective-C Runtime 之二:成员变量与属性。 点击查看
- Objective-C Runtime 之三:方法与消息。 点击查看
- Objective-C Runtime 之四:Method Swizzling。 点击查看
- Objective-C Runtime 之五:协议与分类。 点击查看
- Objective-C Runtime 之六:拾遗。 点击查看
本篇文章记录第一部分:
一、 Objective-C 特性:
Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理。这种动态语言的优势在于:我们写代码时更具灵活性,如我们可以把消息转发给我们想要的对象,或者随意交换一个方法的实现等。
这种特性意味着Objective-C不仅需要一个编译器,还需要一个运行时系统来执行编译的代码。对于Objective-C来说,这个运行时系统就像一个操作系统一样:它让所有的工作可以正常的运行。这个运行时系统即Objc Runtime。Objc Runtime其实是一个Runtime库,它基本上是用C和汇编写的,这个库使得C语言有了面向对象的能力。
二、 Runtime 做了什么:
- 封装(这是Runtime发挥作用的前提)
- 对象:用C语言的结构体表示
- 方法:用C函数实现
- 确定方法的执行流程
- 当程序执行[object doSomething]时,会向消息接收者(object)发送一条消息(doSomething),runtime会根据消息接收者是否能响应该消息而做出不同的反应。
三、 类与对象基础数据结构:
Class
Objective-C类是由Class类型来表示的,我们刚才说了,Runtime做了封装,将类封装成结构体 objc_class
,而我们平时所用到的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; // 类的版本信息,默认为0
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];
其流程是:
1. `[NSArray alloc]`先被执行。因为NSArray没有`+alloc`方法,于是去父类NSObject去查找。
2. 检测NSObject是否响应`+alloc`方法,发现响应,于是检测NSArray类,并根据其所需的内存空间大小开始分配内存空间( 根据 instance_size 确定大小),然后把`isa`指针指向NSArray类( 生成对象 确定 isa 所指的类 对象接收消息时候会用到 )。同时,`+alloc`也被加进cache列表里面。
3. 接着,执行`-init`方法,如果NSArray响应该方法,则直接将其加入`cache`;如果不响应,则去父类查找。
4. 在后期的操作中,如果再以`[[NSArray alloc] init]`这种方式来创建数组,则会直接从cache中取出相应的方法,直接调用。
Object
既然类 Class 是指向类结构体的指针,正常推理 Object 也是这个一个指针,指向类实例结构体。不过我们不用 Object 类表示 ,而是用 id 来表示。
指针:
typedef struct objc_object *id;
结构体:
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
可以看到,这个结构体只有一个字体,即指向其类的isa指针。这样,当我们向一个Objective-C对象发送消息时,运行时库会根据实例对象的isa指针找到这个实例对象所属的类。Runtime库会在类的方法列表及父类的方法列表中去寻找与消息对应的selector指向的方法。找到后即运行这个方法。
当创建一个特定类的实例对象时,分配的内存包含一个objc_object数据结构,然后是类的实例变量的数据。NSObject类的alloc和allocWithZone:方法使用函数class_createInstance来创建objc_object数据结构。
objc_cache
上面提到了objc_class结构体中的cache字段,该字段也是指向结构体的一个指针,它用于缓存调用过的方法。其定义如下:
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};
该结构体的字段描述如下:
1 、mask:一个整数,指定分配的缓存bucket的总数。在方法查找过程中,Objective-C runtime使用这个字段来确定开始线性查找数组的索引位置。指向方法selector的指针与该字段做一个AND位操作(index = (mask & selector))。这可以作为一个简单的hash散列算法。
2、occupied:一个整数,指定实际占用的缓存bucket的总数。
3、buckets:指向Method数据结构指针的数组。这个数组可能包含不超过mask+1个元素。需要注意的是,指针可能是NULL,表示这个缓存bucket没有被占用,另外被占用的bucket可能是不连续的。这个数组可能会随着时间而增长。
元类(Meta Class)
在上面我们提到,所有的类自身也是一个对象,我们可以向这个对象发送消息(即调用类方法)。如:
NSArray *array = [NSArray array];
这个例子中,+array消息发送给了NSArray类,而这个NSArray也是一个对象。既然是一个对象,那么肯定能找到这个对象所属的类,怎么找?通过什么找?这就是要借用 isa 指针了。找到这个类以后,这个类中还要包含一个指向方法列表的指针。这就引出了 meta-class 的概念
meta-class 是一个类对象的类
重点来了:
- 向类的例化对象发送消息,runtime会在这个对象所属的类的方法列表中查找方法
- 向类发送消息时,会在该类所属的元类中的方法列表中查找
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类的一个继承体系了,如下图所示:
Inheritance system.png元类也是有继承关系的。
怎么样才能用代码直观的看出来元类的继承体系呢?可以通过下面的例子看出来,例子中可能有没有涉及的东西,没关系,看懂大概就行:
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函数。
运行后,打印结果是
2017-10-20 22:57:07.352 mountain[1303:41490] This objcet is 0x7a6e22b0
2017-10-20 22:57:07.353 mountain[1303:41490] Class is TestStringClass, super class is NSError
2017-10-20 22:57:07.353 mountain[1303:41490] Following the isa pointer 0 times gives 0x7a6e21b0
2017-10-20 22:57:07.353 mountain[1303:41490] Following the isa pointer 1 times gives 0x0
2017-10-20 22:57:07.353 mountain[1303:41490] Following the isa pointer 2 times gives 0x0
2017-10-20 22:57:07.353 mountain[1303:41490] Following the isa pointer 3 times gives 0x0
2017-10-20 22:57:07.353 mountain[1303:41490] NSObject's class is 0xe10000
2017-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 的定义:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
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;
类名
1 // 获取类的类名
2 const char * class_getName ( Class cls );
- 对于class_getName函数,如果传入的cls为Nil,则返回一个字字符串
父类和元类
1 // 获取类的父类
2 Class class_getSuperclass ( Class cls );
3 // 判断给定的Class是否是一个元类
4 BOOL class_isMetaClass ( Class cls );
-
class_getSuperclass函数,当cls为Nil或者cls为根类时,返回Nil。不过通常我们可以使用NSObject类的superclass方法来达到同样的目的。
-
class_isMetaClass函数,如果是cls是元类,则返回YES;如果否或者传入的cls为Nil,则返回NO。
实例变量大小(instance_size)
1 // 获取实例大小
2 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 );
// 类实例是否响应指定的selector
BOOL class_respondsToSelector ( Class cls, SEL sel );
重点来了:
- class_addMethod (返回BOOL值) 的实现会覆盖父类的方法,但是但是但是但是不会取代本类中已经存在的实现,如果本类中包含一个同名的实现,则函数会返回NO。这样的话是不是就不能修改已经存在的实现了呢???答案是NO!!!我们可以通过 method_setImplementation 来修改。一个Objective-C方法是一个简单的C函数,它至少包含两个参数–self和_cmd。所以,我们的实现函数(IMP参数指向的函数)至少需要两个参数,如下所示:
void myMethodIMP(id self, SEL _cmd)
{
// implementation ....
}
与成员变量不同的是,我们可以为类动态添加方法,不管这个类是否已存在.
另外,参数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:或instancesRespondToSelector:方法来达到相同目的。
协议(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()手动释放。
实例(Example)源码
上面列举了大量类操作的函数,下面我们写个实例,来看看这些函数的实例效果:
先来看一下整个项目的结构图:
product.png
两个类之间是继承关系:
===================================================
***
RuntimeViewController 类
// RuntimeViewController.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface RuntimeViewController : UIViewController
@property (nonatomic, copy) NSArray *array;
@property (nonatomic, copy) NSString *string;
- (void)method1;
- (void)method2;
+ (void)classMethod1;
@end
NS_ASSUME_NONNULL_END
=====================================================
// RuntimeViewController.m
#import "RuntimeViewController.h"
@interface RuntimeViewController (){
NSInteger _instance1;
NSString * _instance2;
}
@property (nonatomic, assign) NSUInteger integer;
- (void)method3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2;
@end
@implementation RuntimeViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
+ (void)classMethod1 {
NSLog(@"类方法 call classMethod1");
}
- (void)method1 {
NSLog(@"实例方法 call method1");
}
- (void)method2 {
NSLog(@"实例方法 call method2");
}
- (void)method3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2 {
NSLog(@"arg1 : %ld, arg2 : %@", arg1, arg2);
}
@end
===================================================
// SubRuntimeViewController 类
#import "SubRuntimeViewController.h"
#import <objc/runtime.h>
@interface SubRuntimeViewController ()
{
NSArray *subArray;
}
@property (nonatomic, strong) NSString *subString;
@end
@implementation SubRuntimeViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"self is : %@", self);
// 类名
NSLog(@"current class name: %s", class_getName([self class]));
NSLog(@"------------------------------------------");
// 父类
NSLog(@"super class name: %s", class_getName(class_getSuperclass(self.class)));
NSLog(@"super class name: %s", class_getName(self.superclass));
// 获取父类方法有两种方式 self.superclass 或者 class_getsuperclass(self.class)
NSLog(@"------------------------------------------");
// 是否是元类
NSLog(@"self.class is %@ a meta-class", class_isMetaClass(self.class)? @"": @"not");
NSLog(@"------------------------------------------");
// 获取当前类的元类
Class meta_class = objc_getMetaClass(object_getClassName(self.class));
NSLog(@"%s's meta-class is %@", object_getClassName(self.class), meta_class);
NSLog(@"------------------------------------------");
// 实例变量大小
NSLog(@"instance size: %zu", class_getInstanceSize(self.class));
NSLog(@"------------------------------------------");
// 成员变量
unsigned int outCount = 0;
// 通过传递地址的方式给变量赋值
Ivar *ivars = class_copyIvarList(self.class, &outCount);
for (NSInteger i = 0; i < outCount; i++) {
Ivar ivar = ivars[I];
NSLog(@"instance variable's name: %s at index: %ld", ivar_getName(ivar), (long)i);
}
free(ivars);
Ivar string = class_getInstanceVariable(self.class, "_subString");
if (string != NULL) {
NSLog(@"instace variable %s", ivar_getName(string));
}
// 数组都要释放
// 并不会查询父类
NSLog(@"------------------------------------------");
//属性操作
objc_property_t *properties = class_copyPropertyList(self.class, &outCount);
for (NSInteger k = 0; k < outCount; k ++) {
objc_property_t property = properties[k];
NSLog(@"property's name: %s", property_getName(property));
}
free(properties);
// 数组都要释放
// 并不会查询父类
NSLog(@"------------------------------------------");
// 方法操作
Method *methods = class_copyMethodList(self.superclass, &outCount);
for (int i = 0; i < outCount; i++) {
Method method = methods[I];
// 此处会报错 咱没找到原因
// NSLog(@"method's signature: %@", method_getName(method));
}
free(methods);
Method method1 = class_getInstanceMethod(self.class, @selector(method1));
if (method1 != NULL) {
NSLog(@"method %s", method_getName(method1));
}
Method classMethod = class_getClassMethod(self.class, @selector(classMethod1));
if (classMethod != NULL) {
NSLog(@"class method : %s", method_getName(classMethod));
}
NSLog(@"MyClass is%@ responsd to selector: method3WithArg1:arg2:", class_respondsToSelector(self.class, @selector(method3WithArg1:arg2:)) ? @"" : @" not");
IMP imp = class_getMethodImplementation(self.class, @selector(method1));
imp();
NSLog(@"------------------------------------------");
}
@end
结果如下图:
result.png
runtime的强大之处在于它能在运行时创建类和对象
动态创建类
动态创建类和对象 主要涉及到以下几个函数:
// 创建一个新类和元类
Class objc_allocateClassPair ( Class superclass, const char *name, size_t extraBytes );
// 销毁一个类及其相关联的类
void objc_disposeClassPair ( Class cls );
// 在应用中注册由objc_allocateClassPair创建的类
void objc_registerClassPair ( Class cls );
- objc_allocateClassPair 函数:如果我们要创建一个根类,则superclass指定为Nil。extraBytes通常指定为0,该参数是分配给类和元类对象尾部的索引ivars的字节数。
重点来了:
- 为了创建一个新类,我们需要调用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)];
程序的输出如下:
2017-10-23 11:35:31.006 RuntimeTest[3800:66152] run sub method 1
2017-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]);
输出结果是:
2017-10-23 12:46:50.781 RuntimeTest[4039:89088] NSString
2017-10-23 12:46:50.781 RuntimeTest[4039:89088] __NSCFConstantString
- 可以看到,使用class_createInstance函数获取的是NSString实例,而不是类簇中的默认占位符类__NSCFConstantString。
- objc_constructInstance函数:在指定的位置(bytes)创建类实例。
- objc_destructInstance函数:销毁一个类的实例,但不会释放并移除任何与其相关的引用。
实例操作函数
实例操作函数主要是针对我们创建的实例对象的一系列操作函数,我们可以使用这组函数来从实例对象中获取我们想要的一些信息,如实例对象中变量的值。这组函数可以分为三小类:
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);
- NSObject 相当于上述提到的A 、MyClass.class 相当于上述提到的B
- object_copy 其实是在copy a 的同时,把所需要的内存空间增加了,增加到B类对象所需要的空间
- 通过 object_setClass() 函数赋值
- 针对对象的 变量进行操作的函数:
// 修改类实例的实例变量的值
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 );
以上内容是第一部分内容,完。
网友评论