Computer >> Máy Tính >  >> Điện thoại thông minh >> iPhone

Cách thực hiện cuộc gọi API trong Swift

Nếu bạn đang muốn trở thành nhà phát triển iOS, có một số kỹ năng cơ bản đáng biết. Đầu tiên, điều quan trọng là phải làm quen với việc tạo chế độ xem bảng. Thứ hai, bạn nên biết cách điền dữ liệu vào các chế độ xem bảng đó. Thứ ba, thật tuyệt nếu bạn có thể tìm nạp dữ liệu từ API và sử dụng dữ liệu này trong chế độ xem bảng của mình.

Điểm thứ ba là những gì chúng tôi sẽ đề cập trong bài viết này. Kể từ khi ra đời Codable trong Swift 4, việc gọi API dễ dàng hơn nhiều. Trước đây hầu hết mọi người đều sử dụng các pod như Alamofire và SwiftyJson (bạn có thể đọc về cách thực hiện điều đó tại đây). Bây giờ cách Swift đã đẹp hơn nhiều, vì vậy không có lý do gì để tải xuống một nhóm.

Hãy xem qua một số khối xây dựng thường được sử dụng để thực hiện lệnh gọi API. Chúng tôi sẽ đề cập đến những khái niệm này trước tiên, vì chúng là những phần quan trọng để hiểu cách thực hiện lệnh gọi API.

  • Trình xử lý hoàn thành
  • URLSession
  • DispatchQueue
  • Duy trì các chu kỳ

Cuối cùng, chúng tôi sẽ tập hợp tất cả lại với nhau. Tôi sẽ sử dụng API Star Wars mã nguồn mở để xây dựng dự án này. Bạn có thể xem mã dự án đầy đủ của tôi trên GitHub.

Cảnh báo từ chối trách nhiệm:Tôi mới làm quen với việc viết mã và phần lớn là tự học. Xin lỗi nếu tôi trình bày sai một số khái niệm.

Các trình xử lý hoàn thành

Cách thực hiện cuộc gọi API trong Swift
Bệnh nhân đáng thương Pheobe

Bạn còn nhớ tập phim Những người bạn, nơi Pheobe dán mắt vào điện thoại trong nhiều ngày chờ đợi để nói chuyện với bộ phận chăm sóc khách hàng? Hãy tưởng tượng nếu ngay khi bắt đầu cuộc điện thoại đó, một người đáng yêu tên là Pip nói:"Cảm ơn bạn đã gọi điện. Tôi không biết bạn sẽ phải chờ bao lâu, nhưng tôi sẽ gọi lại cho bạn khi chúng tôi sẵn sàng cho bạn." Nó sẽ không vui bằng, nhưng Pip đang đề nghị trở thành người xử lý hoàn thành cho Pheobe.

Bạn sử dụng trình xử lý hoàn thành trong một hàm khi bạn biết rằng hàm đó sẽ mất một lúc để hoàn thành. Bạn không biết bao lâu, và bạn không muốn tạm dừng cuộc sống của mình để đợi nó kết thúc. Vì vậy, bạn yêu cầu Pip gõ nhẹ vào vai bạn khi cô ấy sẵn sàng đưa ra câu trả lời cho bạn. Bằng cách đó, bạn có thể tiếp tục cuộc sống của mình, làm một số việc vặt, đọc sách và xem TV. Khi Pip chạm vào vai bạn có câu trả lời, bạn có thể lấy câu trả lời của cô ấy và sử dụng nó.

Đây là những gì xảy ra với các lệnh gọi API. Bạn gửi một yêu cầu URL đến một máy chủ, yêu cầu nó cung cấp một số dữ liệu. Bạn hy vọng máy chủ trả lại dữ liệu nhanh chóng, nhưng bạn không biết sẽ mất bao lâu. Thay vì bắt người dùng của bạn kiên nhẫn đợi máy chủ cung cấp dữ liệu cho bạn, bạn sử dụng trình xử lý hoàn thành. Điều này có nghĩa là bạn có thể yêu cầu ứng dụng của mình tắt và làm những việc khác, chẳng hạn như tải phần còn lại của trang.

Bạn yêu cầu trình xử lý hoàn thành nhấn vào vai ứng dụng của bạn khi ứng dụng có thông tin bạn muốn. Bạn có thể chỉ định thông tin đó là gì. Bằng cách đó, khi ứng dụng của bạn được nhấn mạnh, nó có thể lấy thông tin từ trình xử lý hoàn thành và thực hiện điều gì đó với nó. Thông thường những gì bạn sẽ làm là tải lại chế độ xem bảng để dữ liệu hiển thị cho người dùng.

Dưới đây là một ví dụ về trình xử lý hoàn thành trông như thế nào. Ví dụ đầu tiên là tự thiết lập lệnh gọi API:

func fetchFilms(completionHandler: @escaping ([Film]) -> Void) {
  // Setup the variable lotsOfFilms
  var lotsOfFilms: [Film]
  
  // Call the API with some code
  
  // Using data from the API, assign a value to lotsOfFilms  
  
  // Give the completion handler the variable, lotsOfFilms
  completionHandler(lotsOfFilms)
}
Một hàm sử dụng trình xử lý hoàn thành

Bây giờ chúng ta muốn gọi hàm fetchFilms . Một số điều cần lưu ý:

  • Bạn không cần tham chiếu completionHandler khi bạn gọi hàm. Lần duy nhất bạn tham khảo completionHandler nằm bên trong khai báo hàm.
  • Trình xử lý hoàn thành sẽ cung cấp cho chúng tôi một số dữ liệu để sử dụng. Dựa trên hàm chúng tôi đã viết ở trên, chúng tôi biết rằng dữ liệu sẽ thuộc loại [Film] . Chúng ta cần đặt tên cho dữ liệu để chúng ta có thể tham khảo nó. Dưới đây tôi đang sử dụng tên films , nhưng nó có thể là randomData hoặc bất kỳ tên biến nào khác mà tôi muốn.

Mã sẽ trông giống như sau:

fetchFilms() { (films) in
  // Do something with the data the completion handler returns 
  print(films)
}
Triển khai một hàm với trình xử lý hoàn thành

URLSession

URLSession giống như người quản lý của một đội. Người quản lý không làm bất cứ điều gì một mình. Công việc của cô ấy là chia sẻ công việc với những người trong nhóm của mình và họ sẽ hoàn thành công việc. Nhóm của cô ấy là dataTasks . Mỗi khi bạn cần một số dữ liệu, hãy viết cho sếp và sử dụng URLSession.shared.dataTask .

Bạn có thể cung cấp dataTask các loại thông tin khác nhau để giúp bạn đạt được mục tiêu của mình. Cung cấp thông tin cho dataTask được gọi là khởi tạo. Tôi khởi tạo dataTasks của mình với các URL. dataTasks cũng sử dụng các trình xử lý hoàn thành như một phần của quá trình khởi tạo của chúng. Đây là một ví dụ:

let url = URL(string: "https://www.swapi.co/api/films")

let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in 
  // your code here
})

task.resume()
Cách sử dụng URLSession để tìm nạp một số dữ liệu

dataTasks sử dụng các trình xử lý hoàn thành và chúng luôn trả về các loại thông tin giống nhau:data , responseerror . Bạn có thể đặt các tên khác nhau cho các loại dữ liệu này, chẳng hạn như (data, res, err) hoặc (someData, someResponse, someError) . Vì lợi ích của quy ước, tốt nhất bạn nên gắn bó với một cái gì đó rõ ràng thay vì đi giả mạo với các tên biến mới.

Hãy bắt đầu với error . Nếu dataTask trả về error , bạn sẽ muốn biết điều đó từ trước. Nó có nghĩa là bạn có thể hướng mã của mình để xử lý lỗi một cách duyên dáng. Điều đó cũng có nghĩa là bạn sẽ không bận tâm đến việc cố gắng đọc dữ liệu và làm gì đó với nó vì có lỗi khi trả lại dữ liệu.

Dưới đây tôi đang xử lý lỗi thực sự đơn giản bằng cách in lỗi vào bảng điều khiển và thoát khỏi chức năng. Có nhiều cách khác mà bạn có thể xử lý lỗi nếu muốn. Hãy nghĩ về mức độ cơ bản của dữ liệu này đối với ứng dụng của bạn. Ví dụ:nếu bạn có một ứng dụng ngân hàng và lệnh gọi API này hiển thị cho người dùng số dư của họ, thì bạn có thể muốn xử lý lỗi bằng cách trình bày một phương thức cho người dùng rằng, "Xin lỗi, chúng tôi đang gặp sự cố ngay bây giờ. Vui lòng thử lại sau. "

if let error = error {
  print("Error accessing swapi.co: /(error)")
  return
}
Xử lý lỗi

Tiếp theo, chúng ta xem xét phản hồi. Bạn có thể truyền phản hồi thành httpResponse . Bằng cách đó, bạn có thể xem mã trạng thái và đưa ra một số quyết định dựa trên mã. Ví dụ:nếu mã trạng thái là 404, thì bạn biết rằng trang không được tìm thấy.

Đoạn mã dưới đây sử dụng guard để kiểm tra xem có hai thứ tồn tại hay không. Nếu cả hai đều tồn tại, thì nó cho phép mã tiếp tục đến câu lệnh tiếp theo sau guard mệnh đề. Nếu một trong hai câu lệnh không thành công, thì chúng ta thoát khỏi hàm. Đây là trường hợp sử dụng điển hình của guard mệnh đề. Bạn mong đợi mã sau điều khoản bảo vệ là dòng chảy những ngày hạnh phúc (tức là dòng chảy dễ dàng và không có lỗi).

  guard let httpResponse = response as? HTTPURLResponse,
            (200...299).contains(httpResponse.statusCode) else {
    print("Error with the response, unexpected status code: \(response)")
    return
  }

Cuối cùng bạn tự xử lý dữ liệu. Lưu ý rằng chúng tôi chưa sử dụng trình xử lý hoàn thành cho error hoặc response . Đó là bởi vì trình xử lý hoàn thành đang đợi dữ liệu từ API. Nếu nó không đến phần dữ liệu của mã, thì không cần gọi trình xử lý.

Đối với dữ liệu, chúng tôi đang sử dụng JSONDecoder để phân tích cú pháp dữ liệu một cách tốt đẹp. Điều này khá tiện lợi, nhưng yêu cầu bạn phải thiết lập một mô hình. Mô hình của chúng tôi được gọi là FilmSummary . Nếu JSONDecoder mới đối với bạn, sau đó hãy xem trực tuyến để biết cách sử dụng nó và cách sử dụng Codable . Nó thực sự đơn giản trong Swift 4 trở lên so với Swift 3 ngày.

Trong đoạn mã dưới đây, trước tiên chúng tôi kiểm tra xem dữ liệu có tồn tại hay không. Chúng tôi khá chắc chắn rằng nó sẽ tồn tại, vì không có lỗi và không có phản hồi HTTP lạ. Thứ hai, chúng tôi kiểm tra xem chúng tôi có thể phân tích dữ liệu chúng tôi nhận được theo cách chúng tôi mong đợi hay không. Nếu có thể, chúng tôi sẽ trả lại bản tóm tắt phim cho trình xử lý hoàn thành. Đề phòng trường hợp không có dữ liệu nào để trả về từ API, chúng tôi có một kế hoạch dự phòng cho mảng trống.

if let data = data,
        let filmSummary = try? JSONDecoder().decode(FilmSummary.self, from: data) {
        completionHandler(filmSummary.results ?? [])
      }

Vì vậy, mã đầy đủ cho lệnh gọi API trông như sau:

func fetchFilms(completionHandler: @escaping ([Film]) -> Void) {
    let url = URL(string: domainUrlString + "films/")!

    let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
      if let error = error {
        print("Error with fetching films: \(error)")
        return
      }
      
      guard let httpResponse = response as? HTTPURLResponse,
            (200...299).contains(httpResponse.statusCode) else {
        print("Error with the response, unexpected status code: \(response)")
        return
      }

      if let data = data,
        let filmSummary = try? JSONDecoder().decode(FilmSummary.self, from: data) {
        completionHandler(filmSummary.results ?? [])
      }
    })
    task.resume()
  }

Lưu giữ các chu kỳ

NB:Tôi rất mới hiểu về các chu kỳ lưu giữ! Đây là ý chính của những gì tôi đã nghiên cứu trực tuyến.

Các chu kỳ lưu giữ rất quan trọng để quản lý bộ nhớ. Về cơ bản, bạn muốn ứng dụng của mình dọn dẹp những phần bộ nhớ mà nó không cần nữa. Tôi cho rằng điều này làm cho ứng dụng hoạt động hiệu quả hơn.

Có rất nhiều cách mà Swift giúp bạn thực hiện việc này một cách tự động. Tuy nhiên, có nhiều cách mà bạn có thể vô tình lưu giữ mã trong ứng dụng của mình. Chu kỳ lưu giữ có nghĩa là ứng dụng của bạn sẽ luôn lưu giữ bộ nhớ cho một đoạn mã nhất định. Nói chung, nó xảy ra khi bạn có hai thứ có điểm mạnh đối với nhau.

Để giải quyết vấn đề này, mọi người thường sử dụng weak . Khi một mặt của mã là weak , bạn không có chu kỳ lưu giữ và ứng dụng của bạn sẽ có thể giải phóng bộ nhớ.

Đối với mục đích của chúng tôi, một mẫu phổ biến là sử dụng [weak self] khi gọi API. Điều này đảm bảo rằng khi trình xử lý hoàn thành trả về một số mã, ứng dụng có thể giải phóng bộ nhớ.

fetchFilms { [weak self] (films) in
  // code in here
}

DispatchQueue

Xcode sử dụng các luồng khác nhau để thực thi mã song song. Lợi thế của nhiều chủ đề có nghĩa là bạn không bị mắc kẹt khi chờ đợi một việc hoàn thành trước khi bạn có thể chuyển sang việc tiếp theo. Hy vọng rằng bạn có thể bắt đầu thấy các liên kết đến các trình xử lý hoàn thành tại đây.

Các luồng này dường như còn được gọi là hàng đợi gửi. Các lệnh gọi API được xử lý trên một hàng đợi, thường là hàng đợi trong nền. Khi bạn có dữ liệu từ lệnh gọi API của mình, rất có thể bạn sẽ muốn hiển thị dữ liệu đó cho người dùng. Điều đó có nghĩa là bạn sẽ muốn làm mới chế độ xem bảng của mình.

Chế độ xem bảng là một phần của giao diện người dùng và tất cả các thao tác trên giao diện người dùng phải được thực hiện trong hàng đợi điều phối chính. Điều này có nghĩa là ở đâu đó trong tệp trình điều khiển chế độ xem của bạn, thường là một phần của viewDidLoad , bạn nên có một chút mã để cho chế độ xem bảng của bạn làm mới.

Chúng tôi chỉ muốn chế độ xem bảng làm mới khi nó có một số dữ liệu mới từ API. Điều này có nghĩa là chúng tôi sẽ sử dụng một trình xử lý hoàn thành để gõ nhẹ vào vai chúng tôi và cho chúng tôi biết khi nào lệnh gọi API đó kết thúc. Chúng tôi sẽ đợi cho đến khi nhấn đó trước khi chúng tôi làm mới bảng.

Mã sẽ giống như sau:

fetchFilms { [weak self] (films) in
  self.films = films

  // Reload the table view using the main dispatch queue
  DispatchQueue.main.async {
    tableView.reloadData()
  }
}

viewDidLoad so với viewDidAppear

Cuối cùng, bạn cần quyết định nơi gọi fetchfilms của mình hàm số. Nó sẽ nằm bên trong một bộ điều khiển chế độ xem sẽ sử dụng dữ liệu từ API. Có hai nơi rõ ràng bạn có thể thực hiện lệnh gọi API này. Một bên trong viewDidLoad và cái kia bên trong viewDidAppear .

Đây là hai trạng thái khác nhau cho ứng dụng của bạn. Sự hiểu biết của tôi là viewDidLoad được gọi là lần đầu tiên bạn tải lên chế độ xem đó ở nền trước. viewDidAppear được gọi mỗi khi bạn quay lại chế độ xem đó, chẳng hạn như khi bạn nhấn nút quay lại để quay lại chế độ xem.

Nếu bạn mong đợi dữ liệu của mình thay đổi trong khoảng thời gian mà người dùng sẽ điều hướng đến và từ chế độ xem đó, thì bạn có thể muốn đặt lệnh gọi API của mình trong viewDidAppear . Tuy nhiên, tôi nghĩ rằng đối với hầu hết tất cả các ứng dụng, viewDidLoad là đủ. Apple đề xuất viewDidAppear cho tất cả các lệnh gọi API, nhưng điều đó có vẻ như quá mức cần thiết. Tôi tưởng tượng nó sẽ làm cho ứng dụng của bạn kém hiệu suất hơn vì nó thực hiện nhiều lệnh gọi API hơn mà nó cần.

Kết hợp tất cả các bước

Đầu tiên:viết hàm gọi API. Ở trên, đây là fetchFilms . Điều này sẽ có một trình xử lý hoàn thành, sẽ trả về dữ liệu mà bạn quan tâm. Trong ví dụ của tôi, trình xử lý hoàn thành trả về một mảng phim.

Thứ hai:gọi chức năng này trong bộ điều khiển chế độ xem của bạn. Bạn làm điều này ở đây vì bạn muốn cập nhật chế độ xem dựa trên dữ liệu từ API. Trong ví dụ của tôi, tôi đang làm mới chế độ xem bảng sau khi API trả về dữ liệu.

Thứ ba:quyết định vị trí trong bộ điều khiển chế độ xem của bạn mà bạn muốn gọi hàm. Trong ví dụ của tôi, tôi gọi nó bằng viewDidLoad .

Thứ tư:quyết định phải làm gì với dữ liệu từ API. Trong ví dụ của tôi, tôi đang làm mới một chế độ xem bảng.

Bên trong NetworkManager.swift (chức năng này có thể được xác định trong bộ điều khiển chế độ xem của bạn nếu bạn muốn, nhưng tôi đang sử dụng mẫu MVVM).

func fetchFilms(completionHandler: @escaping ([Film]) -> Void) {
    let url = URL(string: domainUrlString + "films/")!

    let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
      if let error = error {
        print("Error with fetching films: \(error)")
        return
      }
      
      guard let httpResponse = response as? HTTPURLResponse,
            (200...299).contains(httpResponse.statusCode) else {
        print("Error with the response, unexpected status code: \(response)")
        return
      }

      if let data = data,
        let filmSummary = try? JSONDecoder().decode(FilmSummary.self, from: data) {
        completionHandler(filmSummary.results ?? [])
      }
    })
    task.resume()
  }

Bên trong FilmsViewController.swift :

final class FilmsViewController: UIViewController {
  private var films: [Film]?

  override func viewDidLoad() {
    super.viewDidLoad()
    
    NetworkManager().fetchFilms { [weak self] (films) in
      self?.films = films
      DispatchQueue.main.async {
        self?.tableView.reloadData()
      }
    }
  }
  
  // other code for the view controller
}

Chúa ơi, chúng tôi đã thành công! Cảm ơn vì đã gắn bó với tôi.