Objective-C是一门动态语言,把静态语言在编译和链接时期做的事情放到运行的时候来处理。这样我们写代码时可以把消息转发给我们想要的对象,或者交换一个方法的实现等。
但这种特性需要一个运行时系统来执行编译的代码,让所有工作可以正常运行。这个运行时系统就是Objc Runtime,它其实是一个Runtime库,使用C和汇编写的库。
Runtime库做的事情:
- 封装:runtime里面,对象用C语言的结构体表示,方法用C语言的函数的表示,另外可以加上一些额外的特性。这样在运行的时候,创建,检查,修改类、对象和他们的方法。
- 找到最终执行方法的代码:当程序执行[object doSomething]时,会给消息接收方发送消息,runtime会根据接收消息者能否执行方法做出不同的反应。
一.runtime里面的类
1、类
OC中类是Class
类型,它是一个指向objc_class
结构体的指针。
typedef struct objc_class *Class;
objc/runtime.h中objc_class结构体的定义如下(如前面写的,类用结构体表示了):
// An opaque type that represents an Objective-C declared property.
typedef struct objc_property *objc_property_t;
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
2、objc_object(类的实例的结构体)与id
objc_object是一个类的实例的结构体,定义如下:
id是objc_object结构体类型的指针。
/// Represents an instance of a class.(声明了一个实例类)
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
typedef struct objc_object *id;
isa指针注意:
1和2里面需要注意的字段:
1.isa指针
- 发送消息时(执行方法时),isa指向谁,就在谁里面查找要执行的方法;
- 发送消息给一个对象时,isa指针指向这个对象所属的类,会在所属类和父类里面查找要执行的方法;
- 发送消息给一个类时,isa指针指向这个类的元类,元类里面包含了所有的类方法。就会在元类(meta-class)的方法列表里面查找要执行的方法。
eg:[NSArray array];就是在给NSArray发送消息,在NSArray的元类里面查找要执行的方法。
- 发送消息给一个元类(meta-class),所有元类的isa指针都指向NSObject(基类)的元类,而NSObject的isa指针指向自己。形成了一个闭环。
2.super_class:指向该类的父类,如果该类已经是最顶层的根类(如NSObject或NSProxy),则super_class为NULL。
3.cache:用于缓存最近使用的方法。
发送一个消息时,isa指针都会去寻找可以执行该消息的对象。
在每次调用过一个方法之后,这个方法会被缓存到catch之中,下次调用同样方法的时候runtime优先去catch里面查找,如果catch里面没有才去methodlists中查找方法。这样对于经常调用的方法就提高了效率。
eg:
NSArray *array = [[NSArray alloc] init];调用流程:
1、执行[NSArray alloc] 方法,NSArray里面没有alloc方法,就去父类NSObject里面查找,找到后分配内存空间,并把alloc方法放到缓存里面,‘isa’指针指向NSArray类;
2、执行init方法,NSArray响应该方法,就加入‘catche’,否则就去父类里面查找。
3、以后如果在调用 [[NSArray alloc] init]方法,则直接从catche里面取出相应的方法。
- version:版本;在对象的序列化中,可以识别出不同类定义版本中实例变量布局的改变。
3、元类(meta-class)
- 每个类都有一个元类,这个元类里面放着所有的类方法。
- 元类(meta-class)是一个类对象的类。
- 当我们向一个对象发送消息时,runtime会在对象的所属类的方法列表里面寻找。
- 当我们向一个类发送消息时(调用类方法时),就会在这个类的元类的方法列表中查找。
eg:
//我们在给NSArray发送消息。把NSArray当做一个对象(类对象),在给NSArray对象发送array消息。就会到NSArray的元类里面查找array方法。
NSArray *array = [NSArray array];
二、类和对象的操作函数
类的操作方法大部分是以class_
为前缀的;
对象的操作方法大部分是以objc_
或object_
为前缀;
1、类相关操作函数
runtime对类提供的相关操作方法主要是针对objc_class
中各个结构体提供的。
在操作之前,我们创建MyClass类,用MyClass进行操作:
.h文件:
#import <Foundation/Foundation.h>
@interface MyClass : NSObject<NSCopying>
@property(nonatomic,strong) NSArray * array;
@property(nonatomic,copy) NSString * string;
-(void)method1;
-(void)method2;
+(void)classMethod1;
@end
.m文件:
#import "MyClass.h"
@interface MyClass (){
NSInteger instant1;
NSString * instant2;
}
@property(nonatomic,assign) NSUInteger integer;
-(void)method3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2;
@end
@implementation MyClass
+(void)classMethod1{
}
-(void)method1{
NSLog(@"method1");
}
-(void)method2{
NSLog(@"method2");
}
-(void)method3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2{
NSLog(@"arg1 : %ld, arg2 : %@", arg1, arg2);
}
@end
viewcontroller里面:
#import "ViewController.h"
#import "MyClass.h"
#import <objc/runtime.h>
MyClass * myClass = [[MyClass alloc] init];
1-1、获取类的类名
const char * className = class_getName(myClass.class);
const char * nilName = class_getName(nil);
NSLog(@"className:%@,nilName:%@",[NSString stringWithUTF8String:className],[NSString stringWithUTF8String:nilName]);
//打印:className:MyClass,nilName:nil
结论:
const char * class_getName ( Class cls )函数
如果传入的cls为Nil,则返回一个字字符串"nil"
1-2、获取类的父类
Class superClass = class_getSuperclass(myClass.class);
Class nilClass = class_getSuperclass(nil);
Class noSuperClass = class_getSuperclass([[NSObject alloc] init].class);//测试没有父类的情况
NSLog(@"superClass:%@,nilClass:%@,noSuperClass:%@",superClass,nilClass,noSuperClass);
//打印:superClass:NSObject,nilClass:(null),noSuperClass:(null)
结论:
Class class_getSuperclass ( Class cls );
cls为Nil或者cls为根类时,返回Nil
1-3、获取类的元类
Class metaClass = objc_getMetaClass(class_getName(myClass.class));
1-4、判断一个类是不是元类
//获取一个元类
Class metaClass = objc_getMetaClass(class_getName(myClass.class));
BOOL isMeta = class_isMetaClass(myClass.class);
BOOL isMeta1 = class_isMetaClass(metaClass);
NSLog(@"isMeta:%d,isMeta1:%d",isMeta,isMeta1);
//打印:isMeta:0,isMeta1:
1-5、获取实例变量大小
size_t classSize = class_getInstanceSize(myClass.class);
NSLog(@"classSize:%zu",classSize);
//打印:classSize:48
2、成员变量ivars及属性
- 在objc_class中,所有的成员变量、属性的信息都放在链表ivars中。
- ivars是一个数组,数组中每个元素是指向objc_ivar结构体(变量信息)的指针。
- 这个数组不包含在父类中声明的变量。
2-1、 成员变量
2-1-1、获取整个成员变量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
- outcount指针返回数组的大小。
- 注意,必须使用free()来释放这个数组。
unsigned int count = 0;
Ivar * ivars = class_copyIvarList(myClass.class, &count);
for (int i = 0; i <count; i++) {
Ivar ivar = ivars[i];
const char * ivarName = ivar_getName(ivar);//获取ivar对应的字段名称
NSLog(@"ivarName:%@",[NSString stringWithUTF8String:ivarName]);
/*
.h:
property(nonatomic,strong) NSArray * array;
@property(nonatomic,copy) NSString * string;
.m:
{
NSInteger instant1;
NSString * instant2;
}
@property(nonatomic,assign) NSUInteger integer;
打印:(获取到了所有成员变量)
ivarName:instant1
ivarName:instant2
ivarName:_array
ivarName:_string
ivarName:_integer
*/
}
free(ivars);
NSLog(@"count:%d",count);//打印:count:5
2-1-2、 获取指定名称实例成员变量的信息
Ivar class_getInstanceVariable ( Class cls, const char *name );
//获取类中指定名称实例成员变量的信息
//是这么定义的:@property(nonatomic,strong) NSArray * array;
const char * instantName = "_array";//!!!!!添加了_
Ivar ivar = class_getInstanceVariable(myClass.class, instantName);
NSLog(@"ivarName:%s",ivar_getName(ivar));//打印:ivarName:_array
//是这么定义的:NSInteger instant1;
Ivar ivar1 = class_getInstanceVariable(myClass.class, "instant1");
NSLog(@"ivarName1:%s",ivar_getName(ivar1));//打印:ivarName1:instant1
2-2、 属性操作
2-2-1 、根据属性名称获取对应的属性
objc_property_t _Nullable
class_getProperty(Class _Nullable cls, const char * _Nonnull name)
//是这么定义的:@property(nonatomic,strong) NSArray * array;
objc_property_t property1 = class_getProperty(myClass.class, "array");
if (property1) {
NSLog(@"property1Name:%s",property_getName(property1));
//打印:property1Name:array
}
//是这么定义的NSInteger instant1;
objc_property_t property2 = class_getProperty(myClass.class, "instant1");
if (property2) {//未进入if里面
NSLog(@"property2Name:%s",property_getName(property1));
}
- 注意查看2-1-2、2-2-1,获取@property修饰的成员变量,使用class_getInstanceVariable获取时,要加上“_”;使用class_getProperty获取属性时不需要。
- 属性即@property修饰定义的。@property修饰的才能使用class_getProperty获取到。
- class_getProperty获取到的是objc_property_t类型的,class_getInstanceVariable获取到的是Ivar类型的。
2-2-2、 获取属性列表
unsigned int propertyCount = 0;
objc_property_t * proprties = class_copyPropertyList(myClass.class, &propertyCount);
for (int i = 0 ; i < propertyCount; i++) {
objc_property_t property = proprties[i];
NSLog(@"i:%d,propertyName:%s",i,property_getName(property));
/*
打印:
i:0,propertyName:integer
i:1,propertyName:array
i:2,propertyName:string
*/
}
NSLog(@"propertyCount:%d",propertyCount);//打印:propertyCount:3
3、方法(methodLists)
3-1 、获取所有方法
- 返回包含所有实例方法的数组;
- 该列表不包含父类实现的方法。
- outCount参数返回方法的个数。
- 注意:在获取到列表后,我们需要使用free()方法来释放它。
unsigned int methodCount = 0;
Method * methods = class_copyMethodList(myClass.class, &methodCount);
for (int i = 0; i < methodCount; i++) {
Method method = methods[i];
NSLog(@"method:%s",method_getName(method));
/*
打印:
method:method1
method:method2
method:method3WithArg1:arg2:
method:integer
method:setInteger:
method:setArray:
method:.cxx_destruct//ARC下对象实例变量的释放过程在.cxx_destruct内完成
method:setString:
method:array
method:string
*/
}
NSLog(@"methodCount:%d",methodCount);//打印: methodCount:10
free(methods);
结论:
方法总数 =自定义的方法(不包含类方法)+(数据类型的get方法+数据类型set方法)*有几种数据类型+cxx_destruct
当前的MyClass类:
自定义方法 3 + 2 * 3(数据类型有NSArray、NSString、NSUInteger)+1(cxx_destruct方法) = 10个
3-2 、获取所有类方法
一个类的类方法是定义在元类里面。
返回对象的类:Class object_getClass ( id obj );
如果要获取类方法,则可以使用class_copyMethodList(object_getClass(cls), &count)
unsigned int classMethodCount = 0;
Method * classMethods = class_copyMethodList(object_getClass(myClass.class), &classMethodCount);
for (int i = 0; i < classMethodCount; i++) {
Method method = classMethods[i];
NSLog(@"classMethods:%s",method_getName(method));
/*
打印:classMethods:classMethod1
*/
}
NSLog(@"classMethodCount:%d",classMethodCount);//classMethodCount:1
free(classMethods);
3-3、 获取类方法
Method classMethod = class_getClassMethod(myClass.class, @selector(classMethod1));
3-4、 获取实例方法
Method instantMethod = class_getInstanceMethod(myClass.class, @selector(method1));
与class_copyMethodList不同的是:
class_getInstanceMethod、class_getClassMethod函数,这两个函数都会去搜索父类的实现。
3-5、 获取方法的具体实现
IMP methodImp2 = class_getMethodImplementation_stret(myClass.class, @selector(method2));
methodImp2();//调用method2成功
class_getMethodImplementation函数,该函数在向类实例发送消息时会被调用,并返回一个指向方法实现函数的指针。这个函数会比method_getImplementation(class_getInstanceMethod(cls, name))更快。返回的函数指针可能是一个指向runtime内部的函数,而不一定是方法的实际实现。例如,如果类实例无法响应selector,则返回的函数指针将是运行时消息转发机制的一部分。
3-6、 添加方法
//viewcontroller中添加方法:
-(void)methodForMyClass{
NSLog(@"methodForMyClass");
}
//添加方法
IMP methodImp = class_getMethodImplementation(self.class, @selector(methodForMyClass));
class_addMethod(myClass.class, @selector(methodForMyClass), methodImp, "v@:");
[myClass performSelector:@selector(methodForMyClass) withObject:nil afterDelay:0];//成功执行methodForMyClass方法
3-7、 替代方法的实现:
- 如果类中不存在name指定的方法,则类似于class_addMethod函数一样会添加方法;
- 如果类中已经存在name指定的方法,则类似于method_setImplementation一样替代原方法的实现
//viewcontroller中添加方法:
-(void)addMethod{
NSLog(@"addMethod");
}
//替换:(addMethod替换method1里面的实现)
IMP methodImp3 = class_getMethodImplementation_stret(self.class, @selector(addMethod));
IMP replaceMethod = class_replaceMethod(myClass.class, @selector(method1), methodImp3, "v@:");//用addMethod替代method1的实现
[myClass method1];//打印:addMethod
3-8、 类实例是否响应指定的selector:
BOOL isRespond = class_respondsToSelector(myClass.class, @selector(method1));
NSLog(@"isRespond:%d",isRespond);
4、协议
4-1、获取类实现的协议列表
unsigned int protocolCount = 0;
Protocol * __unsafe_unretained _Nonnull * protocols = class_copyProtocolList(myClass.class, &protocolCount);
for (int i = 0; i < protocolCount; i++) {
Protocol * protocol = protocols[i];
NSLog(@"protocolName:%s",protocol_getName(protocol));
/*
打印:protocolName:NSCopying
*/
}
三、动态创建类和对象
1、动态创建类
- 为了创建一个新类,我们需要调用objc_allocateClassPair。
- 然后使用诸如class_addMethod,class_addIvar等函数来为新创建的类添加方法、实例变量和属性等。
- 完成这些后需要调用objc_registerClassPair函数来注册类,之后这个新类就可以在程序中使用了。
注:实例方法和实例变量应该添加到类自身上,而类方法应该添加到类的元类上。
1-1、动态创建类、元类
objc_allocateClassPair函数:
如果我们要创建一个根类,则superclass指定为Nil。
extraBytes通常指定为0,该参数是分配给类和元类对象尾部的索引ivars的字节数。
Class newClass = objc_allocateClassPair(NSObject.class, "Student", 0);
1-2、为类添加成员变量
- 已经有的变量再添加会失败
- 这个方法只能在objc_allocateClassPair之前,objc_registerClassPair之后调用
- 不能给一个已经存在的类添加
- 不能给元类添加
- 员变量的按字节最小对齐量是1<<alignment
- 如果变量的类型是指针类型,则传递log2(sizeof(pointer_type))
BOOL addIVar = class_addIvar(newClass, "_ivar1", sizeof(NSString *), log(sizeof(NSString *)), "i");
1-3、为类添加属性
objc_property_attribute_t type = {"T","@\"NSString\""};
objc_property_attribute_t ownership = {"C",""};
objc_property_attribute_t backingivar = {"V","_ivar1"};
const objc_property_attribute_t properties[] = {type,ownership,backingivar};
class_addProperty(newClass, "propery", properties, 3);
//添加方法
IMP method1Imp = class_getMethodImplementation(self.class, @selector(newClassMethod1));
class_addMethod(newClass, @selector(newClassMethod1), method1Imp, "v@:");
1-4、注册类
objc_registerClassPair(newClass);
//调用
id instantce = [[newClass alloc] init];
[instantce performSelector:@selector(newClassMethod1) withObject:nil afterDelay:0];//执行newClassMethod1成功
1-4、销毁
objc_disposeClassPair用于销毁一个函数,如果程序中还存在类或其子类的示例,则不能针对该类调用销毁方法。
objc_disposeClassPair(newClass);
2、动态创建对象/动态创建类的实例
2-1、 创建类实例:
-
id class_createInstance ( Class cls, size_t extraBytes );
extraBytes参数表示分配的额外字节数。
效果与+alloc方法类似 -
使用class_createInstance函数获取的是NSString实例,而不是类簇中的默认占位符类__NSCFConstantString
id theObject = class_createInstance(NSString.class, sizeof(unsigned));
id str1 = [theObject init];
NSLog(@"%@", [str1 class]);//打印:NSString
id str2 = [[NSString alloc] initWithString:@"test"];
NSLog(@"%@", [str2 class]);//打印:__NSCFConstantString
2-2、 在指定位置创建类实例
id objc_constructInstance ( Class cls, void *bytes );
2-3、 销毁类实例:
不会释放并移除任何与其相关的引用
void * objc_destructInstance ( id obj );
3、实例操作函数
3-1、针对整个对象进行操作的函数
// 返回指定对象的一份拷贝
id object_copy ( id obj, size_t size );
// 释放指定对象占用的内存
id object_dispose ( id obj );
NSObject *a = [[NSObject alloc] init];
id newB = object_copy(a, class_getInstanceSize(MyClass.class));
object_setClass(newB, MyClass.class);
object_dispose(a);
3-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-3、针对对象的类进行操作的函数
// 返回给定对象的类名
const char * object_getClassName ( id obj );
// 返回对象的类
Class object_getClass ( id obj );
// 设置对象的类
Class object_setClass ( id obj, Class cls );
4、获取类定义
- OC动态运行库会自动注册代码中定义的所有类。我们可以在运行时创建类定义并使用objc_addClass函数来注册他们。
- 如果类在运行时未注册:
objc_lookUpClass会返回nil;
objc_getClass会调用类处理回调,并再次确认类是否注册,如果确认未注册,再返回nil;
objc_getRequiredClass函数的操作与objc_getClass相同,只不过如果没有找到类,则会杀死进程;
// 获取已注册的类定义的列表;
//我们不能假设从该函数中获取的类对象是继承自NSObject体系的,所以在这些类上调用方法是,都应该先检测一下这个方法是否在这个类中实现。
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 );
// 返回指定类的元类
// 如果指定的类没有注册,则该函数会调用类处理回调,并再次确认类是否注册,如果确认未注册,再返回nil。不过,每个类定义都必须有一个有效的元类定义,所以这个函数总是会返回一个元类定义,不管它是否有效。
Class objc_getMetaClass ( const char *name );
eg:
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);
}
Runtime笔记二:关联对象Associated Object
上述笔记主要是参考:Objective-C Runtime 运行时之一:类与对象
个人根据自己的理解,把举例部分和注意事项合并到一起。方便查看。
部分代码后续上传到github。
网友评论