月度归档:2016年07月

定位崩溃UIKit`+[UIViewController _viewControllerForFullScreenPresentationFromView:]:

问题描述
最近在开发中遇到了一个crash,该crash可以稳定复现,复现的步骤如下,
1 在一个navigation controller中,push一个包含scroll view的view controller
2 在该view controller中,点击一个按钮,然后push另一个包含了scroll view新的view controller。
3 pop当前的view controller,回到步骤1中的view controller
4 按iOS的status bar,此时,应用crash

遇到了可以稳定复现的crash,我的第一反应就是查看crash log。然而,令我感到意外的是,crash log显示,错误为
EXC_BAD_ACCESS (SIGBUS)
KERN_EXCEPTION_PROTECTED
crash log显示对应的代码为UIKit`+[UIViewController _viewControllerForFullScreenPresentationFromView:]:

崩溃时候的call stack

这岂不是表明,crash的代码位于iOS的系统中,而不是我自己写的代码,难道viewControllerForFullScreenPresentationFromView这个方法有什么bug?

但是,考虑到这个crash可以稳定复现,我仍然觉得这应该是我的某处代码没有写好的缘故,而不是iOS系统的bug。然而,从crash log无法直接定位到我的代码的具体位置,我一时也没有好的线索去定位错误代码。

为了找寻线索,我搜索了stackoverflow,然后还真的发现了一个类似的问题 http://stackoverflow.com/questions/30080990/ios-app-crashes-with-exc-bad-access-sigsegv-on-ipad-ios-7-1-1-device 。

这个问题的crash log如下
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000c
Triggered by Thread: 0

Thread 0 Crashed:
0 libobjc.A.dylib 0x3b3a1626 objc_msgSend + 6
1 UIKit 0x33301b46 +[UIViewController _viewControllerForFullScreenPresentationFromView:] + 174
2 UIKit 0x33301614 -[UIWindow _scrollToTopViewsUnderScreenPointIfNecessary:resultHandler:] + 428
3 UIKit 0x3330143e -[_UIScrollsToTopInitiatorView touchesEnded:withEvent:] + 210
4 UIKit 0x3330134e -[UIStatusBar touchesEnded:withEvent:] + 334
5 UIKit 0x33255790 forwardTouchMethod + 228
6 UIKit 0x3310371c -[UIWindow _sendTouchesForEvent:] + 524
7 UIKit 0x330fe6e6 -[UIWindow sendEvent:] + 754
8 UIKit 0x330d38e8 -[UIApplication sendEvent:] + 192
9 UIKit 0x330d1f92 _UIApplicationHandleEventQueue + 7098
10 CoreFoundation 0x3087e258 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 12
11 CoreFoundation 0x3087d726 __CFRunLoopDoSources0 + 202
12 CoreFoundation 0x3087bf1a __CFRunLoopRun + 618
13 CoreFoundation 0x307e6f0a CFRunLoopRunSpecific + 518
14 CoreFoundation 0x307e6cee CFRunLoopRunInMode + 102
15 GraphicsServices 0x356e065e GSEventRunModal + 134
16 UIKit 0x33132168 UIApplicationMain + 1132
17 MyApp 0x0029c0a0 main (main.m:16)
18 libdyld.dylib 0x3b8a4ab4 start + 0

我发现,这个crash log和我的crash log几乎一模一样,尽管这个stackoverflow的问题并没有人回答,然而,问题的一个comment还是给了我线索。

It’s not a memory leak problem. Somewhere you seem to be trying to access a deallocated object. – rmaddy May 6 ’15 at 18:28

这么说,我遇到的crash也应该是deallocated object导致的了,那么,如果我能够定位到具体是哪一个deallocated object的话,那就可以进一步缩小排查的代码范围了。这时候,我应该用NSZombieEnabled选项来调试我的app了。

首先,启用Enable Zombie Objects选项。
NSZombieEnabled
我们都知道,iOS的运行时是用C和Objective C实现的,每一个对象都采用引用计数进行内存管理,如果某一个对象的引用计数为0了,那么该对象的内存就会被释放掉 ,而我们启用了Zombie Objects后,一个对象引用计数为0的时候,这个对象会被转换为一个Zombie对象,而不是直接释放掉,如果你向一个zombie对象发消息,那么调试器就会捕捉到这个操作,你就可以进一步定位具体错误了。

接下来,我用Enable Zombie Objects选项启动并调试app并且很容易地获取到了这条有效的调试信息。

[UIScrollView retain]: message sent to deallocated instance 0x12eda1e00

从这条调试信息来看,问题与我的代码中声明的某一个UIScrollView对象有关,可是我怎么知道这是哪一个scroll view对象呢,这个scroll view对象在我的代码中用什么属性,什么变量去引用的呢?

我仔细观察了对应的源文件,我发现只有三个View Controller的属性的类型是UIScrollView,为此,我在dealloc的方法中设置了断点,在断点上获取对应的类型为UIScrollView的属性的指针,并记录下来,然后将记录下来的指针与Zombie对象的指针进行对比,经过多次反复的重现这个crash,我终于发现原来crash与是一个名为scrollView1的属性有关,每一次发生crash,deallocated instance一定是scrollView1这个属性的指针,无一例外。

我经过仔细对比,反复代码审查,终于发现了一处潜在的问题,那就是,我会在viewDidLoad之后,viewDidAppear之前就对scrollView执行Scroll操作,我总感觉有些不妥,似乎应该在viewDidAppear之后才能对scrollView进行scroll的。

我对代码进行了改动,凡是要对scrollView执行scroll动作的代码(即调用了UIScrollView setContentOffset方法),都一律延后到viewDidAppear之后进行。

修改代码后,我再次进行测试,这下子发现app不再崩溃了,运行十分正常,问题解决。

结论,千万不要在viewDidAppear之前,或者说,更准确的说,千万不要在当前的ViewController的view.window属性为nil的时候对UIScrollView进行setContentOffset操作,否则,你会发现你遇到了crash,并且crash的位置是UIKit`+[UIViewController _viewControllerForFullScreenPresentationFromView:]:。

Xcode 8 Beta 2转换原Swift 2.2代码到Swift 3.0后报错Found an unexpected Mach-O header code: 0x72613c21

今天试用了Xcode 8 Beta 2,尝试将之前Swift 2.2的代码转换为Swift 3.转换的过程一波三折,Swift 3改动十分大。

诸如dispatch_after这些在Swift 3中都改成了DispatchQueue.main.after这种形式,其次,很多Objc的对象也改了翻译的名字,如Swift 2.2中UIScreen.mainScreen()变成了UIScreen.main()。其实这些都还好,Xcode都可以帮我自动转换好。

还有一个令人非常烦恼的就是objc代码的导入机制的变化,如果你有很多旧的代码还没有用nonnull这些关键字修饰objc的属性和方法的话,你就会发现,原来在Swift 2.2中,导入的objc属性或者方法参数是默认unwrapped的,到了Swift 3中,变成了默认不unwrapped了,而这些代码你必须手动去修改。

然而,最令我感到意外的是,到了最后,Xcode在copy swift libraries的时候,又报了错误
error: Found an unexpected Mach-O header code: 0x72613c21

用这个错误信息去找,看上去最符合的信息就是https://github.com/CocoaPods/CocoaPods/issues/5598,该issue中提到要设置ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES=NO,然而,我的所有的build settings都已经是ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES=NO了,可是Xcode还是会报错Found an unexpected Mach-O header code: 0x72613c21。最后,我只能认为是Xcode 8 Beta 2的一个bug了。

Swift 从1到2,从2到3,都完全没有考虑向后兼容(Backward Compatibility),这只能说明Swift其实连个半成品都不算,各位学习Swift可以,千万不要用在实际的工程中,否则,坑死你。

应用退出登录,数据丢失,原来是iOS系统的bug,NSUserDefaults内容随机消失

最近,我突然发现用着用着很多应用就要我重新登录了,我自己的app在调试的时候,也经常发现原来保存的登录状态也消失了。经过调查,我发现,原因在于保存在NSUserDefaults中的登录信息没了,而保存在keychain和文件中的信息还有。

经过查找,我发现了如下信息,原来是iOS 9.3的一个bug,应用用于保存信息的NSUserDefaults的内容会随机消失。
https://forums.developer.apple.com/thread/44264

iOS 9.3.1 NSUserDefaults Wiped Bug?

elementarteilchen
May 6, 2016 5:11 AM
(in response to staminajim_sg)
Yes. It seems that the iOS just “forgets” to load the data sometimes. If this happens in my own App while debugging, I can easily kill the App and prevent that it will change the userdefaults, and the next time I lanuch it again, the settings are usually loaded just fine. The big problem is that when you are not debugging your own Apps, you do not have a chance to kill an App before it save any settings, and when an App does this, the old settings are overwritten and lost forever.
(给staminajim_sg的回复)
是的。这看起来是iOS似乎有时候忘记及时加载数据了。我在调试的时候常常发现这个现象发生,在这种情况下,我可以用杀掉app的方式防止我的app执行对NSUserDefaults的修改操作,这样子,下一次我再启动app,原来的设置都还在而且也会正确加载。最大的问题是,如果你不是在调试你的App,你就没有办法像我一样杀死App,这就导致App会执行写操作,原有的设置就会被覆盖掉了。

Rygen
May 27, 2016 6:10 AM
(in response to elementarteilchen)
Also facing the same issue since 9.3, and now it persists on 9.3.2.
I’ve had this issue happen with a first party app as well – Weather. It lost all the saved cities, about one month ago.
Whatsapp is by far the worst in terms of data loss. You can’t avoid some of it even if you’re obsessively backing it up to iCloud every single time you use the app.
I know of one person which is not using the iPhone as a development device and is also having the same problem, so this NSUserDefaults issue is probably not related to that.
Which iPhone models are you seeing the issue on? Mine is a 5S.

I’ve submitted a bug report to Apple.
(给elementarteilchen的回复)
我从iOS 9.3版本开始就遇到这样的问题,现在已经升级到9.3.2了,问题依旧存在。
我最开始遇到这个问题,是一个月以前,在我的天气应用上,我的天气应用中保存的城市列表都消失了。
Whatsapp则是受到影响最大的,即便你经常通过iClound备份,你的数据也会丢失。
我知道一个案例,一个用户并不是将他的/她的iPhone作为开发设备,而他/她也遇到了类似的现象,这表明NSUserDefaults问题并不是因为iPhone被设置为开发设备的缘故。
大家在那些iPhone型号上遇到这个问题了?我的是iPhone 5S

ChaoticBox
Jun 19, 2016 1:06 AM
(in response to NeilFau)
I’ve been seeing this as well but figured out a workaround for my own apps: If my keys aren’t found, call resetStandardUserDefaults and try again. It shouldn’t hurt to repeat that a few times before bailing and assuming it’s a first-run, but so far it has always worked after the first reset.
(给NeilFau的回复)
我也遇到了相同的问题,不过我找到了一个变通的方法。如果我的应用在启动的时候发现某个键值对应的值不存在,那么我会首先调用resetStandardUserDefaults,然后接着再试着获取该值。重复几次resetStandardUserDefaults的操作,如果这样仍然无法取到相应的值,那么可以认定这是首次运行。这样的操作并不繁琐复杂,但是截至到目前位置,我发现这个方案是有效的。

alexfit
Jul 6, 2016 12:30 AM
(in response to NeilFau)
This has been happening to me for the last few months. By FAR, the most distressing bug I’ve encountered on iOS as a user. Can’t use What’s App, as I’m constantly losing chat history. Have to log in again to Evernote, Instagram. Settings and tips for many apps reset and I’m sent back to onboarding screens.

For the first time, I’ve lost all trust in my iPhone. A thought of getting a “reliable” Android phone crossed my mind…

I hope this is getting fixed, soon.
(给NeilFau的回复)
这个问题在最近几个月一直困扰着我。到目前为止,这是我见过的最令人烦恼的一个iOS的bug。我不能用Whatsapp,因为我经常会丢失聊天记录。我不得不重复登录Evernote,Instagram。许多应用的设置和提示都被重置了,这导致我每次启动都见到欢迎界面。

这是我首次丧失了对iPhone的信任,我已经开始考虑买一个可靠的安卓手机了。

希望这个bug能够被尽快修复。