isa-Swizzling学习记录

520 阅读2分钟

isa-Swizzling

iOS中以前只知道isa-Swizzling是KVO的实现原理,但是没有在业务中实际实践过,这次有个弹窗的需求,正好可以拿来试试.

如上图,在弹窗展开show的时候,用户是可以点击其他的按钮如搜索,会员分层等这些不在弹出的view上的按钮.

因为是把弹窗加到的keywindow上,这样在点搜索的时候,没办法判断弹窗是否在展示,就不会让弹窗收回,造成view错乱的现象.

初步想的有2种解决方法,

  • 利用View的手势UIGestureRecognizer.
  • 利用view的hitTest触摸事件响应链.

####1.手势UIGestureRecognizer.

给ViewController的view添加TapGestureRecognizer手势.

因为页面的控件都是添加在view上的,所以可以通过tap手势的方法,在delegate里面监听点击事件,如果点击在view上就通过通知移除menuView;

但是有个问题是当点击其他按钮的时候,delegate是监听不到事件的.

####2.view的hitTest

事件响应链,当点击不在特定的范围内或者不在特定的子控件上的时候,发送通知.

使用UIView的category扩展,替换当前viewcontrollerview,在扩展中动态添加一个UIView的子类,使用isa-swizzling让替换view的isa指向,令view成为custom_view的实例对象,custom_view继承自UIView.

-(void)observeCurrentWindowisShowMenuView {
    
    NSString *className = [NSString stringWithFormat:@"custom_%@", self.class];
    Class kclass = objc_getClass([className UTF8String]);
    if (!kclass) {
        kclass = objc_allocateClassPair(self.class, [className UTF8String], 0);
        objc_registerClassPair(kclass);
    }
  
  	//custom_view的hitTest方法的指向
    SEL setterSelector = NSSelectorFromString(@"hitTest:withEvent:");
    Method setterMethod = class_getInstanceMethod(self.class, setterSelector);
    const char *types = method_getTypeEncoding(setterMethod);
  	//custom添加方法,但是方法的imp指向custom_HitTest
    class_addMethod(kclass, setterSelector, (IMP)custom_HitTest, types);
    object_setClass(self, kclass);
}

static UIView * custom_HitTest(id self, SEL _cmd, CGPoint point, UIEvent *event) {
    
    struct objc_super superclass = {
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self))
    };

  //调用父类的hitTest
    UIView * (*objc_msgSendSuperCasted)(const void *, SEL, CGPoint, UIEvent *) = (void *)objc_msgSendSuper;
    UIView *view = objc_msgSendSuperCasted(&superclass, _cmd, point, event);
    
  //这里做发通知的操作
    NSLog(@"custom_HitTest -- %@", view.class);
    const char *name = "menu_UIButton";
    if (![view isKindOfClass: objc_getClass(name)]) {
        [SPDropMenuTool removeDropMenuFromKeyWindow];
    }
    
    return view;
}

对Button的扩展:

-(void)menuFromSubClass {
    NSString *className = [NSString stringWithFormat:@"menu_%@", self.class];
    Class kClass = objc_getClass([className UTF8String]);
    if (!kClass) {
        kClass = objc_allocateClassPair(self.class, [className UTF8String], 0);
        objc_registerClassPair(kClass);
    }
    object_setClass(self, kClass);
}

这样就通过isa-Swizzing替换了view,没有改动原有项目结构,就算有其他业务需求的时候view不是继承自UIView,也可以通过custom创建对应的子类.