使用 CXYWebScript,简化 iOS App 与 H5 交互

CXYWebScript

简化 iOS App 与 H5 交互,H5 直接调用 window.App?.onSayHello('Hello'),即可完成对原生 App onSayHello: 方法的调用。如此,与 H5 在 Android 端调用一致。

H5 端的简化
javascript 复制代码
// 以前 需要区分环境
if (isAndroid) {
    window.App.onSayHello('Hello'); 
}
 
if (isiOS) {
    window.webkit.messageHandlers.onSayHello.postMessage('Hello');   
}
​
// 现在 iOS使用CXYWebScript后, H5无需引入任何库,iOS和Android统一调用:
window.App?.onSayHello('Hello')

Tip1: 以上 window.App 是可以自定义的,如 window.CXY

Tip2: 判断当前环境是客户端还是其他H5端,可直接使用 if (window.App) 就行

iOS 端的简化
swift 复制代码
// 以前
CXYWeakScriptMsgHander *weakSelf = [[CXYWeakScriptMsgHander alloc] initWithHandler:self];
WKUserContentController *contentController = self.webView.configuration.userContentController;
[contentController addScriptMessageHandler:weakSelf name:@"onSayHello"];
[contentController addScriptMessageHandler:weakSelf name:@"onPreviewImages"];
[contentController addScriptMessageHandler:weakSelf name:@"onShareObj"];
​
#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
   
    NSString *selector = message.name;
    id body = message.body;
  
    if ([selector isEqualToString:@"onSayHello"]) {
        
    } else if ([selector isEqualToString:@"onPreviewImages"]) {
        
    } else if ([selector isEqualToString:@"onShareObj"]) {
        
    }
}
​
// 现在
self.webScript = [[CXYWebScript alloc] initWithWebView:self.webView];
[self.webScript useUIDelegate];
​
// block 方式
[self.webScript addJsFunc:@"onSayHello" block:^NSString * _Nullable(NSArray *args) {
    return @"支持返回字符串或nil,给JS";
}];
​
// async-block 方式,支持异步返回值
[self.webScript addJsFunc:@"onPreviewImages" asyncBlock:^(NSArray * _Nonnull args, CXYStrBlock  _Nonnull returnBlock) {  
  
    cxy_dispatch_after_main_delay_block(2, ^{    
        returnBlock(@"我是异步返回值,2秒后才返回"); // returnBlock 必须要执行
    });
}];
​
// 或者 action-target 方式
[self.webScript addTarget:self
                   jsFunc:@"onPreviewImages"
                    ocSel:@selector(onPreviewImages:currentIndex:)];
​
[self.webScript addTarget:self
                   jsFunc:@"onShareObj"
                    ocSel:@selector(onShareObject:)];
​

CXYWebScript 有以下特点:

  • H5 在调用原生 App 方法是时,不需要区分是 Android 还是 iOS 环境
  • 支持使用 block,async-block 和 target-action 方式注册 js 方法
  • 支持原生 App 传递返回值给 H5 (限字符串类型或 nil )
  • 支持 OC 与 Swift, iOS 10+
  • 整个实现不到 200 行代码

原理解释:

1、让 WKWebView 注入下面的代码到 JS 环境中:

ini 复制代码
NSString *js = 
  @"window.App = new Proxy({},{      \
    get: function (target, name) {   \
        return function (...args) {  \
            return window.prompt(name,JSON.stringify(args)); \
        }; \
    }      \
  });"
​
WKUserScript *script = [[WKUserScript alloc] initWithSource:js injectionTime (WKUserScriptInjectionTimeAtDocumentStart) forMainFrameOnly:NO];    
[self.webView.configuration.userContentController addUserScript:script];

提示:

使用window.webkit.messageHandlers[name].postMessage(args); 亦可,只是后续实现略有不同。

问:为什么选用window.prompt方式呢?

答:因为它能直接同步返回值。

Proxy 的作用是拦截对 window.App 对象属性的访问。重写window.Appget 方法,当访问它的属性(name)时,会返回一个匿名函数

  • 该函数的参数是可变的,类数组

  • 该函数返回了window.prompt(window.App.属性名, args的json字符串数组);

    那么当 H5 端 js 调用:

dart 复制代码
window.App.onSayHello('Hello')  等价于 => window.prompt('onSayHello',"['Hello']"); 

2、而在 iOS H5 里调用 window.prompt 会执行 WKWebView.UIDelegate 的方法:

objectivec 复制代码
- (void)webView:(WKWebView *)webView
runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt
    defaultText:(nullable NSString *)defaultText
initiatedByFrame:(WKFrameInfo *)frame
completionHandler:(void (^)(NSString * _Nullable result))completionHandler;
​
// 调用completionHandler后,能同步返回值给H5端。
completionHandler('返回值') 

其中的两个参数分别对应方法名参数列表JSON数组

ini 复制代码
(NSString *)prompt => 'onSayHello'
(nullable NSString *)defaultText => '['Hello']'

3、对于 target-action 方式,根据方法名得到对应的 SEL,使用NSInvocation类,可以构造一个表示方法调用的对象,包括方法选择器、目标对象、参数和返回值。可以处理具有多个参数的方法调用。

ini 复制代码
// self.scriptMap = {@"onSayHello": NSStringFromSelector(onSayHello:)}
NSString *selString = self.scriptMap[prompt];  
SEL sel = NSSelectorFromString(selString);
NSArray *args = [self arrayWithJSON:defaultText];
​
NSMethodSignature *signature = [self methodSignatureForSelector:aSelector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:self];
[invocation setSelector:aSelector];
... 详细见源码

4、对于 blockasync-block 方式,根据方法名找到对应的 block,直接执行 block 就行:

ini 复制代码
// self.blockMap = {@"onSayHello": CXYBlock}
CXYBlock block = self.blockMap[prompt];
NSArray *args = [self arrayWithJSON:defaultText];
block(args);

源码

更多介绍和使用,请看 Github , 欢迎 star

github.com/iHongRen/CX...

相关推荐
如此风景19 小时前
IOS UIKit 相关知识
ios
QuantumLeap丶21 小时前
《Flutter全栈开发实战指南:从零到高级》- 22 -插件开发与原生交互
android·flutter·ios
2501_9159214321 小时前
混合开发应用安全方案,在多技术栈融合下构建可持续、可回滚的保护体系
android·安全·ios·小程序·uni-app·iphone·webview
Sheffi6621 小时前
RunLoop Mode 深度剖析:为什么滚动时 Timer 会“失效“?
ios·objective-c
QuantumLeap丶1 天前
《Flutter全栈开发实战指南:从零到高级》- 21 -响应式设计与适配
android·javascript·flutter·ios·前端框架
2501_915106321 天前
Charles抓包怎么用 Charles抓包工具详细教程、网络调试方法、HTTPS配置与手机抓包实战
网络·ios·智能手机·小程序·https·uni-app·webview
00后程序员张1 天前
Fastlane 结合 开心上架,构建跨平台可发布的 iOS 自动化流水线实践
android·运维·ios·小程序·uni-app·自动化·iphone
wjm0410061 天前
秋招ios面试 -- 真题篇(三)
ios·面试·职场和发展
ii_best1 天前
ios脚本开发工具安装按键精灵uncOver越狱教程ios14以及以下系统
ios·自动化·编辑器
游戏开发爱好者81 天前
iOS 性能测试的工程化方法,构建从底层诊断到真机监控的多工具测试体系
android·ios·小程序·https·uni-app·iphone·webview