吕文翰 php,自己动手打造基于 WKWebView 的混合开发框架(二)——js 向 Native 一句话传值并反射出 Swift 对象执行指定函数...

自己动手打造基于 WKWebView 的混合开发框架(二)——js 向 Native 一句话传值并反射出 Swift 对象执行指定函数

2015-9-2 / 阅读数:39757 / 分类: iOS & Swift

本篇文章中,我将跟大家一起学习使用 WKWebView 屌炸天的新传值方式,实现从 Javascript 层向 Native 层的传值,并反射出我们想要的对方,执行我们想要的方法。

Javascript 层和 Native 层的定义

基本定义

Javascript 层此处指网页中的 js runtime,就是所有 js 运行的地方,我们将其看做一层 js 虚拟机。而此处的 Native 层指的是 Swift 或者 OC 代码运行的那层,严格意义上来讲,这一层并没有运行时(runtime),他们都是编译型语言,在硬件设备上运行时,用的都是二进制形式,所有变量的字符串名称亦不再存在,只有指针。

苹果的 runtime 技术

为了避免上文中编译型语言的缺点,苹果构造甚至直接开放了 runtime,不仅在编译的时候存储了 名称(类,函数,变量)<=>指针 对应表,还把这些底层 runtime 接口开放给所有开发者使用。以我对 OC 浅薄的了解,似乎 OC 底层的面向对象的实现就是直接用的 runtime。

有了 名称<=>指针 对应表,我们就可以胡作非为啦 HIAHIA~

屌炸天的一句话传值

苹果在 WKWebView 中的 js runtime 里事先注入了一个 window.webkit.messageHandlers.OOXX.postMessage() 方法,我们可以使用这个方法直接向 Native 层传值,异常方便。首先,我们要把一个名为 OOXX 的 ScriptMessageHandler 注册到我们的 wk。

增加 delegate

打开我们的 BuildYourOwnHybridDevelopmentFramework 项目,转到 ViewController 类,给 ViewController 增加一个 delegate:class ViewController: UIViewController, WKNavigationDelegate, WKUIDelegate, WKScriptMessageHandler {

... ...

注册 ScriptMessageHandler

修改 wk 的初始化函数为:let conf = WKWebViewConfiguration()

conf.userContentController.addScriptMessageHandler(self, name: "OOXX")

self.wk = WKWebView(frame: self.view.frame, configuration: conf)

handle js 的传值

我们使用之前的优雅方法注册一个处理 js 传过来的值的函数:private typealias wkScriptMessageHandler = ViewController

extension wkScriptMessageHandler {

func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) {

print(message.name)

print(message.body.description)

}

}

哦了,让我们看看效果。

检验成果

依旧使用 Safari 的 WebAPP 开发工具,在当前页面执行 js 传值代码:window.webkit.messageHandlers.OOXX.postMessage("Hello WebKit!")

得到如下结果:

8528c9dc8f8ab5e74b6a3a891121270b.png

搞定!

实现反射

接下来我们将着手反射出我们需要的对象,并执行指定函数,并把结果返回到 js runtime 中。

传递 js 对象到 Native

这个听着有点玄乎呀,不过这个功能是真实存在的,这其实是我某一天脑洞大开想到的,一看苹果果然已经支持了。所谓传递 js 对象,就是我们说的 JSON(严格意义上讲形式上有一点点区别,js 对象的 键 不用加双引号),这是 js 内建的数据存储结构,这种神奇的简单的结构不仅是最流行的格式化数据传输协议,更是 js 这个现代 lisp 语言强大的基础。

我们直接使用上面的数据传递接口传递 js 对象:window.webkit.messageHandlers.OOXX.postMessage({className: "Callme", functionName: "maybe"})

解析为 NSDictionary

在 Native 层直接将其转化为 NSDictionary 并打印出 className 和 functionName 的值:func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) {

if message.name == "OOXX" {

if let dic = message.body as? NSDictionary {

print(dic["className"])

print(dic["functionName"])

}

}

}

运行项目,执行 js,检查成果:

f0f4d9b52ae8867ff772a686b69244d2.png

搞定!下一步反射出对象并执行指定函数。

反射出对象并执行指定函数

我们使用得到的 className 和 functionName 反射出指定的对象,并执行指定函数:func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) {

if message.name == "OOXX" {

if let dic = message.body as? NSDictionary,

className = dic["className"]?.description,

functionName = dic["functionName"]?.description {

if let cls = NSClassFromString(NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName")!.description + "." + className) as? NSObject.Type{

let obj = cls.init()

let functionSelector = Selector(functionName)

if obj.respondsToSelector(functionSelector) {

obj.performSelector(functionSelector)

} else {

print("方法未找到!")

}

} else {

print("类未找到!")

}

}

}

}

上面的代码中,我们使用了 Swift 1.2 中引入的新特性:if let 多个条件,有效的避免了“鞭尸金字塔”的出现。其实这个改进和 if let 本身都只是编译器 trick,帮我们写一些代码而已,但是这些所谓的语法糖能节省开发者许多时间,让开发者把注意力集中在业务逻辑而不是冗长的错误处理代码上面,提高开发效率。同时 OC 连字符串前面都要加 @,实在是有点蛋疼,大家赶快迁移到 Swift 上来吧~

准备 Callme 类和 maybe 函数

为了省事儿我们直接在 ViewController 底部造一个巨简单的 Callme 类,为了反射方便,我们让其继承自 NSObject:class Callme: NSObject {

func maybe() {

print("反射成功!")

}

}

检验成果

下面就到了见证奇迹的时刻,运行项目,调出 Safari 控制台,测试三种情况:

3637da0aa7f859cb31fa7221a1636eab.png

成功!

WRITTEN BY

9918749bb4d134e0a0a19c7c9d51aff8.png

程序员,Swift Contributor,正在写《iOS 可视化编程与 Auto Layout》。

评论:

d519fa3ba424da0387ad97904f00f65e?s=40&d=mm&r=g

ZhHS

2017-03-07 14:02

请问wkwebview与h5交互,h5只能存在本地吗?我在本地尝试交互成功,挂到服务器,JS编译都通不过,提示undefined is not an object (evaluating 'window.webkit.messageHandlers'),请问这是什么原因呢?(麻烦楼主告知一下,我看楼上也有人问,你没有回答,这种方法是只能跟本地的h5交互吗?)

f2eb4aff4d2bcce79de0c4ced545de54.png

2017-03-07 14:11

@ZhHS:当然不是呀,我一直远程用,出现这个错误是 js 执行顺序的问题

f2eb4aff4d2bcce79de0c4ced545de54.png

2017-03-07 14:12

@ZhHS:js 注入要放到 webviewdidfinishload 里面

d519fa3ba424da0387ad97904f00f65e?s=40&d=mm&r=g

ZhHS

2017-03-07 14:30

@JohnLui:谢谢博主!

17795660dc39be85b37e2e2a4b81fd6f.png

鑫鑫

2016-10-24 15:00

window.webkit.messageHandlers.OOXX.postMessage("Hello WebKit!")这句在web检查器里面老是报错

TypeError: undefined is not an object (evaluating 'window.webkit.messageHandlers')

17795660dc39be85b37e2e2a4b81fd6f.png

人人

2016-11-23 16:19

@鑫鑫:我的也报错

17795660dc39be85b37e2e2a4b81fd6f.png

Arthur

2016-12-22 17:31

@人人:有解决的办法了么?

17795660dc39be85b37e2e2a4b81fd6f.png

一只皮皮虾

2017-05-08 20:43

@鑫鑫:我也遇到这个了,。 同求原因~

17795660dc39be85b37e2e2a4b81fd6f.png

时光

2016-10-12 16:11

您好,我想问一下JS的代码只要window.webkit.messageHandlers.OOXX.postMessage({className: "Callme", functionName: "maybe"})

这一集就可以了?不用特别写其他的么?

ac1771acc429c21f800980ed2251f922?s=40&d=mm&r=g

iOS

2016-05-13 08:34

博主,那个用wkwebview的是女,js端怎么调用oc有返回值的方法?

17795660dc39be85b37e2e2a4b81fd6f.png

秦小风

2016-07-13 11:58

@iOS:同问wkwebview 。js端怎么调用oc有返回值的方法

bf33797391677b39906ef209df61d6bf?s=40&d=mm&r=g

小大

2016-11-04 17:47

@秦小风:同问wkwebview 。js端怎么调用oc有返回值的方法

4b9e4612dc60649d9765ac5187454298?s=40&d=mm&r=g

2016-01-25 23:08

明白了 原来问题出在NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName")!.description 我的app名称和项目的文件夹名称是不一样的 我换成项目根目录的文件名即可 。。

c3ce0667366e4d9d32653112402939df.gif

4b9e4612dc60649d9765ac5187454298?s=40&d=mm&r=g

2016-01-25 23:02

不行 加了 nsobject还是找不到类 不知道为啥

4b9e4612dc60649d9765ac5187454298?s=40&d=mm&r=g

2016-01-25 18:29

感谢你的分享   我学会了  并且用了 太感谢了

不过NSObject这个方法在swift2.0里 我调用你这个就不行  去了它 就可以

530f831725d84c0c11764b3cc0a3b707?s=40&d=mm&r=g

雨凡

2015-11-05 21:05

我在实际项目中遇到问题了

cd9254147f2d9380fb749cb8d1fdb71e.gif

通过Safari发送的指令都可以被执行,

但是!-->

我以JS函数的形式写到网页中,然后用swift调用-->it dosen't work!

cd9254147f2d9380fb749cb8d1fdb71e.gif

cd9254147f2d9380fb749cb8d1fdb71e.gif

cd9254147f2d9380fb749cb8d1fdb71e.gif

cd9254147f2d9380fb749cb8d1fdb71e.gif

cd9254147f2d9380fb749cb8d1fdb71e.gif

cd9254147f2d9380fb749cb8d1fdb71e.gif

是什么情况啊?

7dfd52c5a119d28f886509a4996e0e49.gif

7dfd52c5a119d28f886509a4996e0e49.gif

你是不是保留了什么没告诉我

bdc3550ba4c31754d388e0f8bffe54c0.gif

bdc3550ba4c31754d388e0f8bffe54c0.gif

f2eb4aff4d2bcce79de0c4ced545de54.png

2015-11-06 00:38

@雨凡:生命周期问题吧,swift 代码在 js 函数载入之前就调用了那个函数,当然没反应了

530f831725d84c0c11764b3cc0a3b707?s=40&d=mm&r=g

雨凡

2015-11-07 15:21

@JohnLui:我用的是在viewDidLoad里面写了js注入

f2eb4aff4d2bcce79de0c4ced545de54.png

2015-11-07 15:33

@雨凡:果然。。。js 注入要放到 webviewdidfinishload 里面。

530f831725d84c0c11764b3cc0a3b707?s=40&d=mm&r=g

雨凡

2015-11-08 00:35

@JohnLui:

cd9254147f2d9380fb749cb8d1fdb71e.gif

cd9254147f2d9380fb749cb8d1fdb71e.gif这才是重点吗...

43e2aee7ee94ca4aa83e70f1cc1674ad.gif

43e2aee7ee94ca4aa83e70f1cc1674ad.gif

17795660dc39be85b37e2e2a4b81fd6f.png

KangKai

2015-10-04 23:46

吕老师我想问一下,把WKWebView的初始化代码放在viewDidLoad和viewDidAppear有神马区别吗?你这里为什么放在viewDidAppear里面?

f2eb4aff4d2bcce79de0c4ced545de54.png

2015-10-04 23:49

@KangKai:这是一个使用生命周期特性搞的一个省事儿的操作,正式项目中会有些问题,BlackHawk 中已经采用了正确的解决方案。

17795660dc39be85b37e2e2a4b81fd6f.png

KangKai

2015-10-05 00:14

@JohnLui:学习了~

c3ce0667366e4d9d32653112402939df.gif

4b9e4612dc60649d9765ac5187454298?s=40&d=mm&r=g

2016-01-27 13:02

@KangKai:会导致你打开WKWebView慢至少1秒

ab7122cbd35482d23dba510e4ca8d82c?s=40&d=mm&r=g

iuhux

2015-09-15 00:56

楼主,xcode6.4下运行代码performSelector会报 'performSelector' is unavailable: 'performSelector' methods are unavailable这个错,这个方法已经不能在swift里面用了

f2eb4aff4d2bcce79de0c4ced545de54.png

2015-09-15 01:01

@iuhux:这个项目的环境是 Xcode 7 。。。。

ab7122cbd35482d23dba510e4ca8d82c?s=40&d=mm&r=g

iuhux

2015-09-15 23:15

@JohnLui:哦,升级到xcode7就没问题了

abb61acef603f43450efff152f33a259?s=40&d=mm&r=g

Se

2015-10-14 11:44

@JohnLui:xcode7 应该是swift2.0了吧。 那1.2的话,如何解决这个问题呢?

17795660dc39be85b37e2e2a4b81fd6f.png

双木

2015-09-14 16:06

楼主 请教一下

你那个 “依旧使用 Safari 的 WebAPP 开发工具,在当前页面执行 js 传值代码”

那个图是怎么搞出来的?

f2eb4aff4d2bcce79de0c4ced545de54.png

2015-09-14 16:10

@双木:看上一篇文章

0cb50b742823e001090932d8bdf85dc0.gif

0cb50b742823e001090932d8bdf85dc0.gif

发表评论:

昵称

邮件地址 (选填)

个人主页 (选填)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值