往期导航:
简介
网络请求都会在异步完成,所以一定会碰到线程安全问题,需要在对某些共享数据读写时,考虑下多线程读写情况下的加解锁,原理很简单:只要在对线程安全敏感的数据进行读写操作时,加锁,读写结束后解锁即可,但是涉及到线程安全的数据很多,如何封装是个艺术问题~
Alamofire中定义了一个Protected类来当做属性包裹器使用,可以在对包裹的数据进行操作时进行加解锁操作,同时使用了Swift的语法特性与语法糖来让这个类功能非常强大。
锁
线程安全离不开锁,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类 -- 核心,持有锁的属性修饰器类
这个类官方的定义为:
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给返回去,这样就可以调用包裹器中定义的方法,以及自由的选择获取包裹属性类型,还是包裹器的类型
-
还使用了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类,不过可以借鉴他的封装思路自行根据自己的需求进行封装处理
纯属个人理解, 可能存在理解错误的地方, 如有错误, 欢迎评论指出~ 感谢~