美文网首页
AFNetworking 迁移 Alamofire

AFNetworking 迁移 Alamofire

作者: Emagrorrim | 来源:发表于2018-11-29 21:41 被阅读0次

    背景

    最近项目上完成了从AFNetworkingAlamofire的迁移,此为背景。

    Overview

    • Alamofire简介
    • 使用方式
    • 图片下载
    • 如何在OC项目中使用
    • 从AFNetworking迁移到Alamofire注意事项
    • 总结

    Alamofire简介

    Alamofire is an HTTP networking library written in Swift.

    上面这句话是Alamofire的官方Github上面的描述,意思是说:“Alamofire是一个用Swift写的HTTP网络库”。其开发团队与AFNetworking是同一个团队(或许AFNetworking中的AF就是Alamofire,哈哈😆。)

    Alamofire的Github地址:https://github.com/Alamofire/Alamofire

    下面是Alamofire的大版本更新时间。

    • Alamofire第一次Release是在2015年5月11日,发布了1.0版本;
    • 之后在同一年(2015年)9月发布了2.0,
    • 同年10月份就发布了3.0版本,
    • 之后2016年的9月,发布了4.0版本
    • 知道今天,4.0都是一个相对稳定的版本,现在最新版本4.7.3,支持最新的Swift 4.2Xcode 10,可以说更新速度十分快了。同时我们可以看到,现在Alamofire团队正在开发Alamofire 5.0

    安装

    Cocoapods

    在Podfile中添加如下代码:

    source 'https://github.com/cocoapods/specs.git'
    platform :ios, '10.0'
    use_frameworks!
    
    target '<Your Target Name>' do
        ...other pods
        pod 'Alamofire', '~> 4.7'
    end
    
    Carthage
    github "Alamofire/Alamofire" ~> 4.7
    
    Swift Package Manager
    dependencies: [
        .Package(url: "https://github.com/Alamofire/Alamofire.git", majorVersion: 4)
    ]
    

    使用方式

    关于一些细节问题,后面在后面的章节从AFNetworking迁移到Alamofire注意事项中会有涉及,这里只介绍基本的使用方法。

    Alamofire.request(url).response { response in
      if let error = response.result.error {
        // Handle error
        return
      }
      // Handle response.result.value
    }
    

    大家可以看到,在这里,是使用Alamofire发了一个最简单的请求,和AFNetworking还是由不少区别的,比如不管是GET, POST, 都使用request方法,而方法会作为参数传入,并且request的时候不会直接传入completion的closure,而是在.request(url)之后,使用.response()来拿到response,那么一个提醒是——在我们使用.request(url)之后,这个请求就已经发出去了,使用.response()只是把已经获得的response传给你,当然如果你直接链式的调用.response()的话,可能还没有收到response,所以这里还是异步的completion closure的形式来获取response的。

    还有一点需要注意的是:如果没有加Validation的话,404500Alamofire都会认为请求成功了,而不会返回error。只用调用了validation()才能正常的获得error。如下所示:

    Alamofire.request(url).validate().response { response in
      if let error = response.result.error {
        // Handle error
        return
      }
      // Handle response.result.value
    }
    

    另外一点需要注意是:如果按如下方式写的话

    let dataRequest = Alamofire.request(url)
    dataRequest.validate().response { response in
      if let error = response.result.error {
        // Handle error
        return
      }
      // Handle response.result.value
    }
    

    也是不会在400404500的时候得到error的,如果你一定需要单独写request的话,可以写成下面这样:

    let dataRequest = Alamofire.request(url).validate()
    dataRequest.response { response in
      if let error = response.result.error {
        // Handle error
        return
      }
      // Handle response.result.value
    }
    

    最后一点是validate()函数的默认validation是StatusCode在200300之间,ContentType必须是你使用的response方法能够解析的。例如.responseJSON()需要ContentType是application/json之类的。具体大家可以自己尝试。复杂的API可以根据需要自行查看Alamofire的文档。

    附Alamofire request方法完整参数签名:

    Alamofire.request("https://httpbin.org/post", method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: headers)
    

    图片下载

    使用网络Framework,大家都很关注一个问题,就是图片下载的问题,有些人可能会使用SDWebImage,或者KingFisher之类的网络图片第三方库,但是也有一些人会不希望引入过多的第三方库,所以也比较关注一个第三方库是否有图片下载的功能。好消息是,Alamofire确实有,Alamofire有一个对Alamofire拓展的库,叫做AlamofireImage,可以满足你的需求。这里简单介绍下我们需要的API。

    import Alamofire
    import AlamofireImage
    
    Alamofire.request("https://httpbin.org/image/png").responseImage { response in
      if let image = response.result.value {
        print("image downloaded: \(image)")
      }
    }
    

    首先如上面这段代码所示,我们可以方便的直接下载一个图片,然后把它使用在我们需要的地方,另外我们也可以它对UIImageView的拓展快速的给一个ImageView设置网络图片。API如下:

    let imageView = UIImageView()
    imageView.af_setImage(withURL: url)
    

    如果你不喜欢“_”的命名方式大量出现在你的代码中,你也可以自己给UIImageView加一个extension,代码如下:

    import Alamofire
    import AlamofireImage
    
    extension UIImageView {
      func setImage(withURL url: URL) {
        af_setImage(withURL: url)
      }
    }
    
    let imageView = UIImageView()
    imageView.setImage(withURL: url)
    

    这样不仅可以避免代码中出现“_”,还可以对第三方API做一个隔离,不需要到处import AlamofireImage,将来你想换一个网络图片的库了,很简单,把setImage的实现换成新的库的API就可以了。

    如何在OC项目中使用

    首先,Alamofire不用直接在OC的代码中调用,因为其中有许多API有类似于泛型,Swift Enum等。但是,这并不能够阻挡我们在OC的代码中使用Alamofire。

    思路确认:

    • 首先,只有Swift可以使用Alamofire。
    • 第二,有关泛型,Swift 非Int类型的Enum,还有一些Swifty的语法OC不能使用。

    明白了这两点,思路就有了,那就是用Swift封装一层Alamofire,并且对外只暴露OC可以使用的API。举个简单的例子:

    class AlamofireNetworkClient: NSObject {
      @objc func fetchData(completion: @escaping ([String: Any]) -> Void) {
        Alamofire
            .request("https://httpbin.org/get", method: .get, parameters: [:], encoding: URLEncoding.default, headers: nil)
            .responseJSON { response in
              switch response.result {
              case .success(let value):
                let data = value as? [String: Any] ?? [:]
                completion(data)
              case .failure(let error):
                print(error.localizedDescription)
              }
            }
      }
    }
    

    大家可以看上面这段代码,对于Swift中才能使用的Enum, .get还有类似与.success(let value)这样的代码,我们就将其留在封装的的API之中,对外我们只是用可以再OC中使用的代码,例如我们的completion的closure,如果希望将HTTP METHOD也传进来的话,可以使用字符串的方式加在方法的参数列表中传入。在转成Swift中的Enum类型。

    从AFNetworking迁移到Alamofire注意事项

    介于可能会有很多人和我们一样,现在也在使用AFNetworking,这里简单说一下我们在整个迁移过程中遇到的问题。

    首先,简单介绍下我们迁移的流程。


    我们的迁移流程如上图,通过以上流程,可以相对平滑的替换掉现有的AFNetworking,并且也方便将来使用新的Networking的库(这里需要提的一点是,我们使用所有的第三方库的时候,特别是工具类的第三方库的时候,对第三方库进行一个简单的封装,并且用Protocol来限制其行为,可以有效的降低将来替换的工作量)。当然,我们还遇到了许多问题,这里就不一一描述了,只针对几个典型的问题进行描述。

    AFNetworking的API被在项目中直接使用的问题

    这个问题的答案大家其实从迁移流程中已经看到了,我们不打算一一将使用AFNetworking的地方替换成Alamofire,而是先将现有AFNetworking API使用收束到一个统一的类中,我们这里叫
    AFNetworkClient。之后删除所有AFNetworking的头文件引入,除了AFNetworkClient,这样我们就有了一个封装好的API Client。之后就如流程中写的,完成替换工作即可。

    AFNetworking与Alamofire API有许多默认行为不一致的问题

    在替换的过程中,我们会发现,其实有很多原来的AFNetworking中的API不见了,或者行为不一样了,这其实还是造成了不少困扰的。

    • Response验证

    AFNetworking有默认的错误验证,对于404,500这类的http statusCode,我们会在回调中拿到一个error。但是在Alamofire中,它的默认行为是不对状态码和Content-Type验证,既它认为只要我收到了Response,我就认为你的请求是成功的,至于状态码,那不过是你收到的Response的一种,不能算错误。当然,这里它还是提供了一个API的,使用如下写法可以让Alamofire也验证Response

    Alamofire.get("url").validate().response {
      // handler response.
    }
    
    • Response Serializer的类型少了

    AFNetworking帮我们实现了多种Serializer,甚至还有一种组合的Serializer,可以组合多种Serializer使用,因为对于NetworkClient,如果不在invoke method的时候传类型,我们只能让其支持多种Response Content Type,AFNetworking和Alamofire都能支持多种类型的Response,其实我们在设计NetworkingClient的API的时候应该考虑这点,但是因为遗留问题,我们很难将所有的API调用需要的Serializer类型全部理清楚,所以我们使用Alamofire实现了一个类似AFNetworking的组合Serializer,代码如下:

    import Alamofire
    import Foundation
    
    extension DataRequest {
      static func jsonObject(with data: Data?) -> Result<Any> {
        guard let data = data else {
          return .failure(AFError.responseValidationFailed(reason: .dataFileNil))
        }
    
        do {
          let object = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions(rawValue: 0))
          return .success(object)
        } catch let error {
          return .failure(error)
        }
      }
    
      static func xmlObject(with data: Data?) -> Result<Any> {
        guard let data = data else {
          return .failure(AFError.responseValidationFailed(reason: .dataFileNil))
        }
    
        return .success(XMLParser(data: data))
      }
    
      static func imageObject(with data: Data?) -> Result<Any> {
        guard let data = data,
          let image = UIImage(data: data, scale: UIScreen.main.scale) else {
          return .failure(AFError.responseValidationFailed(reason: .dataFileNil))
        }
    
        return .success(image)
      }
    }
    
    extension DataRequest {
      static var acceptableXMLContentTypes: [String] = [
        "application/xml",
        "text/xml",
      ]
    
      static var acceptableJSONContentTypes: [String] = [
        "text/json",
        "text/javascript",
        "application/json",
        "application/hal+json",
      ]
    
      static var acceptableImageContentTypes: [String] = [
        "image/tiff",
        "image/jpeg",
        "image/gif",
        "image/png",
        "image/ico",
        "image/x-icon",
        "image/bmp",
        "image/x-bmp",
        "image/x-xbitmap",
        "image/x-ms-bmp",
        "image/x-win-bitmap",
      ]
    
      static func compoundResponseSerializer() -> DataResponseSerializer<Any> {
        return DataResponseSerializer { (_, response, data, error) -> Result<Any> in
          if let error = error {
            return .failure(error)
          }
          guard let mimeType = response?.mimeType else {
            return .failure(AFError.responseValidationFailed(reason: .missingContentType(acceptableContentTypes: DataRequest.acceptableJSONContentTypes + DataRequest.acceptableXMLContentTypes + DataRequest.acceptableImageContentTypes)))
          }
    
          switch mimeType {
          case let mimeType where DataRequest.acceptableJSONContentTypes.contains(mimeType):
            return DataRequest.jsonObject(with: data)
          case let mimeType where DataRequest.acceptableXMLContentTypes.contains(mimeType):
            return DataRequest.xmlObject(with: data)
          case let mimeType where DataRequest.acceptableImageContentTypes.contains(mimeType):
            return DataRequest.imageObject(with: data)
          default:
            return .success(data as Any)
          }
        }
      }
    
      @discardableResult
      func responseCompoundData(queue: DispatchQueue? = nil, completionHandler: @escaping (DataResponse<Any>) -> Void) -> Self {
        return response(queue: queue, responseSerializer: DataRequest.compoundResponseSerializer(), completionHandler: completionHandler)
      }
    }
    

    总结

    Alamofire相比AFNetworking,无论从API的优雅程度,定位(Alamofire - 工具 VS AFNetworking - 模块),Alamofire都更胜一筹。当然,还有一个重要的原因,那就是AFNetworking的开发人员现在都去开发Alamofire了,AFNetworking经常慢于苹果更新速度很多,这意味着你常常需要手动Fork一份自己改,所以还是痛下决心替换成Alamofire吧。

    相关文章

      网友评论

          本文标题:AFNetworking 迁移 Alamofire

          本文链接:https://www.haomeiwen.com/subject/sjeqcqtx.html