美文网首页
常见面试题

常见面试题

作者: 浪的出名 | 来源:发表于2020-10-26 16:55 被阅读0次

super的本质

  • 新建一个Student类继承至Person,Student实现一个方法调用父类的方法。
  Student *s = [[Student alloc] init];
  [s test1];
  
  @interface Student : Person
  - (void)test1;
  @end
  - (void)test1
  {
     [super test];
  }
  • 通过xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc命令得到c++代码
static void _I_Student_test1(Student * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("test"));
    //去掉一些强转
    objc_msgSendSuper((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("test"));
}
  • 需要注意的是,通过断点看汇编执行流程,发现执行的并不是objc_msgSendSuper,而是objc_msgSendSuper2


    image
  • 我们通过clang命令得到的c++代码只能作为参考,大部分都是正确的。

  • 通过查看objc4源码发现objc_msgSendSuper2传入的2个参数应该是

    • struct objc_super2 {
      id receiver; //receiver是消息接收者
      Class current_class; //current_class是receiver的Class对象
      }结构体
    • SEL


      image
  • super调用的本质就是直接从父类的类对象或元类对象去找方法的实现,消息接收者还是自身。

Method Swizzling的坑与应用

  • Method Swizzling的意思就是方法交换,主要作用是在运行时交换方法的实现(IMP)


    Method Swizzling交换原理.png
  • Method Swizzling涉及的方法
  • class_getInstanceMethod:获取实例方法
  • class_getClassMethod:获取类方法
  • method_getImplementation:获取一个方法的实现
  • method_setImplementation:设置一个方法的实现
  • method_getTypeEncoding:获取方法实现的编码类型
  • class_addMethod:添加方法实现
  • class_replaceMethod:如果方法不存在,就添加一个,如果存在就将IMP替换
// 官方文档给出的描述
This function behaves in two different ways:
If the method identified by name does not yet exist, it is added as if class_addMethod were called. The type encoding specified by types is used as given.
If the method identified by name does exist, its IMP is replaced as if method_setImplementation were called. The type encoding specified by types is ignored.
此函数的行为有两种不同的方式:
如果通过名称标识的方法尚不存在,则将其添加为如同调用class_addMethod一样。 给定使用由类型指定的类型编码。
如果确实存在按名称标识的方法,则将其IMP替换,就像调用method_setImplementation一样。 由类型指定的类型编码将被忽略。
  • method_exchangeImplementations:交换两个方法的实现
IMP imp1 = method_getImplementation(m1);
IMP imp2 = method_getImplementation(m2);
method_setImplementation(m1, imp2);
method_setImplementation(m2, imp1);

method-swizzling使用过程中的一次性问题

  • 一般情况下我们会在load方法里面实现方法交换,但是我们可能会主动调用load方法之后方法又会被交换回来了,这样就可能会导致方法没有交换。
  • 这种情况我们的解决方案一般是将它设计成单列
+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
            交换方法
    });
}

自己没有实现,父类实现的问题

  • 父类实现了instancetypeMeth1,子类没有实现,在子类的分类中交换方法
@interface Person : NSObject
- (void)instancetypeMeth1;
@end
@implementation Person
- (void)instancetypeMeth1
{
    NSLog(@"%s",__func__);
}
@end

@interface Student (XQ)

@end
@implementation Student (XQ)
+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [XQRuntimeTool xq_methodSwizzlingWithClass:self oriSEL:@selector(instancetypeMeth1) swizzledSEL:@selector(stu_instancetypeMeth1)];
    });
}

- (void)stu_instancetypeMeth1
{
    [self stu_instancetypeMeth1];
    NSLog(@"%s",__func__);
}
@end
  • 在viewController里面调用Person实例的instancetypeMeth1方法,会发生崩溃,原因是因为[self stu_instancetypeMeth1];会调用Personstu_instancetypeMeth1方法,而Person并没有实现该方法
    Student *s = [Student new];
    [s instancetypeMeth1];
    Person *p = [Person new];
    [p instancetypeMeth1];
image.png
  • 解决IMP找不到的问题,就是在交换方法的时候,尝试给类添加要交换的方法,如果添加成功,即类中没有这个方法,则通过class_replaceMethod进行替换,其内部会调用class_addMethod进行添加。如果添加不成功,即类中有这个方法,则通过method_exchangeImplementations进行交换
+ (void)xq_betterMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
    
    if (!cls) NSLog(@"传入的交换类不能为空");
  
    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
   // 尝试添加你要交换的方法
    BOOL success = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod));

    if (success) {// 自己没有 - 交换 - 没有父类进行处理 (重写一个)
        class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
    }else{ // 自己有
        method_exchangeImplementations(oriMethod, swiMethod);
    }
}
  • 在上面的基础上,如果父类和子类都没有实现原方法,再调用的时候就会出现递归死循环,原因是oriMethod是空,交换一个空的实现是没有效果的
- (void)stu_instancetypeMeth1
{
    [self stu_instancetypeMeth1];//在这里相当于自己调用自己
    NSLog(@"%s",__func__);
}
  • 因此,我们再交换方法的时候,还要加上对原IMP的判断,最终的代码如下
+ (void)xq_bestMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
    
    if (!cls) NSLog(@"传入的交换类不能为空");
    
    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
    
    if (!oriMethod) { // 避免动作没有意义
        // 在oriMethod为nil时,替换后将swizzledSEL复制一个不做任何事的空实现,代码如下:
        class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
        method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){
            NSLog(@"来了一个空的 imp");
        }));
    }
    
    // 一般交换方法: 交换自己有的方法 -- 走下面 因为自己有意味添加方法失败
    // 交换自己没有实现的方法:
    //   首先第一步:会先尝试给自己添加要交换的方法 :instanceMethod1 (SEL) -> swiMethod(IMP)
    //   然后再将父类的IMP给swizzle  instanceMethod1(imp) -> swizzledSEL
    //oriSEL:instanceMethod1

    BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
    
    if (didAddMethod) {
        class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
    }else{
        method_exchangeImplementations(oriMethod, swiMethod);
    }
}

相关文章

网友评论

      本文标题:常见面试题

      本文链接:https://www.haomeiwen.com/subject/xgzamktx.html