From b0295b736b2134159fabd14ce655c7fe2fe90173 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Thu, 13 Dec 2018 11:02:23 -0500 Subject: [PATCH] Add ReverseDispatchQueue. --- .../Cells/ConversationMediaView.swift | 6 +- .../src/Util/ReverseDispatchQueue.swift | 75 +++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 SignalServiceKit/src/Util/ReverseDispatchQueue.swift diff --git a/Signal/src/ViewControllers/ConversationView/Cells/ConversationMediaView.swift b/Signal/src/ViewControllers/ConversationView/Cells/ConversationMediaView.swift index 3f428b120..05ad14a1e 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/ConversationMediaView.swift +++ b/Signal/src/ViewControllers/ConversationView/Cells/ConversationMediaView.swift @@ -478,7 +478,11 @@ public class ConversationMediaView: UIView { // views that are no longer visible, redundant loads // of media already being loaded, don't retry media // that can't be loaded, etc.). - private static let loadQueue = DispatchQueue(label: "org.signal.asyncMediaLoadQueue") + // * Do them in _reverse_ order. More recently enqueued + // loads more closely reflect the current view state. + // By processing in reverse order, we improve our + // "skip rate" of obsolete loads. + private static let loadQueue = ReverseDispatchQueue(label: "org.signal.asyncMediaLoadQueue") @objc public func loadMedia() { diff --git a/SignalServiceKit/src/Util/ReverseDispatchQueue.swift b/SignalServiceKit/src/Util/ReverseDispatchQueue.swift new file mode 100644 index 000000000..1133f4384 --- /dev/null +++ b/SignalServiceKit/src/Util/ReverseDispatchQueue.swift @@ -0,0 +1,75 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation + +// This is intended to be a drop-in replacement for DispatchQueue +// that processes its queue in reverse order. +@objc +public class ReverseDispatchQueue: NSObject { + + private static let isVerbose: Bool = false + + private let label: String + private let serialQueue: DispatchQueue + + // TODO: We could allow creation with various QOS. + @objc + public required init(label: String) { + self.label = label + serialQueue = DispatchQueue(label: label) + + super.init() + } + + public typealias WorkBlock = () -> Void + + private class Item { + let workBlock: WorkBlock + let index: UInt64 + + required init(workBlock : @escaping WorkBlock, index: UInt64) { + self.workBlock = workBlock + self.index = index + } + } + + // These properties should only be accessed on serialQueue. + private var items = [Item]() + private var indexCounter: UInt64 = 0 + + @objc + public func async(workBlock : @escaping WorkBlock) { + serialQueue.async { + self.indexCounter = self.indexCounter + 1 + let index = self.indexCounter + let item = Item(workBlock: workBlock, index: index ) + self.items.append(item) + + if ReverseDispatchQueue.isVerbose { + Logger.verbose("Enqueued[\(self.label)]: \(item.index)") + } + + self.process() + } + } + + private func process() { + serialQueue.async { + // Note that we popLast() so that we process + // the queue in the _reverse_ order from + // which it was enqueued. + guard let item = self.items.popLast() else { + // No enqueued work to do. + return + } + if ReverseDispatchQueue.isVerbose { + Logger.verbose("Processing[\(self.label)]: \(item.index)") + } + item.workBlock() + + self.process() + } + } +}