Swift 3面向协议的编程导致随机的Sigbus崩溃



我负责完整的Swift 3应用程序,并且经常发生的一次崩溃是我完全无法理解的SIGBUS信号:

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

正如Apple在WWDC视频中所描述的那样,其背后的逻辑是重复使用滚动视图的逻辑(找不到年度和视频...(:

pageView是一个实现reusableView和索引的类:

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
    }
}

可reusableview和索引是这样定义的协议:

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() {}
}

re usableContentView是管理插入或重复使用的视图的视图。它是根据包含视图类型实现的:

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)
                }
            }
        }
    }
}

DocumentViewerViewController.reuseOrInsertPages()调用,由scrollviewDidScroll委托触发。

在这里我的SIGBUS信号可以什么?我用来使协议函数可选的func prepareForReuse() {}的默认实现吗?还有其他想法吗?

当然,这次崩溃是完全随机的,我无法复制它。我只是从该应用程序的产品版本中收到有关它的崩溃报告。感谢您的帮助!

对我来说,pageview.prepareforreuse((中似乎出现了问题。我不知道属性,但从Preparforreuse功能中,您似乎正在访问@iboutlets的属性:

bridge = nil
imageView.image = nil
displaying = nil
pageNumber = 0
zoomView.prepareForReuse()

尝试访问它们时imageViewzoomView是零吗?如果是这样,这可能是最简单的解决方案:

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()
}

再次,我不确定您的页面浏览量的实现详细信息,我只是猜到这一点,因为看起来您正在从笔尖实例化它,因此我猜测您正在使用例如@IBOutlet weak var imageView: UIImageView!

如果出于任何原因,此imageView变为零,访问它将崩溃您的应用。

相关内容

  • 没有找到相关文章