iOS 面试知识点(五)

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 时,可以直接调用UIWebViewstringByEvaluatingJavaScriptFromString:方法或者WKWebViewevaluateJavaScript: 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 方法;
  • JSValueJSContext执行 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 时,可以调用JSContextevaluateScript:方法来执行 JS 代码。

JSValue *value = [self.jsContext evaluateScript:@"document.title"];

由于 Android 与 iOS 的 JS 调用 OC 的方式不一样,要想两平台共用一套代码,iOS 这边需要使用JSExport协议来将 JS 方法映射到 OC 方法,不能直接映射到 OC block。(详细信息,请参看此文章)

WKScriptMessageHandler(只适用于 WKWebView,iOS 8.0+)

使用WKUserContentControlleraddScriptMessageHandler: name:方法来让遵循WKScriptMessageHandler协议的 OC 对象去监听指定名称的 JS 消息。

当 JS 需要调用 OC 时,使用window.webkit.messageHandlers..postMessage()这段 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 端,然后异步调用WKWebViewevaluateJavaScript: 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 进程会崩溃,从而出现白屏现象。(此时WKWebViewURL会变为nil[webView reload]操作已经失效)

iOS 9 以后,当 WebContent 进程崩溃,页面即将白屏的时候,会调用WKNavigationDelegatewebViewWebContentProcessDidTerminate:方法,而此时WKWebViewURL还不是nil,在该方法中执行[webView reload]就可以解决白屏问题。

但并不是所有 H5 页面白屏的时候,都会调用webViewWebContentProcessDidTerminate:方法。例如,在一个高内存消耗的H5页面上 present 系统相机,拍照完毕后返回 H5 页面时出现白屏现象(拍照过程消耗来大量内存,导致内存紧张,WebContent 进程被系统挂起),但是却不会调用webViewWebContentProcessDidTerminate方法。在WKWebView白屏时,WKWebViewtitle会被置nil。所以,可以在 viewWillAppear 时,根据WKWebViewtitle是否为nil来执行[WKWebView loadRequest:]

Cookie 管理

WKWebViewloadRequest:方法不会自动带上存储于NSHTTPCookieStorage中的Cookie,而UIWebView会自动带上Cookie

在执行[WKWebView loadRequest:]操作之前,在requestheader中手动设置好NSHTTPCookieStorage中存储的Cookie就能解决首个请求Cookie带不上的问题。

通过向WKUserContentController中添加document.cookie = cookie脚本来设置Cookie,可以解决后续同域请求Cookie丢失的问题。

跨域请求时,由于WKWebView每次跳转一个 URL 时都会调用WKNavigationDelegatedecidePolicyForNavigationAction:方法,因此可以在该方法中手动在requestheader中带上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*xtest(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(核心操作系统层)。

iOS 架构.png
  • Cocoa Touch:负责用户在 iOS 设备上的触摸交互操作,UIKit 框架就属于这一层;
  • Media:媒体层可以在应用程序中处理各种媒体文件,例如:进行音频与视频的录制,图形的绘制,以及制作基础的动画效果。Core GraphicsCore ImageOpenGL ESCore Audio 等框架属于这一层;
  • Core Services:可以通过核心服务层来访问 iOS 的一些基础服务,例如:Address BookCore DataCore LocationCFNetworkCore FoundationFoundation 等框架;
  • Core OS:核心操作系统层包括内存管理、文件系统、电源管理以及一些其他的操作系统任务,直接和硬件设备进行交互。

__attribute __

参看__attribute __。

弱网优化

参看浅谈APP弱网优化。

你可能感兴趣的:(iOS 面试知识点(五))