JavaScriptCore 框架详细解析(四) —— 工程实践之H5调用原生的jsbridge实践(一)

版本记录

版本号 时间
V1.0 2018.07.10

前言

JavaScriptCore是用来评估应用程序中的JavaScript程序,并支持应用程序的JavaScript脚本编写。接下来这几篇我们就详细的解析一下JavaScriptCore框架的使用情况。感兴趣的可以看上面写的那篇。
1. JavaScriptCore 框架详细解析(一) —— 基本概要
2. JavaScriptCore 框架详细解析(二) —— JS与OC通信
3. JavaScriptCore 框架详细解析(三) —— 内存管理与线程安全

应用场景

在做项目的时候,有时候全部用H5做不是很好,如果用H5做有很多交互不友好的地方,这个时候我们就需要用原生的去完成。所以,我们就需要监听H5中的事件(比如说点击了H5中的按钮),去完成下面的操作,其实就相当于H5调用了你的原生。

H5调用原生的方法可以在原生中的进行其他操作,这就自然而然的相当于将事件处理权由H5过渡到了原生。其中我们做直播的一个应用场景,就是在直播间中半屏H5活动页面,单击H5中的按钮,然后Push到一个全屏的页面,这个Push操作就需要在点击H5页面后去做,其实就是相当于H5调用了我们的原生完成了后面的操作。


实现流程

1. 定义函数

移动端和前端需要一起定义一个函数,函数名称相同,iOS和Android还有前端都是用这个函数,其实就是以后H5调用原生移动端的一个入口。比如我们定义的就是下面这个函数(或称方法)。

- (void)onJsCallback:(NSString *)json

传递的参数是json格式的,H5调用原生的时候就会将参数以json格式传递给移动端,移动端解析出来,获取到数据,对数据进行处理,把处理权掌握在自己手里,就达到了前端H5和移动端的通信。

2. 封装JS引擎类

其实这一步骤可以不做,可以直接在Webview类里面做处理,但是我这里单独提出来,其实是有原因的,如果都放在webview里面,一是会显的很乱;二是耦合太严重逻辑不清晰,所以这里封装了一个JS引擎单例类。

1. JJJSEngine.h
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>

@protocol JJJSExport <JSExport>

//H5调用native
- (void)onJsCallback:(NSString *)json;

@end

@interface JJJSEngine : NSObject <JJJSExport>

@property (nonatomic, weak) UIWebView *webview;
@property (nonatomic, weak) JSContext *jsContext;
@property (nonatomic, strong) NSDictionary *moreDict;

+ (instancetype)shared;

//清理
- (void)cleanUp;

//native向js发送消息
- (void)sendJSMessage:(NSString *)content;

@end
2. JJJSEngine.m
#import "JJJSEngine.h"
#import "JSONHelp.h"
#import <objc/message.h>


@interface JJJSEngine()

@property (nonatomic, strong) dispatch_queue_t jsQueue;
@property (nonatomic, strong) NSMutableDictionary *jsCallbacksDictM;

@end

@implementation JJJSEngine

#pragma mark -  Override Base Function

- (instancetype)init
{
    self = [super init];
    if (self) {
        _jsQueue = dispatch_queue_create("com.maobotv.jsbridge", DISPATCH_QUEUE_SERIAL);
        _jsCallbacksDictM = [NSMutableDictionary dictionaryWithCapacity:5];
    }
    return self;
}

#pragma mark -  Object Private Function

//发送JS消息
- (void)sendJSMessageName:(NSString *)callback response:(NSDictionary *)response
{
    if ([callback isNotEmpty] == NO || self.webview == nil) {
        return;
    }
    
    NSString *responseStr = [JSONHelp object2NSString:response];
    responseStr = responseStr ? : @"";
    responseStr = [responseStr stringByReplacingOccurrencesOfString:@"'" withString:@"\'"];
    NSString *jsCallStr = [NSString stringWithFormat: @"%@(\'%@\');", callback, responseStr];
    dispatch_async(dispatch_get_main_queue(), ^{
        @try {
            NSString *result = [self.webview stringByEvaluatingJavaScriptFromString:jsCallStr];
            if (result == nil) {
                DDLogError(@"evaluate script fail :%@",jsCallStr);
            }
        } @catch (NSException *exception) {
            DDLogError(@"webview evaluate error :%@",[exception reason]);
        }
    });
}

#pragma mark -  Object Public Function

- (void)cleanUp
{
    [self.jsCallbacksDictM removeAllObjects];
}

//native向js发送消息
- (void)sendJSMessage:(NSString *)content
{
    //字符串转对象
    NSDictionary *param = [JSONHelp string2NSObject:content];
    //这个JJJSUtil是工具类,用于一些常用工具处理
    NSDictionary *response = [JJJSUtil jsResponseWithRet:0 errorMsg:nil param:param];
    [self sendJSMessageName:self.jsCallbacksDictM[@"自定义的字符串key"] response:response];
}

#pragma mark -  Class Public Function

+ (instancetype)shared
{
    static JJJSEngine *object;
    static dispatch_once_t predicate;
    dispatch_once(&predicate, ^{
        object = [[self alloc] init];
    });
    return object;
}

#pragma mark -  JJJSExport

//H5调用native
- (void)onJsCallback:(NSString *)json
{
    IMP_WSELF()
    dispatch_async(_jsQueue, ^{
        IMP_SSELF()
        if (sself == nil) {
            return;
        }
        id jsonObj = [JSONHelp string2NSObject:json];
        if ([jsonObj isKindOfClass: [NSDictionary class]]) {
            NSDictionary *dict = (NSDictionary *)jsonObj;
            NSString *actionTypeStr = _To_Str(dict[@"action"]);
            NSDictionary *dataDict = _To_Dict(dict[@"data"]);
            NSString *contentURL = _To_Str(dataDict[@"url"]);
            
            dispatch_async(dispatch_get_main_queue(), ^{
                //全屏的H5
                if ([actionTypeStr isEqualToString:@"toFullPage"]) {
                    JJWebViewVC *webVC = [[JJWebViewVC alloc] init];
                    extern BOOL isTestCondition;
                    webVC.urlStr = contentURL;
                    self.moreDict = _To_Dict(dataDict[@"more"]);
                    webVC.isOpenGuardH5 = YES;
                    webVC.feed = self.feed;
                    webVC.view.backgroundColor = [UIColor colorWithRed:35/255.0 green:27/255.0 blue:54/255.0 alpha:1.0];
                    [JJCurrentNaviController pushViewController:webVC animated:YES];
                }
                //返回上一页
                else if([actionTypeStr isEqualToString:@"wvBack"]){
                    if ([self.webview canGoBack]) {
                        [self.webview goBack];
                    }
                    else {
                        [JJCurrentNaviController popViewControllerAnimated:YES];
                    }
                }
                //展示迷你卡
                else if([actionTypeStr isEqualToString:@"showCard"]){
                    NSString *uid = _To_Str(dataDict[@"uid"]);
                    [[NSNotificationCenter defaultCenter] postNotificationName:kNotification_show_user_info_card object:uid];
                }
            });
            
        }
    });
}

@end

上面的类JJJSUtil是一个工具类,用于存放一些基本的逻辑处理,比如

//判断url的前缀是否相等(?之前)
+ (BOOL)isH5URLPrefixEqual:(NSString *)url1 url2:(NSString *)url2;
//判断url的前缀是否相等(?之前)
+ (BOOL)isH5URLPrefixEqual:(NSString *)url1 url2:(NSString *)url2
{
    if (url1 == nil || url2 == nil) {
        return NO;
    }
    
    NSRange range1 = [url1 rangeOfString:@"?" options:NSBackwardsSearch];
    if (range1.location != NSNotFound) {
        url1 = [url1 substringToIndex:range1.location];
    }
    
    NSRange range2 = [url2 rangeOfString:@"?" options:NSBackwardsSearch];
    if (range2.location != NSNotFound) {
        url2 = [url2 substringToIndex:range2.location];
    }
    
    return [url1 isEqualToString:url2];
}

3. webview类中的处理

前面我们已经写好了jsbridge引擎,下面我们要在webview中做一些事情,这样才能将我们定义的引擎和webview中的H5连接起来,让路“通”起来。

#import "JJJSEngine.h"

@property (nonatomic, strong) JJJSEngine *jsEngine;

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    [self createWebView];

    [JJJSEngine shared].webview = self.webView;
    self.jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    self.jsContext[@"JSCallInterface"] = [JJJSEngine shared];
}

上面self.jsContext[@"JSCallInterface"] = [JJJSEngine shared];JSCallInterface是告诉jsbridge要找那个对象去处理,这个字符串也是和前段定义好的了。

下面接着在代理方法中继续处理

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    _isLoadingFinished = YES;
    [self.loadingView removeLoadingView];
    self.loadingView = nil;
    self.jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    self.jsContext[@"JSCallInterface"] = [JJJSEngine shared];
    [JJJSEngine shared].jsContext = self.jsContext;
    [JJJSEngine shared].webview = self.webView;

    // 增加jsBridge异常的处理
    self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
        context.exception = exceptionValue;
        CPLog(@"jsbridge - 异常信息:%@", exceptionValue);
    };
}

最后别忘记在dealloc中进行销毁处理

- (void)dealloc
{
    [[JJJSEngine shared] cleanUp];
    [JJJSEngine shared].webview = nil;
    
    [self.webView stopLoading];
    self.webView.delegate = nil;
    [self.webView removeFromSuperview];
    
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

4. 场景验证

现在有一个需求,比如说直播间的半屏的H5页面,要求我们点击某一个按钮,我们push到一个全屏的H5页面,按钮点击一定是H5的东西,要求的就是通过jsbridge让原生对点击事件进行响应,具体跳转到哪个界面,都是通过上面一起定义的函数的json数据进行传递的。

半屏H5页面
跳转到全屏H5的独立页面

后记

本篇主要讲述了jsbridge的集成,感兴趣的给个赞或者关注~~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容