Objective-C Runtime介绍

微生俊
2023-12-01

Objective-C Runtime

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这个方法给废弃了

关于IMP和Method

从上例中看到,在获取实例方法时,这里有几个不认识的方法
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"];
 类似资料: