Native 与 Web 页面之间的交互方案有哪几种?
拦截URL(适用于 UIWebView 和 WKWebView)
webView 每次打开一个 URL 时,会首先调用 webView 的代理方法来询问 Native 端是否允许打开这个 URL,如果不允许,webView 就不会打开这个 URL。
当 JS 需要调用 OC 时,Web 端将需要传递给 Native 端的参数拼接到一个特定的 URL 上,并打开这个 URL,Navtive 端在收到打开请求后,对特定 URL 的打开请求进行拦截,并从该 URL 上解析出 Web 端传递过来的参数,然后调用相应的 OC 方法。
#pragma mark - UIWebViewDelegate
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSURL *URL = request.URL;
if ([URL.scheme isEqualToString:@"test://scan"])
{
// 执行 OC 方法
return NO;
}
return YES;
}
#pragma mark - WKNavigationDelegate
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
NSURL *URL = navigationAction.request.URL;
if ([URL.scheme isEqualToString:@"test://scan"])
{
// 执行 OC 方法
// 拦截跳转
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
//允许跳转
decisionHandler(WKNavigationActionPolicyAllow);
}
当 OC 需要调用 JS 时,可以直接调用UIWebView
的stringByEvaluatingJavaScriptFromString:
方法或者WKWebView
的evaluateJavaScript: completionHandler:
方法来执行 JS 代码。
// UIWebView
NSString *returnValue = [webView stringByEvaluatingJavaScriptFromString:@"scanResult('扫描结果')"];
// WKWebView
[self.webView evaluateJavaScript:@"scanResult('扫描结果')" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
NSLog(@"执行scanResult方法后的返回值为: %@", result);
}];
这种方式同时适用于 Android 和 iOS,通用性很强。
JavaCriptCore(只适用于 UIWebView,iOS 7.0+)
JavaCriptCore 是 Apple 网页浏览器的引擎,我们可以使用官方提供的 OC 接口来使用该引擎去执行 JS 方法,以及将 JS 方法映射到 OC 方法。使用 JavaScriptCore 时会用到以下 OC 类:
-
JSContext
:javascript 的运行上下文,能够执行 JS 代码和将 JS 方法映射到 OC 方法; -
JSValue
:JSContext
执行 JS 代码后的返回结果,它可以是任何 JS 类型(例如:基本数据类型,函数类型和对象类型),官方提供了 OC 方法来将其转换为 OC 类型; -
JSManagedValue
:是对JSValue
的封装,用它可以解决 JS 和 OC 之间循环引用的问题; -
JSVirtualMachine
:管理 JS 运行时以及管理暴露给 JS 的 OC 对象的内存; -
JSExport
:是一个协议,可以将 OC 对象暴露给 JS。
当 JS 需要调用 OC 时,需要在 webView 加载完成后,获取 webView 的 javascript 运行上下文JSContext
,然后使用JSContext
将 JS 方法映射到 OC 方法。当 JavaScriptCore 执行这个 JS 方法时,会调用其关联的 OC 方法。
#pragma mark - JS 方法
function share(title, imageUrl, link) {
// 具体实现由 Native 端去实现
}
#pragma mark - UIWebViewDelegate
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
// 获取该UIWebview的javascript上下文
self.jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
// JS 调用 OC
// 'share' 就是 JS 方法的名称,将其映射到了一个 OC block
self.jsContext[@"share"] = ^() {
// 获取到`share`方法的所有参数
NSArray *args = [JSContext currentArguments];
// args中的元素是JSValue,需要转成OC的对象
NSMutableArray *messages = [NSMutableArray array];
for (JSValue *obj in args)
{
[messages addObject:[obj toObject]];
}
// 执行 OC 代码去分享 Web 端传递的数据
};
}
如果 Native 端分享成功或者失败后,需要回调 Web 端来刷新网页的UI的话,可以将回调函数也传递到 Native 端,由 Native 端异步执行该 JS 回调函数。
#pragma mark - JS 方法
function shareNew(shareData) {
}
shareData = {
title: "title",
imgUrl: "http://img.dd.com/xxx.png",
link: location.href,
result: function(isSuccess) {
// 刷新网页 UI
}
}
#pragma mark - UIWebViewDelegate
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
// 获取该UIWebview的javascript上下文
self.jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
self.jsContext[@"shareNew"] = ^(JSValue *shareData) {
NSLog(@"%@", [shareData toObject]);
JSValue *resultFunction = [shareData valueForProperty:@"result"];
// 将 JS 回调封装成 OC block
void (^result)(BOOL) = ^(BOOL isSuccess) {
[resultFunction callWithArguments:@[@(isSuccess)]];
};
// 异步执行回调 block
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
result(YES);
});
};
}
当 OC 需要调用 JS 时,可以调用JSContext
的evaluateScript:
方法来执行 JS 代码。
JSValue *value = [self.jsContext evaluateScript:@"document.title"];
由于 Android 与 iOS 的 JS 调用 OC 的方式不一样,要想两平台共用一套代码,iOS 这边需要使用JSExport
协议来将 JS 方法映射到 OC 方法,不能直接映射到 OC block。(详细信息,请参看此文章)
WKScriptMessageHandler(只适用于 WKWebView,iOS 8.0+)
使用WKUserContentController
的addScriptMessageHandler: name:
方法来让遵循WKScriptMessageHandler
协议的 OC 对象去监听指定名称的 JS 消息。
当 JS 需要调用 OC 时,使用window.webkit.messageHandlers.
这段 JS 代码来发出一个指定名称的消息。此时,Native 端会执行userContentController: didReceiveScriptMessage:
代理回调方法。
#pragma mark- JS 实现
function share (title, imgUrl, link) {
// 发出share消息
window.webkit.messageHandlers.share.postMessage({title: title, imgUrl: imgUrl, link: link});
}
#pragma mark- OC 实现
WKUserContentController *controller = [[WKUserContentController alloc] init];
[controller addScriptMessageHandler:self name:@"share"];
configuration.userContentController = controller;
self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:configuration];
// WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
if ([message.name isEqualToString:@"share"]) {
id body = message.body;
// 执行分享操作
}
}
如果 Web 端在调用 OC 之后还需要执行回调,可以将 JS 方法转换为字符串(userContentController:didReceiveScriptMessage:
回调方法不能传递 JS 函数类型)传递给 Native 端,然后异步调用WKWebView
的evaluateJavaScript: completionHandler:
来执行 JS 方法。
由于WKWebView
的 Web 端调用 OC 的方式与 Andriod 不一样,为了让 Web 端共用一套代码,需要在 iOS 端转换一下 Web 端调用 OC 的方式,具体可以看下此文章。
第三方库WebViewJavascriptBridge(适用于 UIWebView 和 WKWebView)
WebViewJavascriptBridge第三方库是基于拦截 URL 来实现 OC 与 JS 之间的交互的,但其解决了拦截 URL 方式无法直接获取返回值的缺点。
有关 JS 和 OC 之间的交互的更多信息,可参看这篇文章和这篇文章。
使用 WKWebView 时遇到的问题以及如何解决它们?
白屏问题
使用UIWebView
加载网页,当网页内存占用太大时,App 进程会崩溃。
而WKWebView
是一个多进程组件,其网络加载和 UI 渲染是在 WebContent 进程中执行的。使用WKWebView
加载网页,当网页内存占用太大时,App 进程不会崩溃,但 WebContent 进程会崩溃,从而出现白屏现象。(此时WKWebView
的URL
会变为nil
,[webView reload]
操作已经失效)
iOS 9 以后,当 WebContent 进程崩溃,页面即将白屏的时候,会调用WKNavigationDelegate
的webViewWebContentProcessDidTerminate:
方法,而此时WKWebView
的URL
还不是nil
,在该方法中执行[webView reload]
就可以解决白屏问题。
但并不是所有 H5 页面白屏的时候,都会调用webViewWebContentProcessDidTerminate:
方法。例如,在一个高内存消耗的H5页面上 present 系统相机,拍照完毕后返回 H5 页面时出现白屏现象(拍照过程消耗来大量内存,导致内存紧张,WebContent 进程被系统挂起),但是却不会调用webViewWebContentProcessDidTerminate
方法。在WKWebView
白屏时,WKWebView
的title
会被置nil
。所以,可以在 viewWillAppear 时,根据WKWebView
的title
是否为nil
来执行[WKWebView loadRequest:]
。
Cookie 管理
WKWebView
的loadRequest:
方法不会自动带上存储于NSHTTPCookieStorage
中的Cookie
,而UIWebView
会自动带上Cookie
。
在执行[WKWebView loadRequest:]
操作之前,在request
的header
中手动设置好NSHTTPCookieStorage
中存储的Cookie
就能解决首个请求Cookie
带不上的问题。
通过向WKUserContentController
中添加document.cookie = cookie
脚本来设置Cookie
,可以解决后续同域请求Cookie
丢失的问题。
跨域请求时,由于WKWebView
每次跳转一个 URL 时都会调用WKNavigationDelegate
的decidePolicyForNavigationAction:
方法,因此可以在该方法中手动在request
的header
中带上Cookie
。
通知的回调是在哪个线程触发的?
在一个线程中发出通知后,会在该线程中去触发通知的回调方法。
const、static 和 extern 修饰符的作用
const
使用const
修饰变量时,变量是不能被修改的。当使用const
修饰指针时(const
在*
右边),指针不能修改,但是指针指向的内存区域所存储的具体内容是可以修改的。当使用const
修饰指针指向的内存区域时,指针指向的内存区域所存储的具体内容是不能修改的,但是指针可以被修改(将指针指向其他内存区域)。
static
局部变量存储在栈区,当局部变量的作用域(栈帧)被销毁时,局部变量会随之一起被销毁。当使用static
修饰局部变量,局部变量会变为静态局部变量。静态局部变量存储在静态/全局区,在程序运行结束时,由系统自动销毁。
全局变量存储在静态/全局区,其作用域仅为当前.h
或者.m
文件,在其他文件中是不能访问该全局变量的,即使在其他文件中引用了当前.h
文件(.m
文件不能引用),也不能访问该全局变量。当全局变量是在.h
文件中声明并定义的,并使用static
修饰该全局变量时,全局变量会变为静态全局变量,其作用域变为所有的.h
和.m
文件。只要在其他.h
和.m
文件中引用静态全局变量所在的.h
文件,就可以访问该静态全局变量了。如果全局变量是在.m
文件中声明并定义的,想要在其他文件中访问该局部变量的话,则需要使用extern
修饰符。另外,如果在一个类的.h
或者.m
中定义了一个全局变量,然后在其他类的.h
或者.m
文件中又定义了一个名称相同的全局变量,编译器会报错,这时使用static
修饰符后就不会报错了。
extern
在.h
或者.m
文件中声明并定义的全局变量,如果需要在其他.h
或者.m
文件中访问该全局变量,可以直接在其他.h
或者.m
文件中(不需要引入.h
文件),使用extern
修饰符来声明一个名称和类型与全局变量完全相同的变量,编译器在编译时,会首先在当前.h
或者.m
文件中根据所声明的变量的名称和类型去查找对应的全局变量,如果当前.h
或者.m
文件中存在对应的全局变量,就会将变量与全局变量关联起来;如果当前文件不存在,就会到其他.h
或者.m
文件中去查找。如果所有.h
或者.m
文件中都不存在对应的全局变量,编译器会报错。
#define 和 static inline
#define
,宏定义。编译器会在预编译时期将代码中使用的宏替换成真正的代码,但不会进行任何计算。例如#define test(x) x*x
,test(2+1)
会被替换成2+1*2+1
,最后其结果值为5
,而不是9
。使用宏可以抽离很多重复使用的代码段,但是大量的宏会导致编译时间过长,并且编译器不会对宏进行类型检查。
static inline
,内联函数。函数需要被编译成汇编指令,才能真正执行。普通函数在被调用的时候,CPU 会执行CALL
指令。而内联函数在编译的时候,会直接在需要执行内联函数的地方(普通函数执行CALL
的汇编语句处)将内联函数的汇编片段拷贝一份并插入到此处,代替CALL
指令(类似于宏定义)。所以,内联函数的执行效率比较高。但是,内联函数通常都是比较简单的代码片段,不能包含循环,递归等的复杂流程,代码最好不要超过5行。
CocoaPods的原理
CocoaPods 将项目所依赖的第三方库都放到另一个名为 Pods 的项目中,然后让主项目依赖 Pods 项目。CocoaPods 会将第三方库以 Target 的方式组成一个名为 Pods 的工程,整个 Pods 工程会被编译为名称为 libPods.a 的静态库来让主工程去使用。
有关更多信息,可以参看这篇文章。
PNG,JPEG(JPG) 和 WebP 三种图片格式之间的区别
- JPEG(JPG):是一种有损压缩的图片格式,可以精确控制其压缩比,压缩比越低,图像质量就越差,其不支持alph通道;
- PNG:是一种无损压缩的图片格式,其压缩比是有上限的,且不能精确控制其压缩比,其支持完整的alpha通道;
- WebP:同时支持有损压缩和无损压缩,其具有更优的图像数据压缩算法,相较于JPEG的有损压缩和PNG的无损压缩,WebP的有损压缩和无损压缩能在带来更小的图片体积的同时,还能拥有肉眼识别无差异的图像质量,并且其支持alpha通道和动画。
iOS 的系统架构是怎样的
iOS 的系统架构分为四层,由上到下依次为:Cocoa Touch(Cocoa 触摸层)、Media(媒体层)、Core Services(核心服务层)、Core OS(核心操作系统层)。
- Cocoa Touch:负责用户在 iOS 设备上的触摸交互操作,UIKit 框架就属于这一层;
- Media:媒体层可以在应用程序中处理各种媒体文件,例如:进行音频与视频的录制,图形的绘制,以及制作基础的动画效果。Core Graphics,Core Image,OpenGL ES 和 Core Audio 等框架属于这一层;
- Core Services:可以通过核心服务层来访问 iOS 的一些基础服务,例如:Address Book,Core Data,Core Location,CFNetwork,Core Foundation,Foundation 等框架;
- Core OS:核心操作系统层包括内存管理、文件系统、电源管理以及一些其他的操作系统任务,直接和硬件设备进行交互。
__attribute __
参看__attribute __。
弱网优化
参看浅谈APP弱网优化。