Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RxSwift学习与第三方网络库RxAlamofire结合使用——(二) #101

Open
soapgu opened this issue Jan 18, 2022 · 0 comments
Open
Labels

Comments

@soapgu
Copy link
Owner

soapgu commented Jan 18, 2022

  • 前言

上一个章节,我们之间使用URLSession再结合RxCocoa实现http通讯。
但是总是显得有点太简陋,还需要选择一件趁手的“兵器”。

  • 选型

Swift的第三方库相对安卓的第三方库感官上没这么丰富。

我从awesome swift里面找下相关推荐组件
awesome swift
首选 Alamofire
7K的Fork ,37K的Star 从大数据讲应该是优秀

问题Alamofire不能之间使用RxSwift。如果使用需要自己写Create来包装,这样相对麻烦一些

救星来了RxAlamofire
这个组建为Alamofire和RxSwift之间搭了“一座桥”

  • 切入点

这次我们再次不走寻找路,试试之间Clone 源码来看看
Command + R build看看,失败...

  • 学习使用Carthage构建第三方库

再看下项目结构
图片
图片

结合我们上次超声波的经验,这个项目的依赖库是由Carthage来管理的。

第三方依赖库管理工具首选Cocoapods,是当之无愧的扛把子。Carthage感官上也能做第二把交椅,所以这个工具原则上也要会用。

  1. 安装。我用的安装包(Carthage.pkg)安装的
  2. 检查Carthage文件
github "ReactiveX/RxSwift" ~> 6.0.0
github "Alamofire/Alamofire" ~> 5.4

这是RxAlamofire的(不是废话吗,桥接这两个库当然要引用),这里分别代表版本6.X和5.X,和Cocoapad的版本策略一致
CocoaPad的podflile说明
3. 在当前目录执行carthage update
好像有失败

Building universal frameworks with common architectures is not possible. The device and simulator slices for "Alamofire" both build for: arm64
Rebuild with --use-xcframeworks to create an xcframework bundle instead.

看上去又是M1芯片的锅,用arch -x86_64 carthage update还是一样结果

  • 解决M1芯片构建carthage的错误

好在网上还是有解决方法的。github里Carthage下的issue里面可以翻到,解决方式也简单直接

guhui@guhuideMacBook-Pro RxAlamofire % touch carthage.sh
guhui@guhuideMacBook-Pro RxAlamofire % open -a XCode carthage.sh
guhui@guhuideMacBook-Pro RxAlamofire % ./carthage.sh update
zsh: permission denied: ./carthage.sh
guhui@guhuideMacBook-Pro RxAlamofire % sudo chmod -R 777 carthage.sh
guhui@guhuideMacBook-Pro RxAlamofire % ./carthage.sh update         
objc[14805]: Class AMSupportURLConnectionDelegate is implemented in both /usr/lib/libauthinstall.dylib (0x1f9caeb90) and /Library/Apple/System/Library/PrivateFrameworks/MobileDevice.framework/Versions/A/MobileDevice (0x1068002c8). One of the two will be used. Which one is undefined.
objc[14805]: Class AMSupportURLSession is implemented in both /usr/lib/libauthinstall.dylib (0x1f9caebe0) and /Library/Apple/System/Library/PrivateFrameworks/MobileDevice.framework/Versions/A/MobileDevice (0x106800318). One of the two will be used. Which one is undefined.
*** Fetching OHHTTPStubs
*** Fetching RxSwift
*** Fetching Alamofire
*** Checking out Alamofire at "5.5.0"
*** Checking out OHHTTPStubs at "9.1.0"
*** Checking out RxSwift at "6.5.0"
*** xcodebuild output can be found in /var/folders/bn/hcn18txs3456lwt5f4zxwy1m0000gn/T/carthage-xcodebuild.E5im2A.log
*** Downloading RxSwift binary at "Atlas"
*** Building scheme "Alamofire iOS" in Alamofire.xcworkspace
*** Building scheme "Alamofire macOS" in Alamofire.xcworkspace
*** Building scheme "Alamofire tvOS" in Alamofire.xcworkspace
*** Building scheme "Alamofire watchOS" in Alamofire.xcworkspace
*** Building scheme "OHHTTPStubs iOS Framework" in OHHTTPStubs.xcworkspace
*** Building scheme "OHHTTPStubs tvOS Framework" in OHHTTPStubs.xcworkspace
*** Building scheme "OHHTTPStubs Mac Framework" in OHHTTPStubs.xcworkspace
*** Building scheme "RxBlocking" in Rx.xcworkspace
*** Building scheme "RxSwift" in Rx.xcworkspace
*** Building scheme "RxRelay" in Rx.xcworkspace
*** Building scheme "RxCocoa" in Rx.xcworkspace
*** Building scheme "RxTest" in Rx.xcworkspace

完美,后悔没选--platform,等了好久...

  • 从Alamofire开始

在XCode中打开RxAlamofire
图片
你会发现,他就是一个“中间商”没有太多的代码
所以我们先回他的供应商Alamofire去找些有用的线索

先看最简sample

AF.request("https://httpbin.org/get").response { response in
    debugPrint(response)
}

整快代码分为两个重要api,requst 和 response把api分为入口和出口

  1. rquest
    就是参数入口
open func request<Parameters: Encodable>(_ convertible: URLConvertible,
                                         method: HTTPMethod = .get,
                                         parameters: Parameters? = nil,
                                         encoder: ParameterEncoder = URLEncodedFormParameterEncoder.default,
                                         headers: HTTPHeaders? = nil,
                                         interceptor: RequestInterceptor? = nil) -> DataRequest

看下完成的函数签名,使用http的同学们都知道,url,method,parameters(body),headers 基本上该有的都有了

  1. response
    response的API函数如下
// Response Handler - Unserialized Response
func response(queue: DispatchQueue = .main, 
              completionHandler: @escaping (AFDataResponse<Data?>) -> Void) -> Self

// Response Serializer Handler - Serialize using the passed Serializer
func response<Serializer: DataResponseSerializerProtocol>(queue: DispatchQueue = .main,
                                                          responseSerializer: Serializer,
                                                          completionHandler: @escaping (AFDataResponse<Serializer.SerializedObject>) -> Void) -> Self

// Response Data Handler - Serialized into Data
func responseData(queue: DispatchQueue = .main,
                  dataPreprocessor: DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor,
                  emptyResponseCodes: Set<Int> = DataResponseSerializer.defaultEmptyResponseCodes,
                  emptyRequestMethods: Set<HTTPMethod> = DataResponseSerializer.defaultEmptyRequestMethods,
                  completionHandler: @escaping (AFDataResponse<Data>) -> Void) -> Self

// Response String Handler - Serialized into String
func responseString(queue: DispatchQueue = .main,
                    dataPreprocessor: DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor,
                    encoding: String.Encoding? = nil,
                    emptyResponseCodes: Set<Int> = StringResponseSerializer.defaultEmptyResponseCodes,
                    emptyRequestMethods: Set<HTTPMethod> = StringResponseSerializer.defaultEmptyRequestMethods,
                    completionHandler: @escaping (AFDataResponse<String>) -> Void) -> Self

// Response Decodable Handler - Serialized into Decodable Type
func responseDecodable<T: Decodable>(of type: T.Type = T.self,
                                     queue: DispatchQueue = .main,
                                     dataPreprocessor: DataPreprocessor = DecodableResponseSerializer<T>.defaultDataPreprocessor,
                                     decoder: DataDecoder = JSONDecoder(),
                                     emptyResponseCodes: Set<Int> = DecodableResponseSerializer<T>.defaultEmptyResponseCodes,
                                     emptyRequestMethods: Set<HTTPMethod> = DecodableResponseSerializer<T>.defaultEmptyRequestMethods,
                                     completionHandler: @escaping (AFDataResponse<T>) -> Void) -> Self

详解

  • 第一个response函数,不care 返回具体内容
  • 第二个 response<Serializer: DataResponseSerializerProtocol>,这是一个核心基础函数,需要定义返回数据的序列话方式
  • 第三个 responseData,这是返回二进制内容
  • 第四个 responseString,这是返回字符串
  • 第五个 responseDecodable,这是返回可解码对象,常用json,xml我不知道是不是也可以
@discardableResult
    public func responseData(queue: DispatchQueue = .main,
                             dataPreprocessor: DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor,
                             emptyResponseCodes: Set<Int> = DataResponseSerializer.defaultEmptyResponseCodes,
                             emptyRequestMethods: Set<HTTPMethod> = DataResponseSerializer.defaultEmptyRequestMethods,
                             completionHandler: @escaping (AFDataResponse<Data>) -> Void) -> Self {
        response(queue: queue,
                 responseSerializer: DataResponseSerializer(dataPreprocessor: dataPreprocessor,
                                                            emptyResponseCodes: emptyResponseCodes,
                                                            emptyRequestMethods: emptyRequestMethods),
                 completionHandler: completionHandler)
    }

就是说Data也是实现DataResponseSerializerProtocol接口的

可以看下实现的serialize

public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> Data {
        guard error == nil else { throw error! }

        guard var data = data, !data.isEmpty else {
            guard emptyResponseAllowed(forRequest: request, response: response) else {
                throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength)
            }

            return Data()
        }

        data = try dataPreprocessor.preprocess(data)

        return data
    }


public struct PassthroughPreprocessor: DataPreprocessor {
    public init() {}

    public func preprocess(_ data: Data) throws -> Data { data }
}

仔细看可以说啥都没干....

@discardableResult
    public func responseString(queue: DispatchQueue = .main,
                               dataPreprocessor: DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor,
                               encoding: String.Encoding? = nil,
                               emptyResponseCodes: Set<Int> = StringResponseSerializer.defaultEmptyResponseCodes,
                               emptyRequestMethods: Set<HTTPMethod> = StringResponseSerializer.defaultEmptyRequestMethods,
                               completionHandler: @escaping (AFDataResponse<String>) -> Void) -> Self {
        response(queue: queue,
                 responseSerializer: StringResponseSerializer(dataPreprocessor: dataPreprocessor,
                                                              encoding: encoding,
                                                              emptyResponseCodes: emptyResponseCodes,
                                                              emptyRequestMethods: emptyRequestMethods),
                 completionHandler: completionHandler)
    }


@discardableResult
    public func responseDecodable<T: Decodable>(of type: T.Type = T.self,
                                                queue: DispatchQueue = .main,
                                                dataPreprocessor: DataPreprocessor = DecodableResponseSerializer<T>.defaultDataPreprocessor,
                                                decoder: DataDecoder = JSONDecoder(),
                                                emptyResponseCodes: Set<Int> = DecodableResponseSerializer<T>.defaultEmptyResponseCodes,
                                                emptyRequestMethods: Set<HTTPMethod> = DecodableResponseSerializer<T>.defaultEmptyRequestMethods,
                                                completionHandler: @escaping (AFDataResponse<T>) -> Void) -> Self {
        response(queue: queue,
                 responseSerializer: DecodableResponseSerializer(dataPreprocessor: dataPreprocessor,
                                                                 decoder: decoder,
                                                                 emptyResponseCodes: emptyResponseCodes,
                                                                 emptyRequestMethods: emptyRequestMethods),
                 completionHandler: completionHandler)
    }

responseString和responseDecodable都有自己的专属Serializer
所以逻辑结构应该是

ResponseHandle -> response
                  response<Serializer: DataResponseSerializerProtocol> -> responseData
                                                                          responseString
                                                                          responseDecodable

  • 从Alamofire到RxAlamofire

再次回到RxAlamofire源码就相对来说有目标点了
从RxAlamofire入口找

/**
 Creates an observable of the returned decoded Decodable.

 - parameter method: Alamofire method object
 - parameter url: An object adopting `URLConvertible`
 - parameter parameters: A dictionary containing all necessary options
 - parameter encoding: The kind of encoding used to process parameters
 - parameter header: A dictionary containing all the additional headers
 - parameter interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default.
 - returns: An observable of the decoded Decodable as `T`
 */
public func decodable<T: Decodable>(_ method: HTTPMethod,
                                    _ url: URLConvertible,
                                    parameters: Parameters? = nil,
                                    encoding: ParameterEncoding = URLEncoding.default,
                                    headers: HTTPHeaders? = nil,
                                    interceptor: RequestInterceptor? = nil)
  -> Observable<T> {
  return Alamofire.Session.default.rx.decodable(method,
                                                url,
                                                parameters: parameters,
                                                encoding: encoding,
                                                headers: headers,
                                                interceptor: interceptor)
}

func decodable<T: Decodable>(_ method: HTTPMethod,
                               _ url: URLConvertible,
                               parameters: Parameters? = nil,
                               encoding: ParameterEncoding = URLEncoding.default,
                               headers: HTTPHeaders? = nil,
                               interceptor: RequestInterceptor? = nil)
    -> Observable<T> {
    return request(method,
                   url,
                   parameters: parameters,
                   encoding: encoding,
                   headers: headers,
                   interceptor: interceptor).flatMap { $0.rx.decodable() }
  }

func decodable<T: Decodable>(decoder: Alamofire.DataDecoder = JSONDecoder()) -> Observable<T> {
    return result(responseSerializer: DecodableResponseSerializer(decoder: decoder))
  }

func result<T: DataResponseSerializerProtocol>(queue: DispatchQueue = .main,
                                                 responseSerializer: T)
    -> Observable<T.SerializedObject> {
    return Observable.create { observer in
      let dataRequest = self.base
        .response(queue: queue, responseSerializer: responseSerializer) { (packedResponse) -> Void in
          switch packedResponse.result {
          case let .success(result):
            if packedResponse.response != nil {
              observer.on(.next(result))
              observer.on(.completed)
            } else {
              observer.on(.error(RxAlamofireUnknownError))
            }
          case let .failure(error):
            observer.on(.error(error as Error))
          }
        }
      return Disposables.create {
        dataRequest.cancel()
      }
    }
  }

通过源码可以看到,RxAlamofire通过flatMap来连接Alamofire的request和response的API,再另外自己封装处理一些逻辑
同理RxAlamofire.data 这个api就是返回二进制数据类似处理过程

  • 使用RxAlamofire来重构下Demo

    @IBOutlet var tapButton: NSButton!
    @IBOutlet var image: NSImageView!
    @IBOutlet var msgLabel: NSTextField!
    var disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()
        tapButton.rx.tap.flatMapLatest{
            [unowned self] in
            self.getRandomPhotoComposeRxAlamofire()
        }
        .observe(on: MainScheduler.instance)
        .subscribe(onNext: { [weak image,weak msgLabel] data in
            if let currentString = msgLabel?.stringValue {
                msgLabel?.stringValue = currentString.replacingOccurrences(of: "...", with: "")
            }
            image?.image = NSImage(data: data)
        } )
        .disposed(by: disposeBag)
    }

    func getRandomPhotoComposeRxAlamofire() -> Observable<Data>{
        return getRandomPhotoRxAlamofire().flatMap {
            [unowned self,weak msgLabel] photo -> Observable<Data> in
                msgLabel?.stringValue = "Loading \(photo.description ?? "") image..."
                return self.getImageRxAlamofire(urlString: photo.urls.small)
        }
    }
    
    func getRandomPhotoRxAlamofire() ->Observable<Photo>{
        let urlString = "https://api.unsplash.com/photos/random?client_id=ki5iNzD7hebsr-d8qUlEJIhG5wxGwikU71nsqj8PcMM"
        msgLabel?.stringValue = "Loading data..."
        return RxAlamofire.decodable(.get, urlString)
    }
    
    func getImageRxAlamofire( urlString : String ) ->Observable<Data>{
        return RxAlamofire.data(.get, urlString)
    }

代码http部分代码可以精简一点,json解码部分也内置了
图片

  • 存在主要问题及编写时感受

  • 暂时没有验证状态码的机制,Alamofire有但是RxAlamofire没有集成进来,如果一定要加进去那就要重写api了

  • 这个问题暂时没法好好解决,对于嵌套Rx操作闭包嵌套闭包还需要把捕获的变量weak处理避免内存泄漏

  • 闭包省略还不够“聪明”,只能推断单个表达式,如果多行代码的代码块,则推断失效不仅返回值一定要指定,参数也不能用$0,$1省略,无力吐槽

  • 还有就是api精确度不够,明明是Single为啥变成了Observable

  • 相关练习代码仓库

  • Demo的repository(RxSwiftForMac)

@soapgu soapgu added ReactiveX ReactiveX Swift Demo Demo labels Jan 18, 2022
@soapgu soapgu changed the title RxSwift学习与第三方网络库RxAlamofire结合使用——(三) RxSwift学习与第三方网络库RxAlamofire结合使用——(二) Jan 21, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant