美文网首页
iOS Method Swizzling 的一个实际应用

iOS Method Swizzling 的一个实际应用

作者: 舌尖上的大胖 | 来源:发表于2017-05-23 06:05 被阅读0次

    本文 Method Swizzling 部分参考了 《iOS黑魔法-Method Swizzling》

    一、问题

    最近在维护公司一个久远的项目时,发现当时使用了 UIWebView 展示 HTML 页面,为了解决 JavaScript 中 alert 和 confirm 样式不能自定义的问题,所以通过实现以下方法,

    @interface UIWebView (JSConfirmAlert)
    
    - (void)webView:(UIWebView *)sender runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(id)frame;
    - (BOOL)webView:(UIWebView *)sender runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(id)frame;
    
    @end
    
    

    来拦截 alert 和 confirm ,并通过 UIAlertView 重新实现。

    但是原来的代码在实现上有些问题,这部分功能又是做成了 framework 集成到工程里面的,而且由于项目几经易手,framework 部分的源码已经没有了。由于原来是通过给 UIWebView 写 Category 来实现的功能,所以即便不引用 .h 文件,只要集成了 framework,这部分代码就会生效。(这一点是刚发现的,原来一直以为要引用了 .h 才会生效。)

    二、解决方案

    无奈之下,就考虑用 Method Swizzling 来改写之前的方法了。

    // UIWebView+SwizzlingAlertAndConfirm.h
    
    #import <Foundation/Foundation.h>
    
    @interface UIWebView (SwizzlingAlertAndConfirm)
    
    @end
    
    
    // UIWebView+SwizzlingAlertAndConfirm.m
    
    #import <UIKit/UIKit.h>
    #import <objc/runtime.h>
    #import "UIWebView+SwizzlingAlertAndConfirm.h"
    
    @implementation UIWebView (SwizzlingAlertAndConfirm)
    
    + (void)exchangeMethod:(SEL)fromSelector toMethod:(SEL)toSelector {
    
        // 通过class_getInstanceMethod()函数从当前对象中的method list获取method结构体,如果是类方法就使用class_getClassMethod()函数获取。
        Method fromMethod = class_getInstanceMethod([self class], fromSelector);
        Method toMethod = class_getInstanceMethod([self class], toSelector);
    
        /**
         *  我们在这里使用class_addMethod()函数对Method Swizzling做了一层验证,如果self没有实现被交换的方法,会导致失败。
         *  而且self没有交换的方法实现,但是父类有这个方法,这样就会调用父类的方法,结果就不是我们想要的结果了。
         *  所以我们在这里通过class_addMethod()的验证,如果self实现了这个方法,class_addMethod()函数将会返回NO,我们就可以对其进行交换了。
         */
        if (!class_addMethod([self class], fromSelector, method_getImplementation(toMethod), method_getTypeEncoding(toMethod))) {
            method_exchangeImplementations(fromMethod, toMethod);
        }
    
    }
    
    
    + (void)load {
        [super load];
    
        [self exchangeMethod:@selector(webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:)
                    toMethod:@selector(swizzlingWebView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:)];
    
        [self exchangeMethod:@selector(webView:runJavaScriptConfirmPanelWithMessage:initiatedByFrame:)
                    toMethod:@selector(swizzlingWebView:runJavaScriptConfirmPanelWithMessage:initiatedByFrame:)];
    
        [self exchangeMethod:@selector(alertView:clickedButtonAtIndex:)
                    toMethod:@selector(swizzlingAlertView:clickedButtonAtIndex:)];
    
    }
    
    
    
    - (void)swizzlingWebView:(UIWebView *)sender runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(id)frame {
        UIAlertView* customAlert = [[UIAlertView alloc] initWithTitle:@"助手提示" message:message delegate:nil cancelButtonTitle:@"确定bbb" otherButtonTitles:nil];
        [customAlert show];
    }
    
    static BOOL diagStat = NO;
    static NSInteger bIdx = -1;
    
    - (BOOL)swizzlingWebView:(UIWebView *)sender runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(id)frame {
        UIAlertView *confirmDiag = [[UIAlertView alloc] initWithTitle:@"助手提示"
                                                              message:message
                                                             delegate:self
                                                    cancelButtonTitle:@"取消13"
                                                    otherButtonTitles:@"确定13", nil];
    
        [confirmDiag show];
        bIdx = -1;
    
        while (bIdx==-1) {
            //[NSThread sleepForTimeInterval:0.2];
            [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1f]];
        }
        if (bIdx == 0){//取消;
            diagStat = NO;
        }
        else if (bIdx == 1) {//确定;
            diagStat = YES;
        }
        return diagStat;
    }
    
    - (void)swizzlingAlertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
        bIdx = buttonIndex;
    }
    
    
    @end
    
    

    工程引用这两个文件后,问题解决。

    三、讨论
    1、Method Swizzling 的封装

    这里对 Method Swizzling 做了个简单的封装,不过只是为了写着方便随便整了下。真正在项目中我们肯定会在很多地方用到 Method Swizzling,而且在使用这个特性时有很多需要注意的地方。我们可以将 Method Swizzling 封装起来,也可以使用一些比较成熟的第三方。在这里我推荐Github上星最多的一个第三方——JRSwizzle

    2、改进

    都折腾完之后才发现,就这个项目本身的问题而言,其实用不到 Method Swizzling,只要写个 UIWebView 的子类,在子类中重新实现这几个方法,然后直接使用子类就好了。当时没想起来。不过使用子类化的方法,还是需要修改原来代码中对 UIWebView 引用的那部分代码。而使用 Method Swizzling 还是更酷一些,原来的代码不用做任何修改,只要在工程中引入这两个文件就可以了。

    相关文章

      网友评论

          本文标题:iOS Method Swizzling 的一个实际应用

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