让你的iOS应用程序支持运行JavaScript脚本:JavaScriptCore框架详解(四)

八、Hybird App 构建思路


   Hybird App是指混合模式移动应用,即其中既包含原生的结构有内嵌有Web的组件。这种App不仅性能和用户体验可以达到和原生所差无几的程度,更大的优势在于bug修复快,版本迭代无需发版。3月8日苹果给许多开发者发送了一封警告邮件,主要是提示开发者下载脚本动态更改App原本行为的做法将会被提审拒绝。其实这次邮件所提内容和Hybird App并无太大关系(对ReactNative也没有影响),苹果警告的是网络下发脚本并且使用runtime动态修改Native行为的应用,Hybird App的实质并没有修改原Native的行为,而是将下发的资源进行加载和界面渲染,类似WebView。


   关于混合开发,我们有两种模式:


   1.Native内嵌WebView,通过JS与OC交互实现业务无缝的衔接。


   无论是UIWebView还是WKWebKit,我们都可以在其中拿到当前的JSContext,然是使用前面介绍的方法便可以实现数据互通与交互。这种方式是最简单的混合开发,但其性能和原生相比要差一些。示意图如下:


让你的iOS应用程序支持运行JavaScript脚本:JavaScriptCore框架详解(四)


   2.下发JS脚本,使用类似ReactNative的框架进行原生渲染


   这是一种效率非常高的混合开发模式,并且ReactNative也本身支持android和iOS公用一套代码。我们也可以使用JavaScriptCore自己实现一套解析逻辑,使用JavaScript来编写Native应用,要完整实现这样一套东西太复杂了,我们也没有能力完成一个如此庞大的工程,但是我们可以做一个小Demo来模拟其原理,这样可以更好的帮助我们理解Hybird App的构建原理。


我们打算实现这样的功能:通过下发JS脚本创建原生的UILabel标签与UIButton控件,首先编写JS代码如下:


(function(){

   console.log("ProgectInit");

   //JS脚本加载完成后 自动render界面

   return render();

})();


//JS标签类

function Label(rect,text,color){

   this.rect = rect;

   this.text = text;

   this.color = color;

   this.typeName = "Label";

}

//JS按钮类

function Button(rect,text,callFunc){

   this.rect = rect;

   this.text = text;

   this.callFunc = callFunc;

   this.typeName = "Button";

}

//JS Rect类

function Rect(x,y,width,height){

   this.x = x;

   this.y = y;

   this.width = width;

   this.height = height;

}

//JS颜色类

function Color(r,g,b,a){

   this.r = r;

   this.g = g;

   this.b = b;

   this.a = a;

}

//渲染方法 界面的渲染写在这里面

function render(){

   var rect = new Rect(20,100,280,30);

   var color = new Color(1,0,0,1);

   var label = new Label(rect,"Hello World",color);

   var rect2 = new Rect(20,150,280,30);

   var color2 = new Color(0,1,0,1);

   var label2 = new Label(rect2,"Hello Native",color2);

   var rect3 = new Rect(20,200,280,30);

   var color3 = new Color(0,0,1,1);

   var label3 = new Label(rect3,"Hello JavaScript",color3);

   var rect4 = new Rect(20,240,280,30);

   var button = new Button(rect4,"我是一个按钮",function(){

                           var randColor = new Color(Math.random(),Math.random(),Math.random(),1);

                           Globle.changeBackgroundColor(randColor);

                           });

   //将控件以数组形式返回

   return [label,label2,label3,button];

}


创建一个Objective-C类绑定到JS全局对象上,作为OC方法的桥接器:


//.h

#import <Foundation/Foundation.h>

#import <UIKit/UIKit.h>

#import <JavaScriptCore/JavaScriptCore.h>

@protocol GloblePrptocol <JSExport>

-(void)changeBackgroundColor:(JSValue *)value;

@end

@interface Globle : NSObject<GloblePrptocol>

@property(nonatomic,weak)UIViewController * ownerController;

@end

//.m

#import "Globle.h"


@implementation Globle

-(void)changeBackgroundColor:(JSValue *)value{

   self.ownerController.view.backgroundColor = [UIColor colorWithRed:value[@"r"].toDouble green:value[@"g"].toDouble blue:value[@"b"].toDouble alpha:value[@"a"].toDouble];

}

@end

在ViewController中实现一个界面渲染的render解释方法,并建立按钮的方法转换,如下:


//

//  ViewController.m

//  JavaScriptCoreTest

//

//  Created by vip on 17/3/6.

//  Copyright © 2017年 jaki. All rights reserved.

//


#import "ViewController.h"

#import <JavaScriptCore/JavaScriptCore.h>

#import "Globle.h"

@interface ViewController ()


@property(nonatomic,strong)JSContext * jsContext;

@property(nonatomic,strong)NSMutableArray * actionArray;

@property(nonatomic,strong)Globle * globle;

@end


@implementation ViewController


- (void)viewDidLoad {

   [super viewDidLoad];

   //创建JS运行环境

   self.jsContext = [JSContext new];

   //绑定桥接器

   self.globle =  [Globle new];

   self.globle.ownerController = self;

   self.jsContext[@"Globle"] = self.globle;

   self.actionArray = [NSMutableArray array];

   [self render];

}

//界面渲染解释器

-(void)render{

   NSString * path = [[NSBundle mainBundle] pathForResource:@"main" ofType:@"js"];

   NSData * jsData = [[NSData alloc]initWithContentsOfFile:path];

   NSString * jsCode = [[NSString alloc]initWithData:jsData encoding:NSUTF8StringEncoding];

   JSValue * jsVlaue = [self.jsContext evaluateScript:jsCode];

   for (int i=0; i<jsVlaue.toArray.count; i++) {

       JSValue * subValue = [jsVlaue objectAtIndexedSubscript:i];

       if ([[subValue objectForKeyedSubscript:@"typeName"].toString isEqualToString:@"Label"]) {

           UILabel * label = [UILabel new];

           label.frame = CGRectMake(subValue[@"rect"][@"x"].toDouble, subValue[@"rect"][@"y"].toDouble, subValue[@"rect"][@"width"].toDouble, subValue[@"rect"][@"height"].toDouble);

           label.text = subValue[@"text"].toString;

           label.textColor = [UIColor colorWithRed:subValue[@"color"][@"r"].toDouble green:subValue[@"color"][@"g"].toDouble blue:subValue[@"color"][@"b"].toDouble alpha:subValue[@"color"][@"a"].toDouble];

           [self.view addSubview:label];

       }else if ([[subValue objectForKeyedSubscript:@"typeName"].toString isEqualToString:@"Button"]){

           UIButton * button = [UIButton buttonWithType:UIButtonTypeSystem];

           button.frame = CGRectMake(subValue[@"rect"][@"x"].toDouble, subValue[@"rect"][@"y"].toDouble, subValue[@"rect"][@"width"].toDouble, subValue[@"rect"][@"height"].toDouble);

           [button setTitle:subValue[@"text"].toString forState:UIControlStateNormal];

           button.tag = self.actionArray.count;

           [button addTarget:self action:@selector(buttonAction:) forControlEvents:UIControlEventTouchUpInside];

           [self.actionArray addObject:subValue[@"callFunc"]];

           [self.view addSubview:button];

           

       }

   }

}

//按钮转换方法

-(void)buttonAction:(UIButton *)btn{

   JSValue * action  = self.actionArray[btn.tag];

   //执行JS方法

   [action callWithArguments:nil];

}




@end

运行工程,效果如下图所示,点击按钮即可实现简单的界面颜色切换:

让你的iOS应用程序支持运行JavaScript脚本:JavaScriptCore框架详解(四)



上面的示例工程我只实现了UILabel类与UIButton类的JS-OC转换,如果将原生控件和JS对象再进行一层绑定,并且实现大部分JS类与原生类和他们内部的属性,则我们就开发了一套Hybird App开发框架,但并没有这个必要,如果你对更多兴趣,可以深入学习下ReactNative。


   文中的示例Demo我放在了Github上,地址如下:https://github.com/ZYHshao/Demo-Hybird

上一篇:用AI还原地道京片子!作者大谷亲自揭秘老北京视频语音修复,网友:黄渤穿越了?


下一篇:为什么第三方数据报告总是不准?