Alamofire源码学习(十六): Alamofire中的线程安全

2,628 阅读4分钟

往期导航:

Alamofire源码学习目录合集

简介

网络请求都会在异步完成,所以一定会碰到线程安全问题,需要在对某些共享数据读写时,考虑下多线程读写情况下的加解锁,原理很简单:只要在对线程安全敏感的数据进行读写操作时,加锁,读写结束后解锁即可,但是涉及到线程安全的数据很多,如何封装是个艺术问题~

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类,不过可以借鉴他的封装思路自行根据自己的需求进行封装处理

纯属个人理解, 可能存在理解错误的地方, 如有错误, 欢迎评论指出~ 感谢~