标签:方法 c++ 执行 setvalue select _id 额外 32位 覆盖
Objective-C语言是一门动态语言,他将很多静态语言在编译和链接期做的事放到了运行时来处理。这种动态语言的优势在于:我们写代码更具有灵活性,如我们可以把消息转发给我们想要的对象,或者随意交换一下方法的实现等。
这种特性意味着OC不仅需要一个编译器,还需要一个运行时系统来执行编译的代码。对于OC来说,这个运行时系统就像一个操作系统一样:他让所有的工作可以正常的运行,这个运行时系统就是Objc RunTime。objc RunTime 其实是一个RunTime库,他基本上是用C语言和汇编写的。这个库使得C语言具有了面向对象的能力。
RunTime库主要做以下几件事:
1:封装:在这个库中 对象可以使用C语言中的结构体来表示,而方法可以用C函数来实现,另外再加上了一些额外的特性,这些结构体和函数被RunTime函数封装后,我们就可以在程序运行时创建 检查 修改类 对象和他们的方法了。
2:找出方法的最终执行代码 当程序执行[obj doSomething]时 会向消息接收者(obj)发送一条消息(doSomething) RunTime会根据接收消息者是否能响应此消息而做出不同反应。
OC RunTime有两个版本:Modern runtime 和 legacy runtime Modern Runtime 覆盖了64位的Mac OS X Apps,还有 iOS Apps,Legacy Runtime 是早期用来给32位 Mac OS X Apps 用的,也就是可以不用管就是了。
在这一系列文章中,我们将介绍runtime的基本工作原理,以及如何利用它让我们的程序变得更加灵活。在本文中,我们先来介绍一下类与对象,这是面向对象的基础,我们看看在Runtime中,类是如何实现的。
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;
在这个几个定义中 下面几个字段是我们感兴趣的
1> isa: 需要注意的是在OC中 所有的类自身也是i一个对象 这个对象的Class里面 也有一个isa指针 它指向metaClass(元类) 我们会在后面介绍他。
2> super_class 指向该类的父类 如果该类已经是最顶层的根类(比如NSObject或者NSProxy) 则super_class为NULL
3> cache 用于缓存最近使用的方法,一个接收者接收到对象的时候 他会根据isa指针去查找能够响应这个消息的对象。在实际使用中,这个对象只有一部分方法是常用的 很多方法实际上很少用到或者根本用不上。在这种情况下 如果每次来消息时 我们都是methodLists中遍历一遍 性能会很差。这个时候,cache就派上用场了。在我们每次调用过一个方法后,这个方法就会缓存到cache中,下次调用的时候runtime会优先寻找cache 如果cache中没有,采取methodLists中去找 这样对于经常调用到的方法 就会提高很多效率。
4> version 我们可以使用这个字段来提供类的版本信息 这对于对象的序列化非常有用 它可是让我们识别出不同类定义版本中实例变量布局的改变。
针对cache 我们可以用下面的例子来说明其执行过程:
NSArray *array = [[NSArray alloc] init];
其流程是:
1.[NSArray alloc]先被执行。因为NSArray没有+alloc方法,于是去父类NSObject去查找。
2.检测NSObject是否响应+alloc方法,发现响应,于是检测NSArray类,并根据其所需的内存空间大小开始分配内存空间,然后把isa指针指向NSArray类。同时,+alloc也被加进cache列表里面。
3.接着,执行-init方法,如果NSArray响应该方法,则直接将其加入cache;如果不响应,则去父类查找。
4.在后期的操作中,如果再以[[NSArray alloc] init]这种方式来创建数组,则会直接从cache中取出相应的方法,直接调用。
objc_object是表示一个类的实例的结构体,他的定义如下(objc/objc.h):
struct objc_object { Class isa OBJC_ISA_AVAILABILITY; }; /// A pointer to an instance of a class. typedef struct objc_object *id; #endif
可以看到,这个结构体中只有一个字体,即指向其类的isa指针。这样,当我们向一个Objective-C对象发送消息的时候,运行时库会根据实例对象的isa指针找到实例对象所属的类。RunTime库会在类的方法列表及父类的方法列表中寻找与消息对应的selector之乡的方法。找到后就运行这个方法。
当创建一个特定类的实例对象时 分配的内存包含一个objc_object数据结构,然后是类的实例变量的数据。在NSObject类的alloc 和 allocWithZone:方法是用函数class__createInstance来创建objc_object数据结构。
另外还有我们常见的id,它是一个objc_object结构类型的指针。它的存在可以让我们实现类似于C++中泛型的一些操作。该类型的对象可以转换为任何一种对象,有点类似于C语言中void *指针类型的作用。
上面提到了objc_class结构体中的cache字段,他用于缓存调用过的方法。这个字段是一个指向objc_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可能是不连续的。这个数组可能会随着时间而增长。
在上面我们提到 所有类自身也是一个对象 我们可以向这个对象发送消息(调用类方法)
NSArray *array = [NSArray array];
这个例子中,+array消息发送给了NSArray类 而这个NSArray也是一个对象 既然是对象 那么他也是一个objc_object指针 包含一个指向其类的isa指针。那么 有这么一个疑问,这个isa指针指向了什么? 为了调用+array方法 这个类的isa指针必须指向一个包含这些类方法的一个objc_class结构体,这就引出了meta_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都是有效的。
runtime提供了大量的函数来操作类与对象。类的操作方法大部分是以class为前缀的,而对象的操作方法大部分是以objc或object_为前缀。下面我们将根据这些方法的用途来分类讨论这些方法的使用。
我们可以回过头去看看objc_class的定义,runtime提供的操作类的方法主要就是针对这个结构体中的各个字段的。下面我们分别介绍这一些的函数。并在最后以实例来演示这些函数的具体用法。
类名的操作函数主要有:
const char *class_getName(Class cls)
对于 class_getName(Class cls)函数 如果传入的cls为Nil 则返回一个nil。
//获取父类名 Class class_getSuperclass(Class cls) //判断给定的class是不是一个元类 BOOL class_isMetaClass(Class cls)
class_getSuperclass函数,当cls为Nil或者cls为根类时,返回Nil。不过通常我们可以使用NSObject类的superclass方法来达到同样的目的。
class_isMetaClass函数,如果是cls是元类,则返回YES;如果否或者传入的cls为Nil,则返回NO。
示例代码:
#import "ViewController.h" #import <objc/runtime.h> #import "Person.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; Person *p = [[Person alloc] init]; //获取类名 const char *className = class_getName([p class]); //变成oc中的String NSString *ocName = [NSString stringWithUTF8String:className]; //获取父类的名称 Class superClass = class_getSuperclass([p class]); const char *superName = class_getName(superClass); NSString *superOcName = [NSString stringWithUTF8String:superName]; //判断是否为元类 if (class_isMetaClass([p class])) { NSLog(@"我是元类"); }else { NSLog(@"我不是元类"); } NSLog(@"获取到的类名:%@ 它父类的名称是%@",ocName,superOcName); }
打印结果:
2016-12-19 15:51:42.750 RunTimeDemo[1239:93957] 我不是元类 2016-12-19 15:51:42.752 RunTimeDemo[1239:93957] 获取到的类名:Person 它父类的名称是NSObject
// 获取实例大小 size_t class_getInstanceSize ( Class cls );
代码示例:
//获取实例变量的大小 size_t size = class_getInstanceSize([p class]); NSLog(@"获取实例变量的大小%zu",size); //结果 2016-12-19 15:59:20.350 RunTimeDemo[1257:96333] 获取实例变量的大小8
获取一个类的成员变量 主要使用一下函数
// 获取类中指定名称实例成员变量的信息 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()来释放这个数组。
示例代码:
- (void)getClassIvarDemo {
UInt32 count;
//动态获取某个类中的成员变量
Ivar *ivars = class_copyIvarList([Person class], &count);
//遍历属性 列表
for (UInt32 i = 0; i < count; i ++) {
Ivar ivar = ivars[i];
const char *ivarName = ivar_getName(ivar);
NSString *ocName = [NSString stringWithUTF8String:ivarName];
//获取成员变量的类型
const char * typeName = ivar_getTypeEncoding(ivar);
NSLog(@"成员变量:%@ 成员变量的类型%@",ocName,[NSString stringWithUTF8String:typeName]);
}
free(ivars);
Person *p = [[Person alloc] init];
p.name = @"tian";
//获取Person类中一个指定的类的信息
Ivar ivar = class_getInstanceVariable([Person class], "_name");
//获取该属性的值
id name = object_getIvar(p, ivar);
NSString *newName = @"wang";
if ( name != newName) {
//为成员变量赋值
object_setIvar(p, ivar, newName);
}
NSLog(@"%@ 成员变量新值%@",[NSString stringWithUTF8String:ivar_getName(ivar)],p.name);
}
结果:
2016-12-19 17:07:10.333 RunTimeDemo[1437:122637] 成员变量:_age 成员变量的类型i
2016-12-19 17:07:10.334 RunTimeDemo[1437:122637] 成员变量:_name 成员变量的类型@"NSString"
2016-12-19 17:07:10.334 RunTimeDemo[1437:122637] 成员变量:_ID 成员变量的类型@"NSString"
2016-12-19 17:07:10.334 RunTimeDemo[1437:122637] _name 成员变量新
添加成员变量 将在会面的章节介绍。
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 );
示例代码:
#import <Foundation/Foundation.h> @interface Person : NSObject { NSString *weight; } @property (nonatomic,copy) NSString *name; @property (nonatomic,assign) int age; @property (nonatomic,copy) NSString *ID; @end - (void)getClassPropertyListDemo { uint count; //获取属性列表 objc_property_t *propertys = class_copyPropertyList([Person class], &count); for (uint i = 0; i < count; i ++) { objc_property_t property = propertys[i]; //属性的名字 const char * propertyName = property_getName(property); //属性名称 NSString *ocName = [NSString stringWithUTF8String:propertyName]; //获取属性的类型 const char *propertyType = property_getAttributes(property); NSString *ocType = [NSString stringWithUTF8String:propertyType]; NSLog(@"属性的名字%@ ----- 属性的属性%@",ocName,ocType); } free(propertys); Person *p = [[Person alloc] init]; p.name = @"tianlianfeng"; //获取特定的属性 objc_property_t property = class_getProperty([Person class], "name"); //获取特定属性的值 [p setValue:@"tianxiaoer" forKey:[NSString stringWithUTF8String: property_getName(property)]]; NSLog(@"属性的值%@",p.name); } 结果: 2016-12-19 17:42:10.847 RunTimeDemo[1520:134330] 属性的名字name ----- 属性的属性T@"NSString",C,N,V_name 2016-12-19 17:42:10.848 RunTimeDemo[1520:134330] 属性的名字age ----- 属性的属性Ti,N,V_age 2016-12-19 17:42:10.848 RunTimeDemo[1520:134330] 属性的名字ID ----- 属性的属性T@"NSString",C,N,V_ID 2016-12-19 17:42:10.849 RunTimeDemo[1520:134330] 属性的值tianxiaoer
可以看到打印的属性 并没有 weight 因为weight没有被 @property修饰。
在讲方法前 我们需要了解一些东西。
首先看一下方法的定义 Method 就是一个objc_method结构体
objc_method是类的一个方法的描述。
定义如下:
typedef struct objc_method *Method; struct objc_method { SEL method_name; // 方法名称 charchar *method_typesE; // 参数和返回类型的描述字串 IMP method_imp; // 方法的具体的实现的指针 }
那么方法的实现主要有以下函数:
// 添加方法 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 );
代码示例:
先看一下 为了实验配置的Person类
#import <Foundation/Foundation.h> @interface Person : NSObject { NSString *weight; } @property (nonatomic,copy) NSString *name; @property (nonatomic,assign) int age; @property (nonatomic,copy) NSString *ID; - (void)method1; - (void)method2; + (void)classMethod; @end #import "Person.h" @interface Person () - (void)method3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2; @end @implementation Person - (void)method1 { NSLog(@"我是method1的实现"); } -(void)method2 { NSLog(@"我是method2的实现"); } + (void)classMethod { NSLog(@"我是类方法"); } - (void)method3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2 { NSLog(@"arg1 : %ld, arg2 : %@", arg1, arg2); } @end
//获取一个类中的所有方法列表 - (void) getClassMethodList { uint outCount; //获取所有的方法 但是不知道为什么 类方法 没有打印出来 Method *methods = class_copyMethodList([Person class], &outCount); for (uint i = 0; i < outCount; i ++) { Method method = methods[i]; //获取方法的名字 SEL selector = method_getName(method); NSLog(@"%@",NSStringFromSelector(selector)); } free(methods); }
//获取类中的指定方法
- (void)getClassMethod { //获取类中指定的实例方法 Method method = class_getInstanceMethod([Person class], @selector(method1)); if (method) { //获取该方法的实现指针 IMP imp = class_getMethodImplementation([Person class], @selector(method1)); NSLog(@"方法的实现指针%p",imp); } //获取类中指定的类方法 Method classMethod = class_getClassMethod([Person class], @selector(classMethod)); if (classMethod) { //获取该方法的实现指针 IMP imp = class_getMethodImplementation_stret([Person class], @selector(classMethod)); NSLog(@"方法的实现指针%p",imp); } }
替代方法的实现 好像实例方法不能喝类方法交换 试了两次 程序运行崩溃
- (void)getClassMethod { //获取类中指定的实例方法 Method method = class_getInstanceMethod([Person class], @selector(method1)); //获取该方法的实现指针 IMP imp = class_getMethodImplementation([Person class], @selector(method1)); Method method2 = class_getInstanceMethod([Person class], @selector(method2)); //方法二的实现指针 IMP method2Imp = class_getMethodImplementation([Person class], @selector(method2)); //交换方法的实现 方法一 //替代方法的实现 将方法一的实现 改为方法二的实现 现在调用方法一 实现的是方法二 class_replaceMethod([Person class], @selector(method1), method2Imp, method_getTypeEncoding(method2)); //将method2 方法的实现指针 指向 method1 调用method2实现的是method1 class_replaceMethod([Person class], @selector(method2), imp, method_getTypeEncoding(method)); Person *p = [[Person alloc] init]; [p method1]; //执行结果:我是method2的实现 [p method2]; //执行结果:我是method1的实现 //交换方法的实现 方法二 method_exchangeImplementations(method, method2); //交换方法的实现 方法三 method_setImplementation(method, method2Imp); method_setImplementation(method2, imp); [p method1]; //执行结果:我是method2的实现 [p method2]; //执行结果:我是method1的实现 }
通常来讲 交换或者拦截系统的方法 是我们关心的 因为交换我们自己写的方法 其实没什么意义。
- (void)method3 { NSLog(@"我是方法三"); //拦截了系统方法 但是我们又想让他响应 [self method3]; } - (void)addMethodForSelf { //获取方法 Method viewwill = class_getInstanceMethod([self class], @selector(viewWillAppear:)); Method method = class_getInstanceMethod([self class], @selector(method3)); BOOL success = class_addMethod([self class], @selector(viewWillAppear:), class_getMethodImplementation([self class], @selector(method3)), method_getTypeEncoding(method)) ; if (success) { class_replaceMethod([self class],@selector(method3) , class_getMethodImplementation([self class], @selector(viewWillAppear:)),method_getTypeEncoding(viewwill)); }else { method_exchangeImplementations(viewwill, method); } }
交换方法有几种途径 周全起见,有两种情况要考虑一下。第一种情况是要复写的方法(overridden)并没有在目标类中实现(notimplemented),而是在其父类中实现了。第二种情况是这个方法已经存在于目标类中(does existin the class itself)。这两种情况要区别对待。 (译注: 这个地方有点要明确一下,它的目的是为了使用一个重写的方法替换掉原来的方法。但重写的方法可能是在父类中重写的,也可能是在子类中重写的。) 对于第一种情况,应当先在目标类增加一个新的实现方法(override),然后将复写的方法替换为原先(的实现(original one)。 对于第二情况(在目标类重写的方法)。这时可以通过method_exchangeImplementations来完成交换
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:方法来达到相同目的。
协议相关的操作包含以下函数:
// 添加协议 BOOL class_addProtocol ( Class cls, Protocol *protocol ); // 返回类是否实现指定的协议 BOOL class_conformsToProtocol ( Class cls, Protocol *protocol ); // 返回类实现的协议列表 Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );
示例代码
// 协议 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));
标签:方法 c++ 执行 setvalue select _id 额外 32位 覆盖
原文地址:http://www.cnblogs.com/huanying2000/p/6198443.html