关于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

 

未完待续

posted @   LCAC  阅读(805)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
点击右上角即可分享
微信分享提示