OC和JS互相调用小框架

278 阅读3分钟

鄙人不才献上一份UIWebView和JS交互的桥接框架 不多说直接上货吧!!!

献上框架.m文件的内部实现和代码注释

导入<objc/runtime.h>

#import "UIWebView+Bridge.h"
#import <objc/runtime.h>


#define kBridgeScheme                     @"bridge"
#define kUIWebViewBridgeRegisteredMethods @"kUIWebViewBridgeRegisteredMethods"

@interface NSString (Params)

- (NSDictionary *)params;

@end

@implementation NSString (Params)

- (NSDictionary *)params {
    NSArray *params = [self componentsSeparatedByString:@"&"];
    
    NSMutableDictionary *paramsDict = [NSMutableDictionary dictionary];
    for (NSString *query in params) {
        NSArray *paramComp = [query componentsSeparatedByString:@"="];
        if(paramComp.count > 1) {
            NSString *val = [paramComp[1] stringByRemovingPercentEncoding];
            NSData* data = [val dataUsingEncoding:NSUTF8StringEncoding];
            if(data) {
                NSDictionary *valDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
                [paramsDict setObject:valDict forKey:paramComp[0]];
            }
            
        }
    }
    return paramsDict;
}

@end

@interface UIWebView ()

@property (nonatomic,strong) NSMutableDictionary    *registeredMethods;

@end

@implementation UIWebView (Bridge)

#pragma mark - 公共方法 -
/**
 *  是否是一个JS函数
 *
 *  @param jsFunctionName js函数名
 *
 *  @return 是函数返回YES
 */
- (BOOL)isJSFunction:(NSString *)jsFunctionName {
    NSString *result = [self stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"%@&&typeof(%@)==\"function\"",jsFunctionName,jsFunctionName]];
    if([result isEqualToString:@"true"]) {
        return YES;
    }
    return NO;
}

/**
 *  是否存在js变量
 *
 *  @param jsVariableName 变量名
 *
 *  @return 是否存在
 */
- (BOOL)isJSVariable:(NSString *)jsVariableName {
    NSString *result = [self stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"typeof(%@) == \"undefined\"",jsVariableName]];
    if([result isEqualToString:@"false"]) {
        return YES;
    }
    return NO;
}

/**
 *  向UIWebview注册名称空间
 *
 *  @param nameSpace 名称空间
 */
- (void)createJSNameSpace:(NSString *)nameSpace {
    
    /*
    // 对应的js代码
    var Namespace = new Object();
    Namespace.register = function(path){
        var arr = path.split(".");
        var ns = "";
        for (var i = 0; i < arr.length; i++) {
            if (i > 0) {
                ns += ".";
            }
            ns += arr[i];
            eval("if(typeof(" + ns + ") == 'undefined') " + ns + " = new Object();");
        }
    }
    */
    if(![self isJSVariable:@"Namespace"]) {
        [self stringByEvaluatingJavaScriptFromString:@"var Namespace = new Object();"];
    }
    if(![self isJSFunction:@"Namespace.register"]) {
        
        [self stringByEvaluatingJavaScriptFromString:
         @"var Namespace = new Object();Namespace.register = function(path){var arr = path.split(\".\");var ns = \"\";for (var i = 0; i < arr.length; i++) {if (i > 0) {ns += \".\";}ns += arr[i];eval(\"if(typeof(\" + ns + \") == 'undefined') \" + ns + \" = new Object();\");}}"];
    }
    
    if(nameSpace != nil) {
        [self stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"Namespace.register(\"%@\")",nameSpace]];
    }
}


#pragma mark - 注册方法缓存 -
- (NSMutableDictionary *)registeredMethods {
    NSMutableDictionary *registeredMethods = objc_getAssociatedObject(self, kUIWebViewBridgeRegisteredMethods);
    if(registeredMethods == nil) {
        registeredMethods = [NSMutableDictionary dictionary];
        [self setRegisteredMethods:registeredMethods];
    }
    return registeredMethods;
}

- (void)setRegisteredMethods:(NSMutableDictionary *)registeredMethods {
    objc_setAssociatedObject(self, kUIWebViewBridgeRegisteredMethods, registeredMethods, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

#pragma mark - 清空缓存注册方法 -
- (void)clear {
    [[self registeredMethods] removeAllObjects];
}

#pragma mark - 注册方法 -
- (void)registerJSMethod:(NSString *)jsMethod target:(id)target method:(SEL)method {
    //缓存方法
    NSMethodSignature *signature = [[target class] instanceMethodSignatureForSelector:method];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    
    invocation.target = target;
    invocation.selector = method;
    
    NSMutableDictionary *registeredMethods = self.registeredMethods;
    [registeredMethods setObject:invocation forKey:jsMethod];
    
    //设置回调函数缓存
    if(![self isJSVariable:@"__CallbackCaches__"]) {
        [self stringByEvaluatingJavaScriptFromString:@"var __CallbackCaches__ = new Object();"];
    }
    
    //运行时植入js代码,使js拥有js回调能力
    if(![self isJSFunction:jsMethod]) {
        NSString *js = [NSString stringWithFormat:@"%@ = function(params,callback){__CallbackCaches__[\"%@\"]=callback;window.location.href=\"%@://%@?params=\"+encodeURIComponent(JSON.stringify(params));}",jsMethod,jsMethod,kBridgeScheme,jsMethod];
        [self stringByEvaluatingJavaScriptFromString:js];
    }
    
}

#pragma mark - 注册url进行调用OC和js -

- (BOOL)dispatchURL:(NSURL *)url {
    if([url.scheme isEqualToString:kBridgeScheme]) {
        NSString *jsMethod = url.host;
        NSDictionary *invokeParams = [url.query params][@"params"];
        NSInvocation *invocation = [[self registeredMethods] objectForKey:jsMethod];
        [invocation setArgument:&invokeParams atIndex:2];
        __autoreleasing NSDictionary *result = nil;
        [invocation invoke];
        [invocation getReturnValue:&result];
    
        NSData *jsonData = [NSJSONSerialization dataWithJSONObject:result options:NSJSONWritingPrettyPrinted error:nil];
        if(jsonData) {
            NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
            NSString *js = [NSString stringWithFormat:@"__CallbackCaches__[\"%@\"](JSON.parse(decodeURIComponent(\"%@\")))",jsMethod,[jsonString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]];
            [self stringByEvaluatingJavaScriptFromString:js];
        }
        [self stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"__CallbackMap__[\"%@\"]=NULL;",jsMethod]];
        return YES;
    }
    return NO;
}

@end

.h文件

#import <UIKit/UIKit.h>

/**
 *  标准方式桥接js
 */
@interface UIWebView (Bridge)

#pragma mark - JS工具方法 -
/**
 *  是否是一个JS函数
 *
 *  @param jsFunctionName js函数名
 *
 *  @return 是函数返回YES
 */
- (BOOL)isJSFunction:(NSString *)jsFunctionName;

/**
 *  是否存在js变量
 *
 *  @param jsVariableName 变量名
 *
 *  @return 是否存在
 */
- (BOOL)isJSVariable:(NSString *)jsVariableName;

/**
 *  向UIWebview注册名称空间
 *
 *  @param nameSpace 名称空间
 */
- (void)createJSNameSpace:(NSString *)nameSpace;

#pragma mark - 操作方法 -
 
/**
 *  清空已注册的方法
 */
- (void)clear;

/**
 *  js方法和oc方法之间桥接注册
 *
 *  @param jsMethod js方法名称
 *  @param target   oc消息发送者
 *  @param method   oc消息
 */
- (void)registerJSMethod:(NSString *)jsMethod target:(id)target method:(SEL)method;

/**
 *  url转发,用于js回调
 *
 *  @param url url跳转回调
 *
 *  @return 返回YES表示跳转成功,并且桥接调用OC代码,返回NO表示失败
 */
- (BOOL)dispatchURL:(NSURL *)url;

@end

那么我们如何使用呢!我给大家演示一下使用步骤吧

  • 第一步拦截跳转
#pragma mark -  UIWebViewDelegate

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    // 1. 框架使用第一步 拦截跳转
    return [self.webview dispatchURL:request.URL] == NO;
}
  • 第二步 绑定js和oc之间的函数,一定要注意方法名必须和h5那边的方法名一样否则无效
- (void)webViewDidFinishLoad:(UIWebView *)webView {
    // 2. 注册 定义JS和OC之间的通讯函数 => 绑定 JS 函数 和 OC 函数
    [self.webview registerJSMethod:@"mapLink" target:self method:@selector(mapLink:)];
}
  • 第三步 绑定的方法的实现 注意事项:返回值如若没有不能反悔nil,要写成返回空字典的形式
#pragma mark --- Action
// params 参数表示JS调用的时候传递给OC对象的
// 返回值是表示 OC对象把数据返回给JS调用
- (NSDictionary *)mapLink:(NSDictionary *)params {
    DEF_DEBUG(@"%@",params);
    self.parameters = params[@"mapObj"];
    if(_locationBlock)
    {
        _locationBlock(params[@"mapObj"]);
    }
    [self.navigationController popViewControllerAnimated:YES];
    return @{};
}