动态的Objective-C——关于消息机制与运行时的探讨(三)

简介: 动态的Objective-C——关于消息机制与运行时的探讨

三、发送消息的几个函数


1.最重要的两个发送消息函数


   既然Objective-C函数最终的调用都是要转换成消息发送,那么了解下面这些消息发送函数是十分必要的,这些方法都定义在objc/message.h文件中,其中最重要的两个方法是:


//发送消息的函数

/*

self:消息的接收对象

op:方法选择器

...:参数

*/

id objc_msgSend(id self, SEL op, ...);

//发送消息给父类

/*

super:父类对象结构体

op:方法选择器

...:参数

*/

id objc_msgSendSuper(struct objc_super *super, SEL op, ...);

objc_msgSend函数前面已经有过介绍,objc_msgSendSuper函数则是从父类中找方法的实现进行执行。需要注意,这个函数非常重要,理解了这个这个函数进行消息发送的原理,你就明白super关键字的某些令人疑惑的行为了。


2.super关键字到底做了什么


   做了这么久的Objective-C开发,你是否真的理解super关键字的含义?你一定会说,这很简单啊,self调用本类的方法,super调用父类的方法。那么我们来看一个小案例:


   在前面创建的命令行工程中新建一个类,使其继承于MyObject类,命名为MyObjectSon,在其中提供两个方法,如下:


MyObjectSon.h文件:


#import "MyObject.h"

@interface MyObjectSon : MyObject

-(void)showClass;

-(void)showSuperClass;

@end

MyObjectSon.m文件:


#import "MyObjectSon.h"


@implementation MyObjectSon

-(void)showClass{

   NSLog(@"%@",[self className]);

}

-(void)showSuperClass{

   NSLog(@"%@",[super className]);

}

@end

分别调用两个方法,你会惊奇的发现,打印结构都是“MyObjectSon”,super关键字失效了么?非也非也,下面我们来用消息发送机制重新模拟这两个方法的调用。


   首先[self className]在调用时会采用前面介绍的消息发送机制先从当前类中找className函数,当前类中并没有提供className函数,所以消息会随着继承链向上传递,找到MyObject类中也没有className函数的实现,会继续向上,最终在NSObject类中找到这个方法,记住,这条消息处理的两个要素是:当前MyObjectSon实例对象作为接收者,NSObject类中的className方法作为调用函数。


   当调用[super className]时,首先会使用objc_msgSendSuper方法进行消息的发送,等价于如下代码:


-(void)showSuperClass{

   //创建父类接收对象结构体

   struct objc_super superObj = {self, object_getClass([MyObject new])};

   NSString * name = ((id(*)(struct objc_super*,SEL))objc_msgSendSuper)(&superObj,@selector(className));

   NSLog(@"%@",name);

}

objc_msgSendSuper函数第一个参数为一个父类接收者结构体指针,objc_super结构体定义如下:


struct objc_super {

   //接收者

   __unsafe_unretained id receiver;

   //接收者类型

   __unsafe_unretained Class super_class;

};

在构造objc_super这个结构体时,receive为接收消息的对象,super_class为从哪个类中查方法。如此来看一些都清楚了,系统首先从MyObject类中找className方法,没有相应的实现,会继续向上直到找到NSObject类中的className方法,之后进行执行。这条消息处理的两个要素是:当前MyObjectSon实例对象作为接收者,NSObject类中的className方法作为调用函数。这样分析下来,无论是使用self执行的className方法还是使用super执行的className方法,行为实质上是完全一致的!

3.一些辅助的消息发送函数


特殊返回值类型对应不同的发送消息函数:


//返回值为结构体时使用此方法发送消息

void objc_msgSend_stret(id self, SEL op, ...);

void objc_msgSendSuper_stret(struct objc_super *super, SEL op, ...);

//返回值为浮点数时使用此方法发送消息

double objc_msgSend_fpret(id self, SEL op, ...);

除了使用SEL方法选择器来发送消息,也可以直接使用Method来发送消息:


//进行函数的调用

/*

receiver:接收者

m:函数

...:参数

*/

id method_invoke(id receiver, Method m, ...);

//返回结构体数据的函数调用

void method_invoke_stret(id receiver, Method m, ...);

Method也是一种结构体指针,其定义如下:


struct objc_method {

   //选择器

   SEL method_name                                          OBJC2_UNAVAILABLE;

   //参数类型

   char *method_types                                       OBJC2_UNAVAILABLE;

   //函数地址

   IMP method_imp                                           OBJC2_UNAVAILABLE;

}  

示例代码如下:


int main(int argc, const char * argv[]) {

   @autoreleasepool {

       MyObject * obj = [[MyObject alloc]init];

       [obj class];

//为了消除未定义选择器的警告

#pragma clang diagnostic push

#pragma clang diagnostic ignored"-Wundeclared-selector"

       //进行消息发送

       Method method = class_getInstanceMethod([MyObject class], @selector(showSelf:age:));

       ((void(*)(id,Method,NSString *,int))method_invoke)(obj,method,@"珲少",25);

#pragma clang diagnostic pop

     

   }

   return 0;

}

下面这些方法可以跳过当前对象,直接进行消息转发:


//跳过当前对象直接进行消息转发机制

id _objc_msgForward(id receiver, SEL sel, ...);

void _objc_msgForward_stret(id receiver, SEL sel, ...);

一点建议,上面两个方法都是以下划线开头,这也表明设计者并不想让你直接调用这个方法,确实如此,这两个方法会直接出发对象的消息转发流程,即便当前对象类已经实现了相应的方法也不会进行查找。


四、是时候来重温下Runtime了


   所谓运行时是针对于编译时而言的,本篇文章的开头,我们就说过Objective-C是一种极动态的运行时语言。对象的行为是在运行时被决定的,我们前边也了解了有关isa指针即Class的内容,虽然我们并不能直接访问isa指针,但是我们可以通过objc/runtime.h文件中定义的运行时方法来获取或改变类与对象的行为。


1.类相关操作函数


//对OC对象进行内存拷贝 在ARC环境下不可用

/*

obj:要拷贝的对象

size:内存大小

*/

id object_copy(id obj, size_t size);

//进行OC对象内存的释放 在ARC环境下不可用

id object_dispose(id obj);

//获取OC对象的类 注意 这个返回值和isa指针并不是同一个指针

Class object_getClass(id obj);

//重建对象的类

Class object_setClass(id obj, Class cls);

//判断一个OC对象是否是类或元类(前面说过类实际上也是对象)

BOOL object_isClass(id obj);

//获取OC对象的类名

const char *object_getClassName(id obj);

//通过类名获取“类”对象

Class objc_getClass(const char *name);

//通过类名获取元类对象

Class objc_getMetaClass(const char *name);

//这个方法也是返回类的定义 只是如果是未注册的 会返回nil

Class objc_lookUpClass(const char *name);

//这个方法也是返回类的定义 只是如果是未注册的 会直接杀死进程

Class objc_getRequiredClass(const char *name);

/*

获取所有已经注册的类 会返回已经注册的类的个数

通常使用如下示例代码:

int numClasses;

       Class * classes = NULL;

     

       numClasses = objc_getClassList(NULL, 0);

       if (numClasses > 0) {

           classes = (Class*)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);

       }

*/

int objc_getClassList(Class *buffer, int bufferCount);

//拷贝所有注册过的类列表 参数为输出类的个数

Class *objc_copyClassList(unsigned int *outCount);

//获取Class类名字符串

const char *class_getName(Class cls);

//判断一个Class是否为元类

BOOL class_isMetaClass(Class cls);

//获取一个类的父类

Class class_getSuperclass(Class cls);

//修改一个类的父类

Class class_setSuperclass(Class cls, Class newSuper);

//获取一个类的版本

int class_getVersion(Class cls);

//设置一个类的版本

void class_setVersion(Class cls, int version);

//获取类的内存布局

size_t class_getInstanceSize(Class cls);

上面列举的方法都和类相关,你没看错,通过object_setClass()动态改变对象所属的类,但是需要注意,对象的成员变量并不会受到影响,方法则全部替换为新类的方法。如果你喜欢,你甚至可以运行时动态修改类的父类,这十分酷吧。下面这些方法则与类中的变量有关:


2.变量属性相关操作函数


//获取类额外分配内存的指针

void *object_getIndexedIvars(id obj);

//根据变量名获取实例变量指针

/*

Ivar实质上是一个结构体指针,存放变量信息,如下:

struct objc_ivar {

   //变量名

   char *ivar_name                                          OBJC2_UNAVAILABLE;

   //变量类型

   char *ivar_type                                          OBJC2_UNAVAILABLE;

   int ivar_offset                                          OBJC2_UNAVAILABLE;

#ifdef __LP64__

   int space                                                OBJC2_UNAVAILABLE;

#endif

}  

*/

Ivar class_getInstanceVariable(Class cls, const char *name);

//根据变量名获取类变量指针

Ivar class_getClassVariable(Class cls, const char *name);

//获取所有实例变量指针 outCount输出实例变量个数

Ivar *class_copyIvarList(Class cls, unsigned int *outCount);

//通过变量指针获取具体变量的值

id object_getIvar(id obj, Ivar ivar);

//通过变量指针设置具体变量的值

void object_setIvar(id obj, Ivar ivar, id value);

//通过变量指针设置变量的值 并进行强引用

void object_setIvarWithStrongDefault(id obj, Ivar ivar, id value);

//直接通过变量名修改实例变量的值 会返回变量指针

Ivar object_setInstanceVariable(id obj, const char *name, void *value);

//用法同上

Ivar object_setInstanceVariableWithStrongDefault(id obj, const char *name, void *value);

//通过变量名直接获取示例变量的值

Ivar object_getInstanceVariable(id obj, const char *name, void **outValue);

//获取类变量内存布局

const uint8_t *class_getIvarLayout(Class cls);

//设置类变量内存布局

void class_setIvarLayout(Class cls, const uint8_t *layout);

//获取类变量布局 弱引用

const uint8_t *class_getWeakIvarLayout(Class cls);

//同上

void class_setWeakIvarLayout(Class cls, const uint8_t *layout);

//通过属性名获取属性

/*

属性特指使用@prototype定义的

objc_property_t是一个结构体指针,其描述的是属性的信息,如下:

typedef struct {

   const char *name;           /**属性名 */

   const char *value;          /**属性值 */

} objc_property_attribute_t;


*/

objc_property_t class_getProperty(Class cls, const char *name);

//获取所有属性列表

objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount);

//向类添加一个实例变量

/*

需要注意,已经注册存在的类是不能通过这个方法追加实例变量的

这个方法只能在objc_allocateClassPair函数执行后并且objc_registerClassPair执行前进行调用

即这个函数是用来动态生成类的

*/

BOOL class_addIvar(Class cls, const char *name, size_t size,

                              uint8_t alignment, const char *types);

//向类中添加属性

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);

//获取变量指针对应的变量名

const char *ivar_getName(Ivar v);

//获取编码后的变量类型

const char *ivar_getTypeEncoding(Ivar v);

//获取属性名

const char *property_getName(objc_property_t property);

//获取属性attribute

const char *property_getAttributes(objc_property_t property);

char *property_copyAttributeValue(objc_property_t property, const char *attributeName);

//获取属性attribute列表

objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount);

//进行属性关联 这种方式可以为已经存在的类的实例扩展属性

/*

object:要添加属性的对象

key:关联的键

value:添加的属性的值

policy:添加属性的策略

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {

   //assign

   OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */

   //retain nonatimic

   OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.

                                           *   The association is not made atomically. */

   //copy nonatomic

   OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied.

                                           *   The association is not made atomically. */

   //retain

   OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.

                                           *   The association is made atomically. */

   //copy

   OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.

                                           *   The association is made atomically. */

};

*/

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);

//获取关联属性的值

id objc_getAssociatedObject(id object, const void *key);

//移除一个关联的属性

void objc_removeAssociatedObjects(id object);

3.方法操作相关函数


//通过选择器获取某个类的实例方法

Method class_getInstanceMethod(Class cls, SEL name);

//通过选择器定义某个类的类方法

Method class_getClassMethod(Class cls, SEL name);

//通过选择器获取某个类的方法函数指针

IMP class_getMethodImplementation(Class cls, SEL name);

//同上

IMP class_getMethodImplementation_stret(Class cls, SEL name);

//判断某个类是否可以相应选择器

BOOL class_respondsToSelector(Class cls, SEL sel);

//获取某个类的实例方法列表

Method *class_copyMethodList(Class cls, unsigned int *outCount);

//为某个类动态添加一个实例方法

/*

cls:添加方法的类

SEL:添加的方法选择器

IMP:方法实现

types:参数类型字符串

*/

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);

//替换一个方法的实现

/*

cls:类

SEL:要替换实现的选择器

IMP:实现

types:参数类型字符串

*/

IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types);

//获取函数的选择器

SEL method_getName(Method m);

//获取函数的实现

IMP method_getImplementation(Method m);

//获取函数的参数类型

const char *method_getTypeEncoding(Method m);

//获取函数的参数个数

unsigned int method_getNumberOfArguments(Method m);

//获取函数的返回值类型

char *method_copyReturnType(Method m);

//拷贝参数类型列表

char *method_copyArgumentType(Method m, unsigned int index);

//获取返回值类型

void method_getReturnType(Method m, char *dst, size_t dst_len) ;

//获取参数类型

void method_getArgumentType(Method m, unsigned int index, char *dst, size_t dst_len);

//获取函数描述信息

/*

objc_method_description结构体描述函数信息 如下:

struct objc_method_description {

   //函数名

SEL name;               /**< The name of the method */

   //参数类型

char *types;            /**< The types of the method arguments */

};


*/

struct objc_method_description *method_getDescription(Method m);

//修改某个函数的实现

IMP method_setImplementation(Method m, IMP imp);

//交换两个函数的实现

void method_exchangeImplementations(Method m1, Method m2);

//获取选择器名称

const char *sel_getName(SEL sel);

//通过名称获取选择器

SEL sel_getUid(const char *str);

//注册一个选择器

SEL sel_registerName(const char *str);

//判断两个选择器是否相等

BOOL sel_isEqual(SEL lhs, SEL rhs);

//将block作为IMP的实现

IMP imp_implementationWithBlock(id block);

//获取IMP的实现block

id imp_getBlock(IMP anImp);

//删除IMP的block实现

BOOL imp_removeBlock(IMP anImp);

上面列举的函数中很多都用到参数类型的指定,types需要设置为C风格的字符数组,即C字符串,其中第1个字符表示返回值类型,其余字符依次表示参数类型,参数类型与字符的映射表如下:

image.png




目录
相关文章
|
7月前
|
编译器 Swift iOS开发
44 Swift和Objective-C的运行时简介
Swift和Objective-C的运行时简介
45 0
|
iOS开发 开发者
动态的Objective-C——关于消息机制与运行时的探讨(四)
动态的Objective-C——关于消息机制与运行时的探讨
119 0
|
iOS开发 开发者
动态的Objective-C——关于消息机制与运行时的探讨(二)
动态的Objective-C——关于消息机制与运行时的探讨
157 0
动态的Objective-C——关于消息机制与运行时的探讨(二)
|
缓存 自然语言处理 IDE
动态的Objective-C——关于消息机制与运行时的探讨(一)
动态的Objective-C——关于消息机制与运行时的探讨
151 0
动态的Objective-C——关于消息机制与运行时的探讨(一)
|
测试技术 C语言 iOS开发
|
存储 C语言 iOS开发
(转载)Objective-C总Runtime的那点事儿(一)消息机制
原文地址:http://www.cocoachina.com/ios/20141018/9960.html 找工作,Objective-C中的Runtime是经常被问到的一个问题,几乎是面试大公司必问的一个问题。
969 0
|
iOS开发 编译器 C语言
objective-c下的消息机制
l   消息机制:   Objective-c下调用函数并不是采用的想C语言中的函数调用机制而是使用的一种消息传递的机制。下文将讨论这两种协议之间的区别。   传统的函数调用机制是在程序编译的阶段就已经将子函数的引用地址编译进了执行代码,所以当编译完成之后,函数名指向的就是函数的入口地址,而调用函数将直接导向至函数的入口地址,从而直接开始执行函数。   而Objective-c采用的
963 0
|
Android开发 iOS开发
利用Objective-C运行时hook函数的三种方法
方法一,hook已有公开头文件的类: 首先写一个Utility函数: #import void exchangeMethod(Class aClass, SEL oldSEL, SEL newSEL) { Method oldMethod ...
1199 0