前言
面试的时候面试官问:讲讲对runtime
的理解,runtime
能做些什么? 我基本上就是回答动态添加属性、添加方法、方法的替换……等网络罗列的一大堆。但是具体怎么做,这个嘛,我也说不出来。基于此,我特地研究了一下runtime.h
这个文件,过滤了一下里面所有的方法并且了解基本功能并记录在此。
准备
进入到runtime.h
文件里面,发现里面有些方法标记的是OBJC_ARC_UNAVAILABLE
,所以我们需要单独创建一个MRC
项目,以便于尝试所有方法。
创建项目
这里我们创建Command Line Tool
工程即可,如图一:
MRC
项目创建完成后,默认是ARC
模式的,这里我们修改为MRC
。在TATGET-> Build Settings->Objective-C Automatic Reference Counting->NO
,默认为YES
表示使用ARC
模式,这里我们修改为NO
即可。
类
创建我们的测试类,创建Animal
类,添加一个name
属性和一个speak
方法,一起看下类的构造:
方法介绍
准备好了之后,来到main.m
文件,导入objc/runtime.h
和Animal.h
头文件文件。
实例方法
object_copy
原型:
1 | OBJC_EXPORT id _Nullable object_copy(id _Nullable obj, size_t size) |
描述:拷贝对象用的,和copy
方法一样,返回一个新的对象。
参数:obj
被拷贝的对象,size
对象的占用内存大小,从源码可知,size
最小为16。
注意:这个方法是OBJC_ARC_UNAVAILABLE
,在ARC下不可用,而且如果参数obj
为nil
或者是TaggedPointer
,则直接返回obj
,并不进行拷贝。
object_dispose
原型:
1 | OBJC_EXPORT id _Nullable object_dispose(id _Nullable obj) |
描述:销毁对象,并且释放空间。
参数:obj
要释放的对象。
注意:在demo
中,调用此方法后,对象还是能正常使用,没有查找到原因,改天研究下单列一篇文章。
源码:
1 | id object_dispose(id obj) |
从源码中可以看到,首先判断obj
是否为空,否则调用objc_destructInstance
销毁对象,然后调用free()
释放空间。
object_getClass
原型:
1 | OBJC_EXPORT Class _Nullable |
描述:获取对象的Class
,和对象直接调用class
方法基本一致,内部实现是返回obj
的isa
指针,对于isa
指针这里不赘述。
注意:如果obj
传nil
,则返回值为Nil
object_setClass
原型:
1 | OBJC_EXPORT Class _Nullable |
描述: 修改一个对象的类,目的是修改一个对象的isa
指针,返回值是旧的class
。
注意:调用此方法之后,对象的类型就会改变,所以在开发中应该谨慎使用此方法。
由下图中可以看到,调用object_setClass
方法后,a1
由原来的Animal
变成NSObject
对象,然后在向a
发送消息立即崩溃。崩溃信息提示NSObject
不存在setName
方法。
object_isClass
原型:
1 | OBJC_EXPORT BOOL |
描述:判断一个对象是否是类对象,注意:类也是一个对象,具体为什么,这里不会赘述。
由下图可以看出,a1
不是一个类,所以他不是类对象,Animal
是一个类,所以返回TRUE
。
object_getIvar
原型:
1 | OBJC_EXPORT id _Nullable |
描述: 获取obj
的ivar
的值。和这个方法功能类似的还有一个方法object_setInstanceVariable
,但是这个获取的结果速度回更快一些,从object_setInstanceVariable
源码中可以看出来,最终也是调用的这个方法。
为了测试此方法,我们为Animal
类添加一个变量age
。
由下图也可以看出属性和变量的区别。
object_getInstanceVariable
原型:
1 | OBJC_EXPORT Ivar _Nullable |
描述:这个方法和上个方法类似,也是获取属性或者变量的值,只不过参数和返回值不同。
参数:obj
被获取的对象;name
变量的名称,字符串格式;outValue
这个变量的值。
通过源码可以看出这个方法的实现过程就是通过name
获取Ivar
,然后调用上述方法。所以官方注释上说此方法慢与上述方法。这里就不上图了,图和👆的一样。
object_setIvar
object_setIvarWithStrongDefault
原型:
1 | OBJC_EXPORT void |
1 | OBJC_EXPORT void |
描述:因为这两个方法基本相同,所以这里放在一块说了,这两个方法是给变量赋值,相当于a1.name=我是小狗
。
这两个方法的区别在于第一个会给属性用Unretained
修饰,第二个会用Strong
修饰,注意这个前提是这个属性没有被修饰的情况下才会起作用。比如name
已经被copy
修饰了,那就保持原来的样子,那么这两个方法是没有区别的。
参数:obj
被赋值的对象;Ivar
即将赋值的变量,value
被赋的值。
注意:如果对象没有这个属性,是不能赋值的。
object_setInstanceVariable
object_setInstanceVariableWithStrongDefault
原型:
1 | OBJC_EXPORT Ivar _Nullable |
1 | OBJC_EXPORT Ivar _Nullable |
描述:这两个方法和上面两个方法基本相同,只是参数和返回值不同,从源码可以看出内部调用的也是上述两个方法而已。直接演示使用方法:
获得类定义
objc_getClass
objc_lookUpClass
原型:
1 | OBJC_EXPORT Class _Nullable |
1 | OBJC_EXPORT Class _Nullable |
描述:上面两个方法都是通过字符串获取一个Class
,并且整个类是在工程里面存在的,否则会返回nil
。
注释上说objc_getClass
方法会在第一次没有找到Class
的情况下会再次查询一变并且执行回调。
从源码上面看着两个方法都是执行了look_up_class(const char *aClassName, bool includeUnconnected, bool includeClassHandler)
这个方法。
区别在于includeClassHandler
这个参数。objc_getClass
传的是YES
,而后者是NO
。
在look_up_class
实现上看,旧版本的实现是按照注释上所说的一样。但是新版本的这个参数被标记为unused
,基本上现在两个方法相同。
objc_getRequiredClass
原型:
1 | OBJC_EXPORT Class _Nonnull |
描述:这个方法和上面的objc_getClass
方法基本一致,内部调用的就是objc_getClass
这个方法,只不过在没有找到方法的情况下,会报链接失败。贴下源码:
1 | Class objc_getRequiredClass(const char *aClassName) |
objc_getMetaClass
原型:
1 | OBJC_EXPORT Class _Nonnull |
描述:获取类的元类,这个方法和objc_getRequiredClass
基本类似,也是调用了objc_getClass
这个方法,只不过在没有找到Class
的时候会报提示信息,不会Crash
,然后会返回Nil
,最终返回Class
的isa
指针。从图中看到控制台里面打印信息。
贴下源码:
1 | Class objc_getMetaClass(const char *aClassName) |
objc_getClassList
原型:
1 | OBJC_EXPORT int |
描述:获取指定的个数类列表。buffer
需要传入一个Class
数组,bufferCount
需要传入想要的个数,返回值是类的总数。如果bufferCount
传入的数比类的总数大,则最多能返回所有的类。如果bufferCount
比类的总数小,则会返回这个个数的类,从下图中可以看到已经有13062
个类了。
注意:获取的类有可能不是NSObject
的子类,所以要小心调用这些类的方法.
图中实例获取10个类:
objc_copyClassList
原型:
1 | OBJC_EXPORT Class _Nonnull * _Nullable |
描述:这个方法和上面的方法类似,只不过这个是一次性获取所有的类,outCount
向外输出类的总个数。
注意:这个方法返回值是Class
指针,所以在使用完成后需要调用free()
进行释放。
类方法
class_getName
原型:
1 | OBJC_EXPORT const char * _Nonnull |
描述: 获取Class
的名字,返回值类型是const char *
。这个在简单不过,上图已经演示了。不在赘述。
class_isMetaClass
原型:
1 | OBJC_EXPORT BOOL |
描述:判断一个Class
是否是元类,返回值是bool
类型。
class_getSuperclass
原型:
1 | OBJC_EXPORT Class _Nullable |
描述:获取Class
的父类。 和OC
的superclass
功能一样。
从下图可以看出Animal
的父类是NSObject
。
class_setSuperclass
原型:
1 | OBJC_EXPORT Class _Nonnull |
描述:改变某个Class
的父类。一定要谨慎使用这个函数。被标记为not recommended
。返回值是就的父类。
看下图,当把Animal
的父类改成NSString
之后,把a1
当做字符串操作,运行没有任何问题。但是当调用Animal
原来的方法之后,就会报方法找不到。
class_getVersion
class_setVersion
1 | OBJC_EXPORT int |
1 | OBJC_EXPORT void |
描述:这两个方法比较简单,就放一块说了,其实就是设置和获取Class
的版本。 直接上图:
class_getInstanceSize
原型:
1 | OBJC_EXPORT size_t |
描述:获取这个类所占用字节数。不详细讲了,网上都是讲解。
class_getInstanceVariable
原型:
1 | OBJC_EXPORT Ivar _Nullable |
描述:通过一个Class
获取指定属性。 这个和object_getInstanceVariable
基本一致,返回的是同一个Ivar
对象。只不过object_getInstanceVariable
会向外输出值。
class_getClassVariable
原型:
1 | OBJC_EXPORT Ivar _Nullable |
描述: 这个方法至少我认为基本上没什么用处。由字面意思看是获取类变量,OC
语法也不支持这个。
从源码上来看这个就是获取ISA
指针的属性,图上演示的是等效的。
class_copyIvarList
原型:
1 | OBJC_EXPORT Ivar _Nonnull * _Nullable |
描述:我估计这个方法我们接触的比较多,因为在利用runtime
归档 、解档需要用到这个方法;这个方法主要是返回Class
的所有变量,返回值是一个Ivar
数组。
注意:在用完Ivar
数组之后,必须调用free()
函数进行释放,否则会导致内存泄露。
class_getInstanceMethod
原型:
1 | OBJC_EXPORT Method _Nullable |
描述:通过SEL
获取Method
,这个方法估计也很熟悉,在方法替换的的时候,就是通过这个来获取实例方法的。
注意:如果方法只在.h
声明,但是没有在.m
里面实现的话,是找不到此方法的。这个方法会在当前Class
没有找到对应的SEL
的时候,会一直向父级查找, 如下图都能正确找到方法的地址。
class_getClassMethod
原型:
1 | OBJC_EXPORT Method _Nullable |
描述:获取类方法,和class_getInstanceMethod
基本差不多。
为了测试此方法,我们在为Animal
添加一个run
方法,并且在.m
里面实现,此方法能正确返回。如图:
class_getMethodImplementation
原型:
1 | OBJC_EXPORT IMP _Nullable |
描述: 获取方法的实现。
class_getMethodImplementation_stret
原型:
1 | OBJC_EXPORT IMP _Nullable |
描述:获取方法的转发消息的实现。
class_respondsToSelector
原型:
1 | OBJC_EXPORT BOOL |
描述: 判断一个类是否实现了某个方法。和[NSObject respondsToSelector:@selector()];
方法一样。
class_copyMethodList
原型:
1 | OBJC_EXPORT Method _Nonnull * _Nullable |
描述:获取所有的Class
实现的实例方法。不包括类方法,也不包括父类方法。
注意:此方法会返回Method
数组,在用完之后需要调用free()
释放,否则会导致内存泄露。
从下图可以看出,给类添加属性之后,会自动生成get set
方法。
class_conformsToProtocol
原型:
1 | OBJC_EXPORT BOOL |
描述:判断Class
是否遵循某个协议.和[NSObject conformsToProtocol:]
方法一样。
class_copyProtocolList
原型:
1 | OBJC_EXPORT Protocol * __unsafe_unretained _Nonnull * _Nullable |
描述:获取Class
所有遵循的协议。返回值是一个OC
类数组Protocol **
。
注意:在使用完后调用free()
方法释放内存。
class_getProperty
原型:
1 | OBJC_EXPORT objc_property_t _Nullable |
描述:通过name
获取指定的属性。 仅仅包含@property
标记的属性。不包含变量和父级属性,如下图崩溃原因是没用获取到age
属性,调用相应的方法直接报错。
class_copyPropertyList
原型:
1 | OBJC_EXPORT objc_property_t _Nonnull * _Nullable |
描述:获取所有的属性,仅仅包含@property
标记的属性。不包含变量和父级属性。
注意:该方法返回值是一个objc_property_t
数组,并且在使用完后一定要调用free()
方法释放内存,否则会导致内存泄露。
class_getIvarLayout
原型:
1 | OBJC_EXPORT const uint8_t * _Nullable |
class_getWeakIvarLayout
原型:
1 | OBJC_EXPORT const uint8_t * _Nullable |
class_addMethod
原型:
1 | OBJC_EXPORT BOOL |
class_replaceMethod
原型:
1 | OBJC_EXPORT IMP _Nullable |
class_addIvar
原型:
1 | OBJC_EXPORT BOOL |
class_addProtocol
原型:
1 | OBJC_EXPORT BOOL |
class_addProperty
原型:
1 | OBJC_EXPORT BOOL |
class_replaceProperty
原型:
1 | OBJC_EXPORT void |
class_setIvarLayout
原型:
1 | OBJC_EXPORT void |
class_setWeakIvarLayout
原型:
1 | OBJC_EXPORT void |
objc_getFutureClass
原型:
1 | OBJC_EXPORT Class _Nonnull |