Alamofire源碼學習(十六): Alamofire中的線程安全

往期導航:

Alamofire源碼學習目錄合集swift

簡介

網絡請求都會在異步完成,因此必定會碰到線程安全問題,須要在對某些共享數據讀寫時,考慮下多線程讀寫狀況下的加解鎖,原理很簡單:只要在對線程安全敏感的數據進行讀寫操做時,加鎖,讀寫結束後解鎖便可,可是涉及到線程安全的數據不少,如何封裝是個藝術問題~安全

Alamofire中定義了一個Protected類來當作屬性包裹器使用,能夠在對包裹的數據進行操做時進行加解鎖操做,同時使用了Swift的語法特性與語法糖來讓這個類功能很是強大。markdown

線程安全離不開鎖,Alamofire框架同時支持Linux環境,所以定義了一個私有協議來表明鎖對象,擁有加鎖解鎖功能,以後對鎖進行了擴展,添加了線程安全的執行兩個閉包,一個有一個泛型返回值,一個沒有網絡

private protocol Lock {
    func lock()
    func unlock()
}

extension Lock {
    // 線程安全的執行閉包, 並把閉包的返回值返回給調用者
    func around<T>(_ closure: () -> T) -> T {
        lock(); defer { unlock() }
        return closure()
    }

    // 線程安全的執行閉包
    func around(_ closure: () -> Void) {
        lock(); defer { unlock() }
        closure()
    }
}
複製代碼

具體的鎖類型,是根據運行環境來決定的,在Linux下,用的是pthread_mutex_t鎖:多線程

#if os(Linux)
/// A `pthread_mutex_t` wrapper.
final class MutexLock: Lock {
    private var mutex: UnsafeMutablePointer<pthread_mutex_t>

    init() {
        mutex = .allocate(capacity: 1)

        var attr = pthread_mutexattr_t()
        pthread_mutexattr_init(&attr)
        pthread_mutexattr_settype(&attr, .init(PTHREAD_MUTEX_ERRORCHECK))

        let error = pthread_mutex_init(mutex, &attr)
        precondition(error == 0, "Failed to create pthread_mutex")
    }

    deinit {
        let error = pthread_mutex_destroy(mutex)
        precondition(error == 0, "Failed to destroy pthread_mutex")
    }

    fileprivate func lock() {
        let error = pthread_mutex_lock(mutex)
        precondition(error == 0, "Failed to lock pthread_mutex")
    }

    fileprivate func unlock() {
        let error = pthread_mutex_unlock(mutex)
        precondition(error == 0, "Failed to unlock pthread_mutex")
    }
}
#endif
複製代碼

在os環境下,用的是os_unfair_lock鎖:閉包

#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
/// An `os_unfair_lock` wrapper.
final class UnfairLock: Lock {
    private let unfairLock: os_unfair_lock_t

    init() {
        unfairLock = .allocate(capacity: 1)
        unfairLock.initialize(to: os_unfair_lock())
    }

    deinit {
        unfairLock.deinitialize(count: 1)
        unfairLock.deallocate()
    }

    fileprivate func lock() {
        os_unfair_lock_lock(unfairLock)
    }

    fileprivate func unlock() {
        os_unfair_lock_unlock(unfairLock)
    }
}
#endif
複製代碼

Protected類 -- 核心,持有鎖的屬性修飾器類

這個類官方的定義爲:app

A thread-safe wrapper around a value.
線程安全的值包裹器框架

@propertyWrapper
@dynamicMemberLookup
final class Protected<T> {
    
    // 鎖
    #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
    private let lock = UnfairLock()
    #elseif os(Linux)
    private let lock = MutexLock()
    #endif
    
    // 包裹的值
    private var value: T

    // 初始化方法, 供修飾屬性賦值使用
    /** 例: @Protected(3) var test: Int */
    init(_ value: T) {
        self.value = value
    }
    
    // 初始化方法, 修飾的屬性有默認初始化值時調用
    /** 例: @Protected var test: Int = 3 */
    init(wrappedValue: T) {
        value = wrappedValue
    }

    // propertyWrapper修飾必需要有的屬性, 用來保存包裹的值, 這裏定義爲計算屬性
    // 在讀寫時, 使用鎖來線程安全的執行閉包來對私有的value屬性進行操做
    var wrappedValue: T {
        get { lock.around { value } }
        set { lock.around { value = newValue } }
    }

    // projectedValue, Swift語法糖, 可使用$符號來獲取
    /** 例: class Test { @Protected var p: Int = 3 } let test = Test() let a = test.p // a類型爲Int let b = test.$p // b類型爲Protected<Int>類型, 這時候就可使用b來調用Protected類中定義的方法 b.read({ return String($0) //返回 "3" }) */
    var projectedValue: Protected<T> { self }


    //MARK: - 可供Protected調用的一些方法
    
    // 同步的讀取或者變換value值, 相似於map函數
    func read<U>(_ closure: (T) -> U) -> U {
        lock.around { closure(self.value) }
    }

    // 同步修改value值, 閉包入參爲inout類型, 能夠直接在調用write的時候進行修改
    /** let c = b.write({ $0 = 2 }) 調用完成以後, b.value == 2, c == 2 */
    @discardableResult
    func write<U>(_ closure: (inout T) -> U) -> U {
        lock.around { closure(&self.value) }
    }

    // dynamicMemberLookup修飾的必要方法, 對Protected類型使用.***獲取屬性時, 能夠根據該方法來動態獲取屬性
    // 好比Request中的: $mutableState.isFinishing
    // mutableState的類型是被@Protected修飾的Request.MutableState
    // 使用$mutableState獲取到的是Protected<Request.MutableState>類型, 能夠經過該方法來獲取Request.MutableState類型的值
    // $mutableState.isFinishing == mutableState.ifFinishing
    subscript<Property>(dynamicMember keyPath: WritableKeyPath<T, Property>) -> Property {
        get { lock.around { value[keyPath: keyPath] } }
        set { lock.around { value[keyPath: keyPath] = newValue } }
    }
}
複製代碼
  • 該類持有一個泛型value,能夠用來包裹須要線程安全的值,當對該值進行讀寫時,須要經過該包裹器,先上鎖在讀寫數據,而後解鎖,藉助Swift中的propertyWrapper屬性包裹器修飾,能夠很方便的實現包裹器邏輯異步

  • 配合屬性包裹器一塊兒使用的,通常還有rejectedValue語法糖,由於使用@Protected包裹器修飾後的屬性,若是使用self.***這樣直接去獲取屬性的話,會直接獲取到屬性的類型,因此Alamofire又使用Swift中的projectedValue語法糖來把包裹器的原類型Protected給返回去,這樣就能夠調用包裹器中定義的方法,以及自由的選擇獲取包裹屬性類型,仍是包裹器的類型async

  • 還使用了dynamicMemberLookup修飾,用來在使用rejectedValue語法糖獲取到Protected原類型的時候,經過動態查找屬性的方法來獲取所包裹的value的相關屬性值,增長使用靈活性

使用

基本使用

只讀寫簡單的基本值類型屬性

class Test {
    
    var unsafe: Int = 3
    
    @Protected
    var safe: Int = 3
    
    func test() {
        // 寫入
        self.unsafe = 2
        
        self.safe = 2
        self.$safe.write({
            $0 = 2
        })
        //讀取
        let val1: Int = self.unsafe
        
        let val2: Int = self.safe
        let val3: Int = self.$safe.read({
            $0
        })
        let val4: String = self.$safe.read({
            String($0)
        })
    }
    
}
複製代碼

Alamofire中的應用

Alamofire中主要的應用,是把一系列須要線程安全的值,定義爲一個結構體,而後把整個結構體使用包裹器包裹,這樣能夠同時保證多個數據的線程安全。好比:

//在Request.swift中,Request類中定義

/// Type encapsulating all mutable state that may need to be accessed from anything other than the `underlyingQueue`.
    struct MutableState {
        var state: State = .initialized
        ...各類屬性
        var isFinishing = false
    }

    /// Protected `MutableState` value that provides thread-safe access to state values.
    @Protected
    fileprivate var mutableState = MutableState()
複製代碼

後續使用時,普通讀取數據的話就:

/// `State` of the `Request`.
    public var state: State { mutableState.state }
    /// Returns whether `state` is `.initialized`.
    public var isInitialized: Bool { state == .initialized }
    /// Returns whether `state is `.resumed`.
    public var isResumed: Bool { state == .resumed }
複製代碼

須要使用屬性執行某些方法:

$mutableState.read { state in
        //執行state中的請求建立成功回調
        state.urlRequestHandler?.queue.async { state.urlRequestHandler?.handler(request) }
    }
複製代碼

寫入數據:

$mutableState.write { $0.requests.append(adaptedRequest) }
複製代碼

Protected類的擴展

由於持有泛型value,因此Alamofire真對某些特殊類型的value作了一些額外的擴展,可使用$獲取到Protected<T>原類型以後進行調用

// 當T是可變集合協議類型時, 添加快速追加元素的方法
extension Protected where T: RangeReplaceableCollection {
    // 添加一個元素
    func append(_ newElement: T.Element) {
        write { (ward: inout T) in
            ward.append(newElement)
        }
    }

    // 從序列添加元素
    func append<S: Sequence>(contentsOf newElements: S) where S.Element == T.Element {
        write { (ward: inout T) in
            ward.append(contentsOf: newElements)
        }
    }

    // 從集合添加元素
    func append<C: Collection>(contentsOf newElements: C) where C.Element == T.Element {
        write { (ward: inout T) in
            ward.append(contentsOf: newElements)
        }
    }
}

// 當T是可選Data類型時, 添加快速追加Data的方法
extension Protected where T == Data? {
    // 追加Data
    func append(_ data: Data) {
        write { (ward: inout T) in
            ward?.append(data)
        }
    }
}

//當T是Request.MutableState, 添加了兩個工具方法
extension Protected where T == Request.MutableState {
    // 線程安全地檢測下可否改變成新狀態
    // 檢測方法使用的是Request.MutableState本身的方法, 這裏只是作了線程安全處理
    func attemptToTransitionTo(_ state: Request.State) -> Bool {
        lock.around {
            guard value.state.canTransitionTo(state) else { return false }

            value.state = state

            return true
        }
    }

    // 線程安全的使用當前state執行一個閉包
    func withState(perform: (Request.State) -> Void) {
        lock.around { perform(value.state) }
    }
}

複製代碼

有了這些擴展方法,Alamofire在內部使用的時候,就更加自由,不過惋惜這個方法被聲明爲了internal,集成Alamofire框架以後,無法使用Protected類,不過能夠借鑑他的封裝思路自行根據本身的需求進行封裝處理

純屬我的理解, 可能存在理解錯誤的地方, 若有錯誤, 歡迎評論指出~ 感謝~

相關文章
相關標籤/搜索