Alamofire框架详细解析(二) —— 高级用法(一)

2020-10-16  本文已影响0人  刀客传奇

版本记录

版本号 时间
V1.0 2020.10.16 星期五

前言

关于网络请求有很多优秀的三方框架,比较常用的比如说OC的AFNetworking,这里我们就一起学习一下Swift的网络请求框架 - Alamofire。感兴趣的可以看下面几篇文章。
1. Alamofire框架详细解析(一) —— 基本概览(一)

开始

首先看下主要内容:

在本教程中,您将学习Alamofire的高级用法。 主题包括处理OAuthnetwork logging, reachability,caching等。内容来自翻译

下面看一下写作环境:

Swift 5, iOS 14, Xcode 12

接着就是正文啦。

Alamofire是最流行和广泛使用的Swift网络库之一。它建立在Apple的Foundation网络堆栈上,提供了一个优雅的API来发出网络请求。它在GitHub上拥有3万颗星,是最受好评的Swift仓库之一。

今天,您将学习并掌握它。

在本教程中,您将介绍Alamofire的高级用法。在构建GitHub客户端应用程序GitOnFire时,您将应用这些概念。在此过程中,您将学习如何:

这是一个有趣的事实:Alamofire以得克萨斯州官方州花之一Alamo Fire flower花的名字命名。

注意:本教程假定您熟悉Apple’s URL Loading System。如果您是Alamofire的新手,请查看Alamofire 5 For iOS: Getting Started。您还需要一个GitHub帐户。

打开入门项目。

您将使用一个名为GitOnFire的应用程序。入门项目随附Alamofire网络库和GitHub API的基本集成。

在启动文件夹中打开项目。构建并运行。

您会在GitHub中看到最受欢迎的Swift存储库列表。 注意Alamofire在该列表上的位置。

指定搜索文本以获取热门存储库的相关列表。 点击存储库的名称以查看其最近的提交。

您将更新网络调用以使用自定义URLSessionConfiguration。 您还将集成OAuth登录并从您的帐户中获取存储库。

这些是您首先要处理的文件:

在本教程中,您将使用GitHub API处理OAuth身份验证,获取仓库和提交。 有关所有GitHub API及其用途的详尽列表,请参阅此API documentation


Custom Session and URLSessionConfiguration

打开GitAPIManager.swift并找到searchRepositories(query:completion :)。 搜索GitHub存储库的实现已经到位。 您将利用这个机会来了解方法的内部原理。

func searchRepositories(
  query: String,
  completion: @escaping ([Repository]) -> Void
) {
  // 1
  let url = "https://api.github.com/search/repositories"
  // 2
  var queryParameters: [String: Any] = [
    "sort": "stars",
    "order": "desc",
    "page": 1
  ]
  queryParameters["q"] = query
  // 3
  AF.request(url, parameters: queryParameters).responseDecodable(
    of: Repositories.self) { response in
      guard let items = response.value else {
        return completion([])
      }
      completion(items.items)
    }
}

以下是分步细分:

每个请求的默认超时为60秒。 要将超时间隔更新为30秒,可以将requestModifier指定为请求的一部分,如下所示:

AF.request(url, parameters: queryParameters) { urlRequest in
  urlRequest.timeoutInterval = 30
}

您可以在构造请求时将requestModifier指定为尾随block。 但是,如果所有会话请求都需要超时间隔,该怎么办? 您使用自定义URLSessionConfiguration

1. Customizing Session

Alamofire提供了Session,在责任方面类似于URLSession。 它有助于创建和管理不同的请求,并为所有请求提供通用功能,例如侦听,响应缓存等。 在本教程的后面,您将了解有关不同类型请求的更多信息。

您可以通过将URLSessionConfiguration与所需的配置一起使用来自定义会话行为。 将以下代码添加到GitAPIManager:

//1
let sessionManager: Session = {
  //2
  let configuration = URLSessionConfiguration.af.default
  //3
  configuration.timeoutIntervalForRequest = 30
  //4
  return Session(configuration: configuration)
}()

在这里:

现在,您将使用此sessionManager处理所有网络请求。 在searchRepositories(query:completion :)中,将下面:

AF.request(url, parameters: queryParameters)

替换为

sessionManager.request(url, parameters: queryParameters)

在这里,您使用sessionManager而不是AF发出请求。

接下来在fetchCommits(for:completion :)中,替换:

AF.request(url)

sessionManager.request(url)

现在,两个网络请求的请求超时间隔都设置为30秒。

接下来,您将看到使用自定义会话的优势。

sessionManager中,在configuration.timeoutIntervalForRequest = 30下面添加以下代码:

configuration.waitsForConnectivity = true

在这里,将waitsForConnectivity设置为true,这使会话在发出请求之前先等待网络连接。 您必须为URLSessionConfiguration设置所有自定义配置,然后才能将它们添加到Session中。 如果您在将配置属性添加到会话Session后对其进行了更改,则它们不会产生任何影响。

查看实际的代码。 关闭WiFi。 构建并运行。

该应用会加载,并显示一个带有加载指示器的空白列表。 然后,打开WiFi。 在几秒钟内,存储库将加载。 感觉像魔术吗?

在本教程的稍后部分,您将学习如何监视和处理网络可达性。

接下来,您将学习如何使用事件监视器Event Monitor记录网络请求和响应。


Logging Network Requests and Responses Using Event Monitor

到目前为止,您已经发出网络请求以获取存储库和提交,并将结果显示在table view中。 很好,但是您可以走得更远,看到原始的网络请求和响应。

Alamofire提供了一种通过EventMonitor协议深入了解所有内部事件的强大方法。 EventMonitor包含多个Alamofire事件,例如URLSessionDelegate请求事件。 这使EventMonitor非常适合记录事件。

Networking组中创建一个名为GitNetworkLogger.swift的新Swift文件。 添加以下代码:

import Alamofire

class GitNetworkLogger: EventMonitor {
  //1
  let queue = DispatchQueue(label: "com.raywenderlich.gitonfire.networklogger")
  //2
  func requestDidFinish(_ request: Request) {
    print(request.description)
  }
  //3
  func request<Value>(
    _ request: DataRequest,
    didParseResponse response: DataResponse<Value, AFError>
  ) {
    guard let data = response.data else {
      return
    }
    if let json = try? JSONSerialization
      .jsonObject(with: data, options: .mutableContainers) {
        print(json)
    }
  }
}

以下是代码细分:

您可以使用自定义DispatchQueue初始化queue以保持性能。 这是一个串行队列,可以处理会话中的所有事件。

打开GitAPIManager.swift。 在sessionManager中的configuration.waitsForConnectivity = true下面添加以下代码:

let networkLogger = GitNetworkLogger()

在这里,您将networkLogger定义为GitNetworkLogger的实例。

现在,将return Session(configuration:configuration)替换为以下内容:

return Session(configuration: configuration, eventMonitors: [networkLogger])

Session初始化期间,将networkLogger以数组形式传递给eventMonitors。 构建并运行。

现在,您将在控制台中看到所有网络请求和响应记录。很好!

到目前为止,您已经获取了公共存储库。现在是时候从您自己的GitHub帐户中获取存储库了。准备一些授权乐趣。


GitHub Authorization

要获取您的私有存储库,您需要通过您的应用程序登录GitHub。应用可以通过两种方式获得访问GitHub API的授权:

在本教程中,您将学习如何使用OAuth 2.0token

1. OAuth Overview

授权应用程序通过OAuth 2.0访问用户存储库的步骤如下:

接下来,您将创建一个GitHub OAuth应用。

2. Creating GitHub OAuth App

登录GitHub并按照以下步骤steps创建具有以下设置的OAuth应用:

3. Logging Into GitHub

注册应用程序后,复制Client IDClient Secret值。 然后在您的Xcode项目中,打开GitHubConstants.swift并使用相应的值更新clientIDclientSecret

接下来,打开GitAPIManager.swift并在右括号之前添加以下方法:

func fetchAccessToken(
  accessCode: String,
  completion: @escaping (Bool) -> Void
) {
  // 1
  let headers: HTTPHeaders = [
    "Accept": "application/json"
  ]
  // 2
  let parameters = [
    "client_id": GitHubConstants.clientID,
    "client_secret": GitHubConstants.clientSecret,
    "code": accessCode
  ]
  // 3
  sessionManager.request(
    "https://github.com/login/oauth/access_token",
    method: .post,
    parameters: parameters,
    headers: headers)
    .responseDecodable(of: GitHubAccessToken.self) { response in
      guard let cred = response.value else {
        return completion(false)
      }
      TokenManager.shared.saveAccessToken(gitToken: cred)
      completion(true)
    }
}

以下是分步细分:

要了解有关使用钥匙串和存储安全信息的更多信息,请阅读KeyChain Services API Tutorial for Passwords in Swift

打开LoginViewController.swift。 在getGitHubIdentity()中,替换//TODO: Call to fetch access token will be added here为以下内容:

GitAPIManager.shared.fetchAccessToken(accessCode: value) { [self] isSuccess in
  if !isSuccess {
    print("Error fetching access token")
  }
  navigationController?.popViewController(animated: true)
}

在这里,您使用临时码进行调用以获取访问token。 响应成功后,控制器将显示存储库列表。

现在打开RepositoriesViewController.swift。 在viewDidLoad()中,删除以下行:

loginButton.isHidden = true

这将显示登录按钮。 构建并运行。

点击Login以登录。浏览器随后将您重定向到该应用程序,并且“登录”按钮将变为注销。 您会在控制台中看到访问access tokenscope

很好! 现在是时候获取您的存储库了。


Fetching User Repositories

打开GitAPIManager.swift。 在GitAPIManager中,添加以下方法:

func fetchUserRepositories(completion: @escaping ([Repository]) -> Void) {
  //1
  let url = "https://api.github.com/user/repos"
  //2
  let parameters = ["per_page": 100]
  //3
  sessionManager.request(url, parameters: parameters)
    .responseDecodable(of: [Repository].self) { response in
      guard let items = response.value else {
        return completion([])
      }
      completion(items)
    }
}

这是您添加的内容:

接下来,打开RepositoriesViewController.swift并找到fetchAndDisplayUserRepositories()。 替换//TODO: Add more here..为:

//1
loadingIndicator.startAnimating()
//2
GitAPIManager.shared.fetchUserRepositories { [self] repositories in
  //3
  self.repositories = repositories
  loadingIndicator.stopAnimating()
  tableView.reloadData()
}

以下是代码细分:

默认情况下,Alamofire在主队列上调用响应处理程序。 因此,您无需添加代码即可切换到主线程来更新UI。

构建并运行。

清单是空的! 检查Xcode控制台,您会看到401未经授权的请求。

您必须在标头中传递access token以进行授权。 您可以在GitAPIManager的fetchUserRepositories(completion :)内添加一个Authentication标头。 但是,为每个请求单独添加标头的过程可能会重复。

为了避免这种情况,Alamofire提供了RequestInterceptor,该协议可启用强大的按会话和按请求的功能。


Request Overview

在深入研究RequestInterceptor之前,您应该了解Request的不同类型。

AlamofireRequest是所有请求的超类。 有几种类型:

每个请求均以initialized状态开始。 它可以在其生命周期中被suspended, resumedcanceled。 请求以finished状态结束。

目前,您正在使用DataRequest来获取存储库。 现在,您将使用RequestInterceptor拦截请求。


RequestInterceptor Overview

AlamofireRequestInterceptor包含两个协议:RequestAdapterRequestRetrier

通过RequestAdapter,您可以在发送每个请求之前对其进行检查和更改。 当每个请求都包含一个Authorization header时,这是理想的选择。

RequestRetrier重试遇到错误的请求。

1. Integrating RequestInterceptor

Networking中,创建一个名为GitRequestInterceptor.swift的新Swift文件。 打开文件并添加:

import Alamofire

class GitRequestInterceptor: RequestInterceptor {
  //1
  let retryLimit = 5
  let retryDelay: TimeInterval = 10
  //2
  func adapt(
    _ urlRequest: URLRequest,
    for session: Session,
    completion: @escaping (Result<URLRequest, Error>) -> Void
  ) {
    var urlRequest = urlRequest
    if let token = TokenManager.shared.fetchAccessToken() {
      urlRequest.setValue("token \(token)", forHTTPHeaderField: "Authorization")
    }
    completion(.success(urlRequest))
  }
  //3
  func retry(
    _ request: Request,
    for session: Session,
    dueTo error: Error,
    completion: @escaping (RetryResult) -> Void
  ) {
    let response = request.task?.response as? HTTPURLResponse
    //Retry for 5xx status codes
    if 
      let statusCode = response?.statusCode,
      (500...599).contains(statusCode),
      request.retryCount < retryLimit {
        completion(.retryWithDelay(retryDelay))
    } else {
      return completion(.doNotRetry)
    }
  }
}

以下是分步细分:

该方法检查并调整请求。由于completion handler是异步的,因此此方法可以在发出请求之前从网络或磁盘中获取token

在这里,您从钥匙串中获取token并将其添加到Authorization header中。 GitHub OAuth Appsaccess token没有到期时间。但是,授权该应用程序的用户可以通过GitHub设置将其撤消。

在这里,您检查响应码是否包含5xx错误码。当服务器无法满足有效请求时,它将返回5xx码。例如,当服务关闭以进行维护时,您可能会获得503错误代码。

如果错误包含5xx错误代码,则请求将以retryDelay中指定的延迟重试,前提是计数在retryLimit之内。

打开GitAPIManager.swift。在sessionManagerlet networkLogger = GitNetworkLogger()下面添加以下代码:

let interceptor = GitRequestInterceptor()

在这里,您将interceptor定义为GitRequestInterceptor的实例。 在sessionManager中将Session初始化替换为以下内容:

return Session(
  configuration: configuration,
  interceptor: interceptor,
  eventMonitors: [networkLogger])

使用此代码,您可以在Session的构造函数中传递新创建的interceptor。 现在,通过GitRequestInterceptor实例拦截了属于sessionManager的所有请求。 构建并运行。

现在,您将看到从GitHub帐户获取的存储库。


Routing Requests and URLRequestConvertible

到目前为止,在发出网络请求时,您已经为每个请求提供了URL路径,HTTP方法和查询参数。 随着应用程序大小的增长,必须使用一些常见的模式来构建网络堆栈。 Router设计模式通过定义每个请求的路由和组件来提供帮助。

Networking中,打开GitRouter.swift。 您将看到到目前为止所提出的所有请求,并在枚举中捕获为不同的情况。 使用此GitRouter构造您的请求。

将以下扩展名添加到GitRouter.swift的末尾:

//1
extension GitRouter: URLRequestConvertible {
  func asURLRequest() throws -> URLRequest {
    //2
    let url = try baseURL.asURL().appendingPathComponent(path)
    var request = URLRequest(url: url)
    request.method = method
    //3
    if method == .get {
      request = try URLEncodedFormParameterEncoder()
        .encode(parameters, into: request)
    } else if method == .post {
      request = try JSONParameterEncoder().encode(parameters, into: request)
      request.setValue("application/json", forHTTPHeaderField: "Accept")
    }
    return request
  }
}

这是一个细分:

现在,打开GitAPIManager.swift。 您将更新所有请求方法以使用GitRouter

fetchCommits(for:completion :)中,删除以let url =开头的行。 现在,将以下内容替换为sessionManager.request(url)以使用新路由器:

sessionManager.request(GitRouter.fetchCommits(repository))

searchRepositories(query:completion :)中,删除sessionManager.request ...之前的所有内容。现在,将sessionManager.request(url,parameters:queryParameters)替换为:

sessionManager.request(GitRouter.searchRepositories(query))

同样,在fetchAccessToken(accessCode:completion :)中,删除sessionManager.request ...之前的所有内容。现在,将sessionManager.request(...)调用替换为:

sessionManager.request(GitRouter.fetchAccessToken(accessCode))

最后,在fetchUserRepositories(completion :)中,删除sessionManager.request(url,parameters:parameters)之前的所有内容,并将该行替换为:

sessionManager.request(GitRouter.fetchUserRepositories)

您将删除在每个方法中本地声明的URL,查询参数和标头,因为您不再需要它们。GitRouter为每个请求构造URLRequests。 构建并运行。

除了基础请求使用GitRouter之外,您将看到存储库像以前一样加载。

到目前为止,该应用程序运行良好。 有了良好的网络,结果几乎是即时的。 但是网络是不可预测的野兽。

了解何时无法访问网络并在您的应用中通知用户,这一点很重要。 AlamofireNetworkReachabilityManager随时为您服务!


Network Reachability

Networking组中,打开GitNetworkReachability.swift。 在右括号之前添加以下内容:

// 1
let reachabilityManager = NetworkReachabilityManager(host: "www.google.com")
// 2
func startNetworkMonitoring() {
  reachabilityManager?.startListening { status in
    switch status {
    case .notReachable:
      self.showOfflineAlert()
    case .reachable(.cellular):
      self.dismissOfflineAlert()
    case .reachable(.ethernetOrWiFi):
      self.dismissOfflineAlert()
    case .unknown:
      print("Unknown network state")
    }
  }
}

GitNetworkReachability提供了一个单例。 它包括显示和关闭弹窗的功能。 这是您添加的内容:

现在,打开AppDelegate.swift。 在return true之前,在application(_:didFinishLaunchingWithOptions :)中添加以下内容:

GitNetworkReachability.shared.startNetworkMonitoring()

在这里,您可以在GitNetworkReachability上调用startNetworkMonitoring()以在应用启动时开始侦听网络可达性状态。

构建并运行。 应用启动后,请关闭网络。

该应用程序会在无法访问网络时向用户显示弹窗,并在可访问网络时将其关闭。 以用户为中心,真是太好了!

注意:您应该在真实设备上测试与网络可达性相关的功能,因为可达性可能无法在模拟器上正常工作。 您可以通过阅读此GitHub post来了解有关此问题的更多信息。

有时显示弹窗不是理想的体验。 相反,您可能希望在没有网络时显示以前获取的应用程序数据。 AlamofireResponseCacher在这里为您提供帮助。


Caching Using ResponseCacher

打开GitAPIManager.swift。 在sessionManager中删除以下配置选项:

configuration.timeoutIntervalForRequest = 30
configuration.waitsForConnectivity = true

在这里,您删除了timeoutIntervalForRequestwaitsForConnectivity配置选项,因此该应用程序无需等待网络连接。

sessionManagerlet configuration = URLSessionConfiguration.af.default下面添加:

//1
configuration.requestCachePolicy = .returnCacheDataElseLoad
//2
let responseCacher = ResponseCacher(behavior: .modify { _, response in
  let userInfo = ["date": Date()]
  return CachedURLResponse(
    response: response.response,
    data: response.data,
    userInfo: userInfo,
    storagePolicy: .allowed)
})

这是一个细分:

现在,如下所示在sessionManager中更新Session初始化:

return Session(
  configuration: configuration,
  interceptor: interceptor,
  cachedResponseHandler: responseCacher,
  eventMonitors: [networkLogger])

在这里,您将Session的构造函数中的responseCacher作为cachedResponseHandler传递。 这使responseCacher处理Session中所有请求的缓存行为。

打开AppDelegate.swift。 注释掉以下可启动网络监视的代码行:

GitNetworkReachability.shared.startNetworkMonitoring()

这样可以防止应用程序离线时显示No Network弹窗。 关闭模拟器或设备上的网络访问。 构建并运行。

您将看到从缓存加载的存储库。

恭喜你! 您现在是Alamofire专业人士。

在此Alamofire教程中,您学习了如何:

要了解更多信息,请查看Alamofire高级用法文档Alamofire advanced usage documentation

后记

本篇主要讲述了Alamofire框架的基本概览,感兴趣的给个赞或者关注~~~

上一篇下一篇

猜你喜欢

热点阅读