objective-c是mac osx下的编程语言,其中Runtime库为oc语言的动态属性提供支持,因此所有通过oc编译的应用程序都可以连接到该库。
应用程序连接到runtime库,需要引入runtime头文件。
#import <objc/runtime.h>
runtime库的主要设计意图是作为objective-c和其他编程语言之间的桥接层,或者作为一个低级的调试方式。而在macOS操作系统上的oc runtime库不同于Mac生态环境下的runtime库。最新的runtime库是更新于版本为10.5的OS X系统。
oc runtime支持的内容很多,目前仅仅介绍在runtime时对类对象的属性和方法的调用。
首先,我们先通过一个runtime类的例子讲述。
在OS X系统下,获取鼠标指针的大小,有如下的方式
CGPoint point = [[NSCursor currentSystemCursor] hotSpot]
有没有其他的方式,也能获取这个属性呢?
static void* getIvarPoniter(id object, char const *name) {
Ivar ivar = class_getInstanceVariable(object_getClass(object), name);
if(!ivar) return 0;
return (uint8_t*)(__bridge void*)object + ivar_getOffset(ivar);
}
CGPoint *point = getIvarPointer([NSCursor currentSystemCursor], "_hotSpot");
如上所示,这是runtime库通过实例指针,获取hotspot的偏移指针取得的point
当然我们首先需要知道NSCursor的类结构,这里提供GitHub链接NSCursor
NSCursor结构如下:
@interface NSCursor : NSObject <NSCoding>
{
struct CGPoint _hotSpot;
struct _cursorFlags {
unsigned int onMouseExited:1;
unsigned int onMouseEntered:1;
unsigned int cursorType:8;
unsigned int :22;
} _flags;
id _image;
}
这里使用的runtime提供的api为class_getInstanceVariable,是用于获取实例对象的变量地址。
如上文写的方法是获取实例类的成员变量,同样的,我们也可以获取实例的方法。
runtime库提供的api有
/**
* Returns a specified instance method for a given class.
*
* @param cls The class you want to inspect.
* @param name The selector of the method you want to retrieve.
*
* @return The method that corresponds to the implementation of the selector specified by
* \e name for the class specified by \e cls, or \c NULL if the specified class or its
* superclasses do not contain an instance method with the specified selector.
*
* @note This function searches superclasses for implementations, whereas \c class_copyMethodList does not.
*/
OBJC_EXPORT Method class_getInstanceMethod(Class cls, SEL name)
__OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);
/**
* Returns a pointer to the data structure describing a given class method for a given class.
*
* @param cls A pointer to a class definition. Pass the class that contains the method you want to retrieve.
* @param name A pointer of type \c SEL. Pass the selector of the method you want to retrieve.
*
* @return A pointer to the \c Method data structure that corresponds to the implementation of the
* selector specified by aSelector for the class specified by aClass, or NULL if the specified
* class or its superclasses do not contain an instance method with the specified selector.
*
* @note Note that this function searches superclasses for implementations,
* whereas \c class_copyMethodList does not.
*/
OBJC_EXPORT Method class_getClassMethod(Class cls, SEL name)
__OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);
这里同样以NSCursor为例,调用的方法为
- (long long)_coreCursorType;
实例如下
//获取实例方法
SEL coreCursorType = NSSelectFromString(@"_coreCursorType");
Method method_coreCursorType = class_getInstanceMethod([NSCursor class], coreCursorType);
//调用实例方法
IMP imp = method_getImplementation(method_coreCursorType);
NSCursor *cursor = [NSCursor currentSystemCursor];
typedef long long (*fn)(id,SEL);
fn f = (fn)imp;
long long ret = f(cursor, coreCursorType);
这里可能调用失效,因为NSCursor目前似乎把_coreCursorType这个方法给废弃了
从上例中看到,在获取实例方法时,这里有几个不认识的方法
SEL
Method
IMP
这些都是定义在runtime中的概念,这三个概念分别对应于
typedef struct objc_selector *SEL;
typedef objc_method Method;
id (*IMP)(id, SEL, ...)
struct objc_method
{
SEL method_name;
char * method_types;
IMP method_imp;
};
Method是一个结构体,其中
method_name 是函数签名
method_types 是函数参数类型
method_imp 是函数指针
当然,如果是同名函数的实现类,通过函数签名获取的method就不一定是对的。
这个Method获取,会逐级向上找,即当前类如果没有找个签名函数,就会去当前类的父类中寻找,找不到会继续向上找。
IMP是implement的缩写,提供了方法的内存地址,而IMP的应用场景往往是不希望通过发送消息的调用语法
[classA invokeMethodA]
或objc_msgSend()方法调用。就可以获取IMP来调用方法。
SEL在cocoa编程中更为常见,作为选择器,通过调用选择器,并制定参数来实现
SEL coreCursorType = NSSelectFromString(@"_coreCursorType");
NSCursor *cursor = [NSCursor currentSystemCursor];
long long ret = [cursor performSelector:coreCursorType];
//如果带有参数 for example
[cursor performSelector:sel withObject:@"params"];