代码之家  ›  专栏  ›  技术社区  ›  Reinhard Männer

在tableView更新期间对数据源的线程安全访问

  •  1
  • Reinhard Männer  · 技术社区  · 6 年前

    我的应用程序使用 tableView
    当数据源更改时 表格视图 更新,而不是重新加载,使用

    tableView.performBatchUpdates({ 
        tableView.deleteRows(at: deletions, with: .automatic)
        tableView.insertRows(at: insertions, with: .automatic)
        for (from, to) in movements {
            tableView.moveRow(at: from, to: to)
        }
    }, completion: { (finished) in
        if !finished {
            self.tableView.reloadData()
        } else {
            // some cleanup code
        }
        completion(finished)
    }  
    

    completion(finished) 是一些完成块。

    表格视图

    我可以想象我得到了一个 NSCondition tableView.performBatchUpdates ,并在完成块中释放它,当然,对于对数据源的每一个其他读写访问,也可以如建议的那样 here . 但是大多数关于SO的帖子建议不要使用这种低级同步机制,而是使用GCD。

    One suggestion 使用线程安全 SynchronizedArray 使用带有同步读取和异步屏障写入的并发队列,这允许并发读取。但我不知道我怎么能在 当我使用GCD时。

    这个问题有标准的解决办法吗?

    1 回复  |  直到 6 年前
        1
  •  0
  •   Reinhard Männer    6 年前

    编辑:

    我对下面提供的解决方案不满意,因为它不允许并发读取。
    WriteLockableSynchronizedArray .

    它的基础是 Basem Emara's SynchronizedArray (再次感谢,Basem),即它确实允许并发读取,并且具有以下特性:

    • 它有一个方便的初始化器,可以使 从一个 Array array 返回底层数组。
    • 它有两个功能 lockArray() unlockArray()
    • 它采用 Sequence 协议,所以 for element in writeLockableSynchronizedArray {} .
    • 如果 写入按顺序执行。

    以下是新的解决方案(推荐):

    import Foundation
    
    /// A lockable, thread-safe array.
    // It is a modification of Basem Emara's SynchronizedArray, see <http://basememara.com/creating-thread-safe-arrays-in-swift/>
    // It provides concurrent reads and serialized writes. A write is only executed after all reads have been completed.
    // If the LockableSynchronizedArray is locked, new writes are deferred until it is unlocked again, while new reads are executed normally.
    public class WriteLockableSynchronizedArray<Element> {
    
        typealias WriteOperation = ()->Void
    
        fileprivate var lockCounter = 0
        fileprivate let queue = DispatchQueue(label: "com.zeh4soft.WriteLockableSynchronizedArray", attributes: .concurrent)
        fileprivate var internalArray = [Element]()
        fileprivate var deferredWriteOperations: [WriteOperation] = []
    
        /// The internal array of the elements
        var array: [Element]? {
            var result: [Element]?
            queue.sync { result = self.internalArray }
            return result
        }
    }
    
    // MARK: - Properties
    public extension WriteLockableSynchronizedArray {
    
        /// The first element of the collection.
        var first: Element? {
            var result: Element?
            queue.sync { result = self.internalArray.first }
            return result
        }
    
        /// The last element of the collection.
        var last: Element? {
            var result: Element?
            queue.sync { result = self.internalArray.last }
            return result
        }
    
        /// The number of elements in the array.
        var count: Int {
            var result = 0
            queue.sync { result = self.internalArray.count }
            return result
        }
    
        /// A Boolean value indicating whether the collection is empty.
        var isEmpty: Bool {
            var result = false
            queue.sync { result = self.internalArray.isEmpty }
            return result
        }
    
        /// A textual representation of the array and its elements.
        var description: String {
            var result = ""
            queue.sync { result = self.internalArray.description }
            return result
        }
    }
    
    // MARK: - Init
    public extension WriteLockableSynchronizedArray {
        convenience init(with array: [Element]) {
            self.init()
            self.internalArray = array
        }
    }
    
    // MARK: - Lock - Unlock
    public extension WriteLockableSynchronizedArray {
        /// Locks the array for writes. Must be unlocked by unlockArray()
        func lockArray() {
            queue.async(flags: .barrier) {
                self.lockCounter += 1
            }
        }
    
        /// Unlocks the array after it has been locked by lockArray()
        func unlockArray() {
            queue.sync(flags: .barrier) {
                if self.lockCounter > 0 { 
                    self.lockCounter -= 1 
                }
                if self.lockCounter == 0 {
                    while self.deferredWriteOperations.count > 0 {
                        let nextOp = self.deferredWriteOperations.remove(at: 0)
                        self.queue.async(flags: .barrier) { nextOp() }
                        print("Enqueued deferred write op")
                    }
                }
            }
        }
    }
    
    // MARK: - Immutable
    public extension WriteLockableSynchronizedArray {
        /// Returns the first element of the sequence that satisfies the given predicate or nil if no such element is found.
        ///
        /// - Parameter predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element is a match.
        /// - Returns: The first match or nil if there was no match.
        func first(where predicate: (Element) -> Bool) -> Element? {
            var result: Element?
            queue.sync { result = self.internalArray.first(where: predicate) }
            return result
        }
    
        /// Returns an array containing, in order, the elements of the sequence that satisfy the given predicate.
        ///
        /// - Parameter isIncluded: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element should be included in the returned array.
        /// - Returns: An array of the elements that includeElement allowed.
        func filter(_ isIncluded: (Element) -> Bool) -> [Element] {
            var result = [Element]()
            queue.sync { result = self.internalArray.filter(isIncluded) }
            return result
        }
    
        /// Returns the first index in which an element of the collection satisfies the given predicate.
        ///
        /// - Parameter predicate: A closure that takes an element as its argument and returns a Boolean value that indicates whether the passed element represents a match.
        /// - Returns: The index of the first element for which predicate returns true. If no elements in the collection satisfy the given predicate, returns nil.
        func index(where predicate: (Element) -> Bool) -> Int? {
            var result: Int?
            queue.sync { result = self.internalArray.index(where: predicate) }
            return result
        }
    
        /// Returns the elements of the collection, sorted using the given predicate as the comparison between elements.
        ///
        /// - Parameter areInIncreasingOrder: A predicate that returns true if its first argument should be ordered before its second argument; otherwise, false.
        /// - Returns: A sorted array of the collection’s elements.
        func sorted(by areInIncreasingOrder: (Element, Element) -> Bool) -> [Element] {
            var result = [Element]()
            queue.sync { result = self.internalArray.sorted(by: areInIncreasingOrder) }
            return result
        }
    
        /// Returns an array containing the non-nil results of calling the given transformation with each element of this sequence.
        ///
        /// - Parameter transform: A closure that accepts an element of this sequence as its argument and returns an optional value.
        /// - Returns: An array of the non-nil results of calling transform with each element of the sequence.
        func flatMap<ElementOfResult>(_ transform: (Element) -> ElementOfResult?) -> [ElementOfResult] {
            var result = [ElementOfResult]()
            queue.sync { result = self.internalArray.compactMap(transform) }
            return result
        }
    
        /// Calls the given closure on each element in the sequence in the same order as a for-in loop.
        ///
        /// - Parameter body: A closure that takes an element of the sequence as a parameter.
        func forEach(_ body: (Element) -> Void) {
            queue.sync { self.internalArray.forEach(body) }
        }
    
        /// Returns a Boolean value indicating whether the sequence contains an element that satisfies the given predicate.
        ///
        /// - Parameter predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value that indicates whether the passed element represents a match.
        /// - Returns: true if the sequence contains an element that satisfies predicate; otherwise, false.
        func contains(where predicate: (Element) -> Bool) -> Bool {
            var result = false
            queue.sync { result = self.internalArray.contains(where: predicate) }
            return result
        }
    }
    
    // MARK: - Mutable
    public extension WriteLockableSynchronizedArray {
    
        /// Adds a new element at the end of the array.
        ///
        /// - Parameter element: The element to append to the array.
        func append( _ element: Element) {
            let op = { self.internalArray.append(element) }
            handleWriteOperation(op)
        }
    
        /// Adds a new element at the end of the array.
        ///
        /// - Parameter element: The element to append to the array.
        func append( _ elements: [Element]) {
            let op = { self.internalArray += elements }
            handleWriteOperation(op)
        }
    
        /// Inserts a new element at the specified position.
        ///
        /// - Parameters:
        ///   - element: The new element to insert into the array.
        ///   - index: The position at which to insert the new element.
        func insert( _ element: Element, at index: Int) {
            let op = { self.internalArray.insert(element, at: index) }
            handleWriteOperation(op)
        }
    
        /// Removes and returns the element at the specified position.
        ///
        /// - Parameters:
        ///   - index: The position of the element to remove.
        ///   - completion: The handler with the removed element.
        func remove(at index: Int, completion: ((Element) -> Void)? = nil) {
            let op = {
                let element = self.internalArray.remove(at: index)
                DispatchQueue.main.async {
                    completion?(element)
                }
            }
            handleWriteOperation(op)
        }
    
        /// Removes and returns the element at the specified position.
        ///
        /// - Parameters:
        ///   - predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element is a match.
        ///   - completion: The handler with the removed element.
        func remove(where predicate: @escaping (Element) -> Bool, completion: ((Element) -> Void)? = nil) {
            let op = {
                guard let index = self.internalArray.index(where: predicate) else { return }
                let element = self.internalArray.remove(at: index)
                DispatchQueue.main.async {
                    completion?(element)
                }
            }
            handleWriteOperation(op)
        }
    
        /// Removes all elements from the array.
        ///
        /// - Parameter completion: The handler with the removed elements.
        func removeAll(completion: (([Element]) -> Void)? = nil) {
            let op = {
                let elements = self.internalArray
                self.internalArray.removeAll()
                DispatchQueue.main.async {
                    completion?(elements)
                }
            }
            handleWriteOperation(op)
        }
    }
    
    public extension WriteLockableSynchronizedArray {
    
        /// Accesses the element at the specified position if it exists.
        ///
        /// - Parameter index: The position of the element to access.
        /// - Returns: optional element if it exists.
        subscript(index: Int) -> Element? {
            get {
                var result: Element?
    
                queue.sync {
                    guard self.internalArray.startIndex..<self.internalArray.endIndex ~= index else { return }
                    result = self.internalArray[index]
                }
    
                return result
            }
            set {
                guard let newValue = newValue else { return }
    
                let op = { self.internalArray[index] = newValue }
                handleWriteOperation(op)
            }
        }
    }
    
    
    // MARK: - Equatable
    public extension WriteLockableSynchronizedArray where Element: Equatable {
    
        /// Returns a Boolean value indicating whether the sequence contains the given element.
        ///
        /// - Parameter element: The element to find in the sequence.
        /// - Returns: true if the element was found in the sequence; otherwise, false.
        func contains(_ element: Element) -> Bool {
            var result = false
            queue.sync { result = self.internalArray.contains(element) }
            return result
        }
    }
    
    // MARK: - Infix operators
    public extension WriteLockableSynchronizedArray {
    
        static func +=(left: inout WriteLockableSynchronizedArray, right: Element) {
            left.append(right)
        }
    
        static func +=(left: inout WriteLockableSynchronizedArray, right: [Element]) {
            left.append(right)
        }
    }
    
    // MARK: - Protocol Sequence
    extension WriteLockableSynchronizedArray: Sequence {
    
        public func makeIterator() -> Iterator {
            return Iterator(self.array)
        }
    
        public struct Iterator: IteratorProtocol {
            private var index: Int
            private var arr: [Element]?
    
            init(_ array: [Element]?) {
                self.arr = array
                index = 0
            }
    
            mutating public func next() -> Element? {
                guard let arr = self.arr, arr.count > index else { return nil }
                let returnValue = arr[index]
                index += 1
                return returnValue
            }
        }
    }
    
    // MARK: - Private helper
    fileprivate extension WriteLockableSynchronizedArray {
        func handleWriteOperation(_ op: @escaping WriteLockableSynchronizedArray.WriteOperation) {
            queue.sync { 
                if self.lockCounter > 0 {
                    self.deferredWriteOperations.append { op() }
                } else {
                    queue.async(flags: .barrier) {
                        op()
                    }
                }
            }
        }
    
    }
    

    这是我以前的解决方案(不再推荐):

    • 我实现了一个类 LockableArray SynchronizedArray 使用递归锁同步对数组的访问。
    • 我实现了一个方便的初始化器,它使 可锁定阵列 从一个 阵列 阵列 .
    • 我定义了两个函数 锁阵列() 解锁数组() 可以在批处理tableView操作之前和之后调用的。
    • 顺序 可锁定阵列 可用于如下语句中 for element in lockablaArray {} .

    同步阵列 .

    以下是实现方法:

    import Foundation
    
    /// A lockable, thread-safe array.
    // It is a modification of Basem Emara's SynchronizedArray, see <http://basememara.com/creating-thread-safe-arrays-in-swift/>
    // It does not use dispatch queues, but a recursive lock, so that multiple array operations can be locked as a group.
    // Additions to Basem Emara's implementation:
    // - A convenience initializer make a LockableArray from an Array, and a readonly property allows to acces the underlying Array.
    // - Protocol Sequence is adopted, so that statements like "for .. in .." can be used.
    
    public class LockableArray<Element> {
        fileprivate var lock = NSRecursiveLock() // Must be recursive, so that batch accesses can be locked together
        fileprivate var privateArray = [Element]()
    
        /// The internal array of the elements
        var array: [Element]? {
            let result: [Element]
            lock.lock()
            result = privateArray
            lock.unlock()
            return result
        }
    }
    
    // MARK: - Properties
    public extension LockableArray {
    
        /// The first element of the collection.
        var first: Element? {
            var result: Element?
            lock.lock()
            result = self.privateArray.first
            lock.unlock()
            return result
        }
    
        /// The last element of the collection.
        var last: Element? {
            var result: Element?
            lock.lock()
            result = self.privateArray.last
            lock.unlock()
            return result
        }
    
        /// The number of elements in the array.
        var count: Int {
            var result = 0
            lock.lock()
            result = self.privateArray.count
            lock.unlock()
            return result
        }
    
        /// A Boolean value indicating whether the collection is empty.
        var isEmpty: Bool {
            var result = false
            lock.lock()
            result = self.privateArray.isEmpty
            lock.unlock()
            return result
        }
    
        /// A textual representation of the array and its elements.
        var description: String {
            var result = ""
            lock.lock()
            result = self.privateArray.description
            lock.unlock()
            return result
        }
    }
    
    // MARK: - Init
    public extension LockableArray {
        convenience init(with array: [Element]) {
            self.init()
            self.privateArray = array
        }
    }
    
    // MARK: - Lock - Unlock
    public extension LockableArray {
        /// Locks the array for multiple writes. Must be unlocked by unlockArray()
        func lockArray() {
            lock.lock()
        }
    
        /// Unlocks the array after it has been locked by lockArray()
        func unlockArray() {
            lock.unlock()
        }
    }
    
    // MARK: - Immutable
    public extension LockableArray {
    
        /// Returns the first element of the sequence that satisfies the given predicate or nil if no such element is found.
        ///
        /// - Parameter predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element is a match.
        /// - Returns: The first match or nil if there was no match.
        func first(where predicate: (Element) -> Bool) -> Element? {
            var result: Element?
            lock.lock()
            result = self.privateArray.first(where: predicate)
            lock.unlock()
            return result
        }
    
        /// Returns an array containing, in order, the elements of the sequence that satisfy the given predicate.
        ///
        /// - Parameter isIncluded: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element should be included in the returned array.
        /// - Returns: An array of the elements that includeElement allowed.
        func filter(_ isIncluded: (Element) -> Bool) -> [Element] {
            var result = [Element]()
            lock.lock()
            result = self.privateArray.filter(isIncluded)
            lock.unlock()
            return result
        }
    
        /// Returns the first index in which an element of the collection satisfies the given predicate.
        ///
        /// - Parameter predicate: A closure that takes an element as its argument and returns a Boolean value that indicates whether the passed element represents a match.
        /// - Returns: The index of the first element for which predicate returns true. If no elements in the collection satisfy the given predicate, returns nil.
        func index(where predicate: (Element) -> Bool) -> Int? {
            var result: Int?
            lock.lock()
            result = self.privateArray.index(where: predicate)
            lock.unlock()
            return result
        }
    
        /// Returns the elements of the collection, sorted using the given predicate as the comparison between elements.
        ///
        /// - Parameter areInIncreasingOrder: A predicate that returns true if its first argument should be ordered before its second argument; otherwise, false.
        /// - Returns: A sorted array of the collection’s elements.
        func sorted(by areInIncreasingOrder: (Element, Element) -> Bool) -> [Element] {
            var result = [Element]()
            lock.lock()
            result = self.privateArray.sorted(by: areInIncreasingOrder)
            lock.unlock()
            return result
        }
    
        /// Returns an array containing the non-nil results of calling the given transformation with each element of this sequence.
        ///
        /// - Parameter transform: A closure that accepts an element of this sequence as its argument and returns an optional value.
        /// - Returns: An array of the non-nil results of calling transform with each element of the sequence.
        func compactMap<ElementOfResult>(_ transform: (Element) -> ElementOfResult?) -> [ElementOfResult] {
            var result = [ElementOfResult]()
            lock.lock()
            result = self.privateArray.compactMap(transform)
            lock.unlock()
            return result
        }
    
        /// Returns an array containing the non-nil results of calling the given transformation with each element of this sequence.
        ///
        /// - Parameter transform: A closure that accepts an element of this sequence as its argument and returns an optional value.
        /// - Returns: An array of the non-nil results of calling transform with each element of the sequence.
        func map<ElementOfResult>(_ transform: (Element) -> ElementOfResult) -> [ElementOfResult] {
            var result = [ElementOfResult]()
            lock.lock()
            result = self.privateArray.map(transform)
            lock.unlock()
            return result
        }
    
        /// Calls the given closure on each element in the sequence in the same order as a for-in loop.
        ///
        /// - Parameter body: A closure that takes an element of the sequence as a parameter.
        func forEach(_ body: (Element) -> Void) {
            lock.lock()
            self.privateArray.forEach(body)
            lock.unlock()
        }
    
        /// Returns a Boolean value indicating whether the sequence contains an element that satisfies the given predicate.
        ///
        /// - Parameter predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value that indicates whether the passed element represents a match.
        /// - Returns: true if the sequence contains an element that satisfies predicate; otherwise, false.
        func contains(where predicate: (Element) -> Bool) -> Bool {
            var result = false
            lock.lock()
            result = self.privateArray.contains(where: predicate)
            lock.unlock()
            return result
        }
    }
    
    // MARK: - Mutable
    public extension LockableArray {
    
        /// Adds a new element at the end of the array.
        ///
        /// - Parameter element: The element to append to the array.
        func append( _ element: Element) {
            lock.lock()
            self.privateArray.append(element)
            lock.unlock()
        }
    
        /// Adds a new element at the end of the array.
        ///
        /// - Parameter element: The element to append to the array.
        func append( _ elements: [Element]) {
            lock.lock()
            self.privateArray += elements
            lock.unlock()
        }
    
        /// Inserts a new element at the specified position.
        ///
        /// - Parameters:
        ///   - element: The new element to insert into the array.
        ///   - index: The position at which to insert the new element.
        func insert( _ element: Element, at index: Int) {
            lock.lock()
            self.privateArray.insert(element, at: index)
            lock.unlock()
        }
    
        /// Removes and returns the element at the specified position.
        ///
        /// - Parameters:
        ///   - index: The position of the element to remove.
        ///   - completion: The handler with the removed element.
        func remove(at index: Int, completion: ((Element) -> Void)? = nil) {
            lock.lock()
            let element = self.privateArray.remove(at: index)
            DispatchQueue.main.async {
                completion?(element)
            }
            lock.unlock()
        }
    
        /// Removes and returns the element at the specified position.
        ///
        /// - Parameters:
        ///   - predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element is a match.
        ///   - completion: The handler with the removed element.
        func remove(where predicate: @escaping (Element) -> Bool, completion: ((Element) -> Void)? = nil) {
            lock.lock()
            guard let index = self.privateArray.index(where: predicate) else { return }
            let element = self.privateArray.remove(at: index)
            DispatchQueue.main.async {
                completion?(element)
            }
            lock.unlock()
        }
    
        /// Removes all elements from the array.
        ///
        /// - Parameter completion: The handler with the removed elements.
        func removeAll(completion: (([Element]) -> Void)? = nil) {
            lock.lock()
            let elements = self.privateArray
            self.privateArray.removeAll()
            DispatchQueue.main.async {
                completion?(elements)
            }
            lock.unlock()
        }
    }
    
    public extension LockableArray {
    
        /// Accesses the element at the specified position if it exists.
        ///
        /// - Parameter index: The position of the element to access.
        /// - Returns: optional element if it exists.
        subscript(index: Int) -> Element? {
            get {
                var result: Element?
                lock.lock()
                guard self.privateArray.startIndex ..< self.privateArray.endIndex ~= index else { return nil }
                result = self.privateArray[index]
                lock.unlock()
                return result
            }
            set {
                guard let newValue = newValue else { return }
                lock.lock()
                self.privateArray[index] = newValue
                lock.unlock()
            }
        }
    }
    
    // MARK: - Equatable
    public extension LockableArray where Element: Equatable {
    
        /// Returns a Boolean value indicating whether the sequence contains the given element.
        ///
        /// - Parameter element: The element to find in the sequence.
        /// - Returns: true if the element was found in the sequence; otherwise, false.
        func contains(_ element: Element) -> Bool {
            var result = false
            lock.lock()
            result = self.privateArray.contains(element)
            lock.unlock()
            return result
        }
    }
    
    // MARK: - Infix operators
    public extension LockableArray {
    
        static func +=(left: inout LockableArray, right: Element) {
            left.append(right)
        }
    
        static func +=(left: inout LockableArray, right: [Element]) {
            left.append(right)
        }
    }
    
    // MARK: - Protocol Sequence
    extension LockableArray: Sequence {
    
        public func makeIterator() -> Iterator {
            return Iterator(self.array)
        }
    
        public struct Iterator: IteratorProtocol {
            private var index: Int
            private var arr: [Element]?
    
            init(_ array: [Element]?) {
                self.arr = array
                index = 0
            }
    
            mutating public func next() -> Element? {
                guard let arr = self.arr, arr.count > index else { return nil }
                let returnValue = arr[index]
                index += 1
                return returnValue
            }
        }
    }