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
複製代碼
這個類官方的定義爲: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中主要的應用,是把一系列須要線程安全的值,定義爲一個結構體,而後把整個結構體使用包裹器包裹,這樣能夠同時保證多個數據的線程安全。好比:
//在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) }
複製代碼
由於持有泛型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類,不過能夠借鑑他的封裝思路自行根據本身的需求進行封裝處理
純屬我的理解, 可能存在理解錯誤的地方, 若有錯誤, 歡迎評論指出~ 感謝~