关于OC的类方法和实例方法的获取和操作
一、在了解类方法和实例方法之前先要知道:什么是实例、什么是类对象、什么是元类对象
1、程序运行之后,存在的类就创建了对应的类对象和元来对象(把他们看成一个对象,而不是简单的类;即一个的实例)(对应的类对象和元类对象都是唯一的)
2、当创建一个类的实例的时候,则会有一个指针指向了对应的类对象(isa),如下图所示
3、类对象中存放了对应的实例方法, 元类对象中存放了对应的类方法(静态成员函数)所以要获取对应的实例方法时候可以通过[Student class]的方式; 该方式获取到的是对应的类对象,既然这样子那能否通过[[Student class] class]获取都对应的元类对象呢?答案是不行的,class方法只能获取到本类对象,而无法获取本类对象对应的元类对象。要获取对应的元类对象则需要使用:object_getClass([Student class])的方式来获取
具体的元类跟类还有实例的关系可以看Objective-C高级编程 iOS与OS X多线程和内存管理.pdf
4、3中提到的类对象是如何保存对应的isa还有各自对应的方法方法列表的呢?因为OC是对C的扩展,所以关于保存isa还有对应的方法列表是放在一个结构体里面,具体的结构体如下所示:
typedef struct objc_class *Class;
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;
如上所示:实例对象,类对象和元类对象都是使用objc_class进行存储;各自对象的方法则存储在objc_method_list列表,他们方法的查找流程是:本类查找,如果没找到就去super_class查找;
题外话:其实通过这个结构体我们也知道了为何OC只支持单继承多协议。(只有一个super_class指针,却有一个协议列表)
结构体中还有objc_cache 。程序在方法的查找的时候实际执行的流程是:1、会先在这个cache中查找如果命中直接返回;2、1中没找到再走方法列表的查找流程,如果找到则则更新cache并返回对应的方法。如下是cache的结构体
struct objc_cache { unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE; unsigned int occupied OBJC2_UNAVAILABLE; Method _Nullable buckets[1] OBJC2_UNAVAILABLE; };
5、如之前介绍的,类方法和元类方法在程序开始执行的时候就构建并且唯一。那么能否在程序执行的过程中修改结构体的内容呢?答案明显是可以的,这也是OC是动态语言的强大保证。OC支持程序执行过程中修改对应的类方法和元类方法:
// 添加方法 BOOL class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types) // 替换方法 IMP _Nullable class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types) // 设置方法 IMP _Nonnull method_setImplementation(Method _Nonnull m, IMP _Nonnull imp) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0) // 交换方法 void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0) // 等等还有其他的接口
6、方法操作的例子:
1、IMP _Nullable class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)
@interface Person1 : NSObject - (void) Eat:(int)val; @end @interface Ani : NSObject - (void) To:(int)val; - (void) Do; @end
在Do函数中进行如下操作,则Ani的To函数会被替换成Person1的Eat函数;当接下去在Ani的Do调用[self To:10];时候则会执行Eat函数
// 1、Class的获取 Class cls = [self class]; // 2、这里通过字符串的方式获取sel,也可以通过@selector的方式, 这里需要注意的是@selector只能在在本类中查找 SEL sel = NSSelectorFromString(@"To:"); sel = @selector(To:); IMP imp = class_getMethodImplementation([Person1 class], NSSelectorFromString(@"Eat:")); // types可以自己手写,在知道具体的哪个类和函数时也可以通过函数method_getTypeEncoding获取 // 各个字段的含义可以通过这个链接查看 https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100 const char *types = "v@:@"; types = method_getTypeEncoding(class_getInstanceMethod([Person1 class], NSSelectorFromString(@"Eat:"))); class_replaceMethod(cls, sel, imp, types);
关于这些接口的总结:
1、类对象的获取:[self class]或者[Person1 class] 这两种方式都可以获取到累对象,self只是泛指可以是实例对象,第二个是通过类名
2、元类对象的获取:如上第一张图所示,想道的元类获取方式是[[Person1 class] class] 但是这个方式获取到的依旧是类对象而不是自己希望的元类对象。元类的获取对象是通过:object_getClass([Person class]) 即在类对象的外面再包一层object_getClass来获取
3、SEL的获取:可以通过SSelectorFromString(NSString *aSelectorName) 也可以通过@selector(To:);的方式;但是@selector只能在本类中查找;反之要获取对应的SEL名字则可以通过NSString *NSStringFromSelector(SEL aSelector)
4、Method 的获取:如果是要获取类方法(即实例方法)则使用 class_getInstanceMethod([Person1 class], SEL _Nonnull name); 如果要获取元类方法(静态类方法)则使用class_getClassMethod([Person1 class], SEL _Nonnull name);
5、IMP的获取:通过Method来获取method_getImplementation(Method _Nonnull m); 另一种则是通过类方法获取class_getMethodImplementation(Class _Nullable cls, SEL _Nonnull name) 在借口说明中也提到了class_getMethodImplementation可能会比method_getImplementation速度更快;所以在获取IMP的时候可以尽量使用class_getMethodImplementation (具体的原因未去研究)
6、方法的替换:1、通过method_setImplementation(Method _Nonnull m, IMP _Nonnull imp) 直接设置方法的实现,另一种则是通过class_replaceMethod(Class cls, SEL name, IMP imp, const char * types) 该方法的操作步骤是如果要替换的方法不存在则使用class_addMethod 将对应的方法添加进去,如果方法存在则使用method_setImplementation直接将方法设置进去
7、方法的添加:class_addMethod(Class cls, SEL name, IMP imp, const char * types) 使用该类方法需要注意的是:该方法只操作本类,如果本类中已经有对应的方法则放弃添加并返回false,如果本类中没有对应的方法则添加成功(不会去管父类是否有该方法,如果有就是继承关系中的overwrite而已)
8、方法的交换:method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) 互换两个方法的实现;
注意:在调用SEL的时候最好需要先判断下对应的类是否实现了该SEL即:class_respondsToSelector(Class _Nullable cls, SEL _Nonnull sel) 返回true 再执行performSelector
未完待续
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!