八、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,然是使用前面介绍的方法便可以实现数据互通与交互。这种方式是最简单的混合开发,但其性能和原生相比要差一些。示意图如下:
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
运行工程,效果如下图所示,点击按钮即可实现简单的界面颜色切换:
上面的示例工程我只实现了UILabel类与UIButton类的JS-OC转换,如果将原生控件和JS对象再进行一层绑定,并且实现大部分JS类与原生类和他们内部的属性,则我们就开发了一套Hybird App开发框架,但并没有这个必要,如果你对更多兴趣,可以深入学习下ReactNative。
文中的示例Demo我放在了Github上,地址如下:https://github.com/ZYHshao/Demo-Hybird。