I am responsible of a complete Swift 3 application and one of the crashes that occurs regularly is a SIGBUS
signal that I can't understand at all:
Thread 0 Crashed:
0 libswiftCore.dylib 0x00000001009b4ac8 0x1007b8000 +2083528
1 LeadingBoards @objc PageView.prepareForReuse() -> () (in LeadingBoards) (PageView.swift:0) +1114196
2 LeadingBoards specialized ReusableContentView<A where ...>.reuseOrInsertView(first : Int, last : Int) -> () (in LeadingBoards) (ReusableView.swift:101) +1730152
3 LeadingBoards DocumentViewerViewController.reuseOrInsertPages() -> () (in LeadingBoards) (DocumentViewerViewController.swift:0) +1036080
4 LeadingBoards specialized DocumentViewerViewController.scrollViewDidScroll(UIScrollView) -> () (in LeadingBoards) (DocumentViewerViewController.swift:652) +1089744
5 LeadingBoards @objc DocumentViewerViewController.scrollViewDidScroll(UIScrollView) -> () (in LeadingBoards) +1028252
6 UIKit 0x000000018c2a68d4 0x18bf85000 +3283156
7 UIKit 0x000000018bfb2c08 0x18bf85000 +187400
8 UIKit 0x000000018c143e5c 0x18bf85000 +1830492
9 UIKit 0x000000018c143b4c 0x18bf85000 +1829708
10 QuartzCore 0x00000001890755dc 0x18906b000 +42460
11 QuartzCore 0x000000018907548c 0x18906b000 +42124
12 IOKit 0x00000001860d7b9c 0x1860d2000 +23452
13 CoreFoundation 0x0000000185e01960 0x185d3e000 +801120
14 CoreFoundation 0x0000000185e19ae4 0x185d3e000 +899812
15 CoreFoundation 0x0000000185e19284 0x185d3e000 +897668
16 CoreFoundation 0x0000000185e16d98 0x185d3e000 +888216
17 CoreFoundation 0x0000000185d46da4 0x185d3e000 +36260
18 GraphicsServices 0x00000001877b0074 0x1877a4000 +49268
19 UIKit 0x000000018bffa058 0x18bf85000 +479320
20 LeadingBoards main (in LeadingBoards) (AppDelegate.swift:13) +77204
21 libdyld.dylib 0x0000000184d5559c 0x184d51000 +17820
The logic behind that is the logic for reusing views in a scrollview, as described by Apple in a WWDC video (can't find the year and the video...):
PageView is a class that implement ReusableView and Indexed:
class PageView: UIView {
enum Errors: Error {
case badConfiguration
case noImage
}
enum Resolution: String {
case high
case low
static var emptyGeneratingTracker: [PageView.Resolution: Set<String>] {
return [.high:Set(),
.low:Set()]
}
/// SHOULD NOT BE 0
var quality: CGFloat {
switch self {
case .high:
return 1
case .low:
return 0.3
}
}
var JPEGQuality: CGFloat {
switch self {
case .high:
return 0.8
case .low:
return 0.25
}
}
var atomicWrite: Bool {
switch self {
case .high:
return false
case .low:
return true
}
}
var interpolationQuality: CGInterpolationQuality {
switch self {
case .high:
return .high
case .low:
return .low
}
}
var dispatchQueue: OperationQueue {
switch self {
case .high:
return DocumentBridge.highResOperationQueue
case .low:
return DocumentBridge.lowResOperationQueue
}
}
}
@IBOutlet weak var imageView: UIImageView!
// Loading
@IBOutlet weak var loadingStackView: UIStackView!
@IBOutlet weak var pageNumberLabel: UILabel!
// Error
@IBOutlet weak var errorStackView: UIStackView!
// Zoom
@IBOutlet weak var zoomView: PageZoomView!
fileprivate weak var bridge: DocumentBridge?
var displaying: Resolution?
var pageNumber = 0
override func layoutSubviews() {
super.layoutSubviews()
refreshImageIfNeeded()
}
func configure(_ pageNumber: Int, zooming: Bool, bridge: DocumentBridge) throws {
if pageNumber > 0 && pageNumber <= bridge.numberOfPages {
self.bridge = bridge
self.pageNumber = pageNumber
self.zoomView.configure(bridge: bridge, pageNumber: pageNumber)
} else {
throw Errors.badConfiguration
}
NotificationCenter.default.addObserver(self, selector: #selector(self.pageRendered(_:)), name: .pageRendered, object: bridge)
NotificationCenter.default.addObserver(self, selector: #selector(self.pageFailedRendering(_:)), name: .pageFailedRendering, object: bridge)
pageNumberLabel.text = "PAGE".localized + " \(pageNumber)"
if displaying == nil {
loadingStackView.isHidden = false
errorStackView.isHidden = true
}
if displaying != .high {
refreshImage()
}
if zooming {
startZooming()
} else {
stopZooming()
}
}
fileprivate func isNotificationRelated(notification: Notification) -> Bool {
guard let userInfo = notification.userInfo else {
return false
}
guard pageNumber == userInfo[DocumentBridge.PageNotificationKey.PageNumber.rawValue] as? Int else {
return false
}
guard Int(round(bounds.width)) == userInfo[DocumentBridge.PageNotificationKey.Width.rawValue] as? Int else {
return false
}
guard userInfo[DocumentBridge.PageNotificationKey.Notes.rawValue] as? Bool == false else {
return false
}
return true
}
func pageRendered(_ notification: Notification) {
guard isNotificationRelated(notification: notification) else {
return
}
if displaying == nil || (displaying == .low && notification.userInfo?[DocumentBridge.PageNotificationKey.Resolution.rawValue] as? String == Resolution.high.rawValue) {
refreshImage()
}
}
func pageFailedRendering(_ notification: Notification) {
guard isNotificationRelated(notification: notification) else {
return
}
if displaying == nil {
imageView.image = nil
loadingStackView.isHidden = true
errorStackView.isHidden = false
}
}
func refreshImageIfNeeded() {
if displaying != .high {
refreshImage()
}
}
fileprivate func refreshImage() {
let pageNumber = self.pageNumber
let width = Int(round(bounds.width))
DispatchQueue.global(qos: .userInitiated).async(execute: { [weak self] () in
do {
try self?.setImage(pageNumber, width: width, resolution: .high)
} catch {
_ = try? self?.setImage(pageNumber, width: width, resolution: .low)
}
})
}
func setImage(_ pageNumber: Int, width: Int, resolution: Resolution) throws {
if let image = try self.bridge?.getImage(page: pageNumber, width: width, resolution: resolution) {
DispatchQueue.main.async(execute: { [weak self] () in
if pageNumber == self?.pageNumber {
self?.imageView?.image = image
self?.displaying = resolution
self?.loadingStackView.isHidden = true
self?.errorStackView.isHidden = true
}
})
} else {
throw Errors.noImage
}
}
}
extension PageView: ReusableView, Indexed {
static func instanciate() -> PageView {
return UINib(nibName: "PageView", bundle: nil).instantiate(withOwner: nil, options: nil).first as! PageView
}
var index: Int {
return pageNumber
}
func hasBeenAddedToSuperview() { }
func willBeRemovedFromSuperview() { }
func prepareForReuse() {
NotificationCenter.default.removeObserver(self, name: .pageRendered, object: nil)
NotificationCenter.default.removeObserver(self, name: .pageFailedRendering, object: nil)
bridge = nil
imageView?.image = nil
displaying = nil
pageNumber = 0
zoomView?.prepareForReuse()
}
func prepareForRelease() { }
}
// MARK: - Zoom
extension PageView {
func startZooming() {
bringSubview(toFront: zoomView)
zoomView.isHidden = false
setNeedsDisplay()
}
func stopZooming() {
zoomView.isHidden = true
}
}
where ReusableView and Indexed are protocols defined that way :
protocol Indexed {
var index: Int { get }
}
protocol ReusableView {
associatedtype A
static func instanciate() -> A
func hasBeenAddedToSuperview()
func willBeRemovedFromSuperview()
func prepareForReuse()
func prepareForRelease()
}
// Make some func optionals
extension ReusableView {
func hasBeenAddedToSuperview() {}
func willBeRemovedFromSuperview() {}
func prepareForReuse() {}
func prepareForRelease() {}
}
ReusableContentView is a view that manage the view that are inserted, or reused. It's implemented depending of the containing view type :
class ReusableContentView<T: ReusableView>: UIView where T: UIView {
var visible = Set<T>()
var reusable = Set<T>()
...
}
extension ReusableContentView where T: Indexed {
/// To insert view using a range of ids
func reuseOrInsertView(first: Int, last: Int) {
// Removing no longer needed views
for view in visible {
if view.index < first || view.index > last {
reusable.insert(view)
view.willBeRemovedFromSuperview()
view.removeFromSuperview()
view.prepareForReuse()
}
}
// Removing reusable pages from visible pages array
visible.subtract(reusable)
// Add the missing views
for index in first...last {
if !visible.map({ $0.index }).contains(index) {
let view = dequeueReusableView() ?? T.instanciate() as! T // Getting a new page, dequeued or initialized
if configureViewWithIndex?(view, index) == true {
addSubview(view)
view.hasBeenAddedToSuperview()
visible.insert(view)
}
}
}
}
}
Witch is called by DocumentViewerViewController.reuseOrInsertPages()
, triggered by scrollviewDidScroll
delegate.
What can provoque my SIGBUS
signal here? Is that the default implementation of func prepareForReuse() {}
I use to make the protocol function optional? Any other ideas?
Of course, this crash is completly random and I wasn't able to reproduice it. I just receive crash reports about it from prod version of the app. Thanks for your help !
For me it looks like something went wrong in PageView.prepareForReuse(). I'm not aware of the properties but from the prepareForReuse function it looks like your are accessing properties which maybe are @IBOutlets:
Could it be that
imageView
orzoomView
are nil when you try to access them? If so, this could be the most simplistic fix:Again, I am not sure about the implementation details of your PageView and I am only guessing this because it looks like you are instantiating it from a Nib and therefore my guess is you are using for example
@IBOutlet weak var imageView: UIImageView!
.If for whatever reason this imageView becomes nil, accessing it will crash your app.