iOS问题记录本:iOS8/Swift/WKWebView/address=0x0错误

iOS问题记录本:iOS8/Swift/WKWebView/address=0x0错误

0、背景描述

前一段时间,公司有个小的新项目,因为考虑到项目本身没有任何技术要求,所以决定采用Swift加上WKWebView的方案进行开发;而且前一段时间团队内部也进行了基础开发库Swift化的工作,这个项目也可以用来检验之前的工作。

当然,吃螃蟹必然是要付出一些代价的,不过因为项目本身的规模很小,所以Swift语言引起的问题并不多,最麻烦的地方在于WKWebView,其中最麻烦的一个是WKWebView的JS交互阻塞AJAX请求的问题,不过这并不是本文叙述的要点,就不详述了。

项目测试快结束的时候,测试人员在进行兼容性测试时,发现在iOS8系统上启动就闪退,我调试发现原因是Swift基础库pod中依赖了Contacts系统framework,而这个framework是iOS9系统之后才有的,所以启动必闪退。
解决了这个问题后,我运行发现应用在iOS8系统上还是闪退,断点停留在AppDelegate上,报错信息为:

EXC_BAD_ACCESS(code=1,address=0x0)

1、问题查找

因为闪退时断点停留在AppDelegate上,且没有更详细的报错信息,所以无法使用常规的方法查找问题所在。

不过类似的问题还是遇到过的,比如EXC_BAD_ACCESS(code=1,address=0x10)这个问题,它是block释放后被执行造成的闪退问题,所以我怀疑现在这个问题也类似,但是在网上无法搜索到类似的信息,StackOverFlow上有人提问,但没有最终结果。

所以最后只能采用笨办法——排除法,把系统启动时执行的代码挨个注释掉查看效果。最终定位到问题所在是为WebView设置UserAgent的代码:

// 获取默认User-Agent
let wkWebView = WKWebView()
wkWebView.evaluateJavaScript("navigator.userAgent") { (result, error) in
    var oldAgent = ""
    if result is String {
        oldAgent = result as! String
        if oldAgent.count > 0 {
            oldAgent += " "
        }
    }
    let newAgent = oldAgent + "app-iOS"
    // 设置global User-Agent
    UserDefaults.standard.register(defaults: ["UserAgent":newAgent])
    UserDefaults.standard.synchronize()
}

2、原因分析

经过测试,即便是在一个空白的Swift工程里进行最简单的调用,如:

WKWebView().evaluateJavaScript("") { (result, error) in }

同样会导致闪退。

而该方法的回调参数不传时WKWebView().evaluateJavaScript("")则不会报错。

我们上一节说过有一个类似的问题,而address=0x10问题的原因在是block的结构体寻址问题:

//__block_imp:  这个是编译器给我们生成的结构体,每一个block都会用到这个结构体
struct __block_impl {
  void *isa;         //类型
  int Flags;          //标识字段
  int Reserved;       //保留字段       
  void *FuncPtr;       //函数指针,这个会指向编译器给我们生成的下面的静态函数__main_block_func_0
};

0x10是十六进制,也就是struct基地址后的第16个字节,其中void *类型占8个字节,int类型占4个字节,所以0x10的地址就是FuncPtr的地址,而address=0x10的问题也正是对值为nil的block强行调用导致的。

那么,举一反三来看,address=0x0的问题,0x0地址就是指向isa函数指针字段的地址,这个错误发生的原因就是调用isa造成的。

WKWebView执行JavaScript时为何调用isa我们暂不去考虑,已经可以确定如果block的值为nil时,调用isa字段,是会造成address=0x0问题的。所以,下面我们需要考虑的就是block为什么变成nil?

在本节开头贴的简单代码中,有几点是需要注意的:

  • WKWebView()会生成一个临时变量;
  • evaluateJavaScript方法执行时是异步的;
  • 异步执行时,尾随闭包可能是直接或者间接被WKWebView的实例持有的;

那么,block的值变为nil的原因应该是WKWebView的临时变量实例被自动释放,所以block变量也随同被释放了。

经过多次测试也发现,该问题并非是100%重现的,我在iPhone6 plus测试机上运行时,有90%左右的重现几率。而这一现象如果用autoReleasePool来解释也是通的。

3、解决方案

这个问题最好的解决方法当然是让WKWebView在执行JavaScript脚本时,对block变为nil的情况进行容错处理;但是很抱歉,这需要苹果爸爸去修改,而且它们可能也无法修复iOS8这种低版本系统的问题。

那么头痛医头脚痛医脚,既然问题的原因可能是block被释放导致的,那就让它不被释放好了。

于是,我让在类里面直接声明并持有闭包变量,然后调用WKWebView方法的时候传递闭包变量进去,可是然并卵!!应用还是闪退了!

然后我就意识到我犯蠢了,作为参数传递的闭包变量,在WKWebView内部一般都要copy一份,所以我直接持有闭包变量并无法影响WKWebView内部闭包变量的释放。

最终还是将WKWebView声明为类的属性,让类持有它,这样它就不会因为是临时变量而被autoReleasePool所释放。

最后测试,效果上OK,闪退问题没有重现。

4.说明

最终的解决方案只是基于对问题原因的猜测,至于问题的原因是否确实如此还没有时间深入探寻,所以如果最终并非如此而对其他人造成误导,还请大家谅解。如果文章中有错误,还请多多指点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值