Menulis lapisan jaringan Anda di Swift: pendekatan berorientasi protokol



Sekarang hampir 100% aplikasi menggunakan jaringan, sehingga semua orang dihadapkan pada organisasi dan penggunaan lapisan jaringan. Ada dua pendekatan utama untuk memecahkan masalah ini, itu baik menggunakan perpustakaan pihak ketiga, atau implementasi Anda sendiri dari lapisan jaringan. Pada artikel ini kita akan mempertimbangkan opsi kedua, dan mencoba menerapkan lapisan jaringan menggunakan semua fitur bahasa terbaru, menggunakan protokol dan enumerasi. Ini akan menyelamatkan proyek dari dependensi yang tidak perlu dalam bentuk perpustakaan tambahan. Mereka yang pernah melihat Moya akan segera mengenali banyak detail serupa dalam implementasi dan penggunaan, seperti itu, hanya saja kali ini kita akan melakukannya sendiri tanpa menyentuh Moya dan Alamofire.


Dalam panduan ini, kita akan melihat bagaimana mengimplementasikan lapisan jaringan pada Swift murni, tanpa menggunakan pustaka pihak ketiga. Setelah Anda meninjau artikel ini, kode Anda akan menjadi

  • berorientasi protokol
  • mudah digunakan
  • mudah digunakan
  • ketik aman
  • untuk titik akhir enum akan digunakan


Di bawah ini adalah contoh bagaimana tampilan lapisan jaringan kami setelah implementasi:



Dengan hanya menulis router.request (. Dan menggunakan semua kekuatan enumerasi, kita akan melihat semua opsi kueri yang mungkin dan parameternya.

Pertama, sedikit tentang struktur proyek

Setiap kali Anda membuat sesuatu yang baru, dan agar dapat dengan mudah memahami segala sesuatu di masa depan, sangat penting untuk mengatur dan menyusun semuanya dengan benar. Saya memegang keyakinan bahwa struktur folder yang tertata dengan baik adalah detail penting ketika membangun arsitektur aplikasi. Agar kita dapat mengatur semuanya dengan benar di folder, mari kita buat terlebih dahulu. Ini akan terlihat seperti struktur folder umum dalam proyek:



Protokol jenis akhir

Pertama-tama, kita perlu mendefinisikan protokol EndPointType kita. Protokol ini akan berisi semua informasi yang diperlukan untuk mengonfigurasi permintaan. Apa itu permintaan (titik akhir)? Intinya, ini adalah URLRequest dengan semua komponen terkait, seperti tajuk, parameter permintaan, badan permintaan. Protokol EndPointType adalah bagian terpenting dari implementasi lapisan jaringan kami. Mari kita buat file dan beri nama EndPointType . Letakkan file ini di folder Layanan (bukan di folder EndPoint, mengapa - ini akan dihapus sedikit kemudian)



Protokol HTTP

EndPointType kami berisi beberapa protokol yang perlu kami buat permintaan. Mari kita lihat apa protokol-protokol ini.

HTTPMetode

Buat file, beri nama HTTPMetode dan letakkan di folder Layanan. Daftar ini akan digunakan untuk mengatur metode HTTP permintaan kami.



HTTPTask
Buat file, beri nama HTTPTask dan letakkan di folder Layanan. HTTPTask bertanggung jawab untuk mengonfigurasi parameter permintaan tertentu. Anda dapat menambahkan sebanyak mungkin opsi kueri yang berbeda sesuai kebutuhan, tetapi saya, pada gilirannya, akan membuat kueri reguler, kueri dengan parameter, kueri dengan parameter dan header, jadi saya hanya akan melakukan tiga jenis kueri ini.



Di bagian selanjutnya, kita akan membahas Parameter dan bagaimana kita akan bekerja dengannya

HTTPHeaders

HTTPHeaders hanyalah typealias untuk kamus. Anda dapat membuatnya di bagian atas file HTTPTask Anda.

public typealias HTTPHeaders = [String:String] 


Parameter & Pengkodean

Buat file, beri nama ParameterEncoding dan letakkan di folder Encoding. Buat typealias untuk Parameter , itu lagi akan menjadi kamus reguler. Kami melakukan ini untuk membuat kode terlihat lebih mudah dimengerti dan dibaca.

 public typealias Parameters = [String:Any] 


Selanjutnya, tentukan protokol ParameterEncoder dengan fungsi enkode tunggal. Metode penyandian memiliki dua parameter: inout URLRequest dan Parameters . INOUT adalah kata kunci Swift yang mendefinisikan parameter fungsi sebagai referensi. Biasanya, parameter diteruskan ke fungsi sebagai nilai. Saat Anda menulis masukan sebelum parameter fungsi dalam panggilan, Anda menetapkan parameter ini sebagai jenis referensi. Untuk mempelajari lebih lanjut tentang argumen inout, Anda dapat mengikuti tautan ini. Singkatnya, inout memungkinkan Anda untuk mengubah nilai variabel itu sendiri, yang diteruskan ke fungsi, dan tidak hanya mendapatkan nilainya dalam parameter dan bekerja dengannya di dalam fungsi. Protokol ParameterEncoder akan diimplementasikan dalam JSONParameterEncoder dan di URLPameterEncoder .

 public protocol ParameterEncoder { static func encode(urlRequest: inout URLRequest, with parameters: Parameters) throws } 


ParameterEncoder berisi fungsi tunggal yang tugasnya menyandikan parameter. Metode ini dapat melempar kesalahan yang perlu ditangani, jadi kami menggunakan throw.

Mungkin juga bermanfaat untuk menghasilkan bukan kesalahan standar, tetapi kesalahan khusus. Selalu cukup sulit untuk mendekripsi apa yang diberikan Xcode kepada Anda. Ketika Anda memiliki semua kesalahan yang disesuaikan dan dijelaskan, Anda selalu tahu persis apa yang terjadi. Untuk melakukan ini, mari kita tentukan enumerasi yang diwarisi dari Kesalahan .



Buat file, beri nama URLParameterEncoder dan letakkan di folder Encoding .



Kode ini mengambil daftar parameter, mengonversi, dan memformatnya untuk digunakan sebagai parameter URL. Seperti yang Anda ketahui, beberapa karakter tidak diizinkan di URL. Parameter juga dipisahkan oleh simbol "&", jadi kita harus mengurus ini. Kami juga harus menetapkan nilai default untuk header jika mereka tidak diatur dalam permintaan.

Ini adalah bagian dari kode yang seharusnya dicakup oleh unit test. Membangun permintaan URL adalah kuncinya, jika tidak kita bisa memancing banyak kesalahan yang tidak perlu. Jika Anda menggunakan API terbuka, Anda jelas tidak ingin menggunakan volume permintaan penuh yang mungkin untuk pengujian yang gagal. Jika Anda ingin tahu lebih banyak tentang tes Unit, Anda bisa mulai dengan artikel ini.

JSONParameterEncoder

Buat file, beri nama JSONParameterEncoder dan letakkan di folder Encoding.



Semuanya sama seperti dalam kasus URLParameter , hanya di sini kita akan mengkonversi parameter untuk JSON dan kembali menambahkan parameter mendefinisikan encoding "application / json" ke header.

Networkrouter

Buat file, beri nama NetworkRouter dan letakkan di folder Layanan. Mari kita mulai dengan menentukan typealias untuk penutupan.

 public typealias NetworkRouterCompletion = (_ data: Data?,_ response: URLResponse?,_ error: Error?)->() 


Selanjutnya, kita mendefinisikan protokol NetworkRouter .



NetworkRouter memiliki EndPoint yang digunakan untuk permintaan, dan segera setelah permintaan selesai, hasil dari permintaan ini diteruskan ke penutupan NetworkRouterCompletion . Protokol juga memiliki fungsi batal , yang dapat digunakan untuk mengganggu permintaan bongkar-muat jangka panjang. Kami juga menggunakan jenis terkait di sini karena kami ingin Router kami mendukung semua jenis EndPointType . Tanpa menggunakan tipe terkait, router harus memiliki beberapa tipe spesifik yang mengimplementasikan EndPointType . Jika Anda ingin tahu lebih banyak tentang tipe terkait, Anda dapat membaca artikel ini .

Router

Buat file, beri nama Router dan taruh di folder Layanan. Kami mendeklarasikan variabel pribadi jenis URLSessionTask . Semua pekerjaan akan ada di sana. Kami menjadikannya pribadi karena kami tidak ingin siapa pun di luar dapat mengubahnya.



Minta

Di sini kita membuat URLSession menggunakan URLSession.share , ini adalah cara termudah untuk membuat. Tetapi ingat bahwa metode ini bukan satu-satunya. Anda dapat menggunakan konfigurasi URLSession yang lebih kompleks yang dapat mengubah perilakunya. Lebih lanjut tentang ini di artikel ini .

Permintaan dibuat dengan memanggil fungsi buildRequest. Panggilan fungsi dibungkus dengan do-try-catch, karena fungsi enkode di dalam buildRequest dapat melempar pengecualian. Respons , data, dan kesalahan diteruskan ke penyelesaian.



Bangun permintaan

Kami membuat permintaan kami menggunakan fungsi buildRequest . Fungsi ini bertanggung jawab untuk semua pekerjaan penting di lapisan jaringan kami. Pada dasarnya mengonversi EndPointType ke URLRequest . Dan begitu EndPoint berubah menjadi permintaan, kita bisa meneruskannya ke sesi . Banyak hal yang terjadi di sini, jadi mari kita lihat metodenya. Pertama, mari kita periksa metode buildRequest :

1. Kami menginisialisasi variabel permintaan URLRequest . Kami menetapkan URL basis kami di dalamnya dan menambahkan jalur permintaan khusus yang akan digunakan untuk itu.

2. Tetapkan request.httpMetode metode http dari EndPoint kami.

3. Kami membuat blok coba-coba-tangkap, karena pembuat enkode kami mungkin melakukan kesalahan. Dengan membuat satu blok do-try-catch besar, kami menghilangkan kebutuhan untuk membuat blok terpisah untuk setiap percobaan.

4. Di sakelar, periksa route.task .

5. Bergantung pada jenis tugas, kami memanggil encoder yang sesuai.



Konfigurasikan Parameter

Buat fungsi configureParameters di Router.



Fungsi ini bertanggung jawab untuk mengonversi parameter kueri kami. Karena API kami mengasumsikan penggunaan bodyParameters dalam bentuk JSON dan URLParameters yang dikonversi ke format URL, kami hanya meneruskan parameter yang sesuai ke fungsi konversi yang sesuai, yang kami jelaskan di awal artikel. Jika Anda menggunakan API yang menyertakan berbagai jenis penyandian, maka dalam hal ini saya akan merekomendasikan menambahkan HTTPTask dengan enumerasi tambahan dengan jenis penyandian. Daftar ini harus berisi semua jenis penyandian yang memungkinkan. Setelah itu, di configureParameters tambahkan satu argumen lagi dengan enumerasi ini. Bergantung pada nilainya, beralih menggunakan sakelar dan buat enkode yang Anda butuhkan.

Tambahkan Header Tambahan

Buat fungsi addAdditionalHeaders di Router.



Tambahkan saja semua header yang diperlukan ke permintaan.

Batalkan

Fungsi batal akan terlihat sangat sederhana:



Contoh penggunaan

Sekarang mari kita coba menggunakan layer jaringan kita pada contoh nyata. Kami akan terhubung ke TheMovieDB untuk menerima data untuk aplikasi kami.

MovieEndPoint

Buat file MovieEndPoint dan letakkan di folder EndPoint. MovieEndPoint sama dengan
dan TargetType di Moya. Di sini kita menerapkan EndPointType kita sendiri sebagai gantinya. Artikel yang menjelaskan cara menggunakan Moya untuk contoh serupa dapat ditemukan di tautan ini .

 import Foundation enum NetworkEnvironment { case qa case production case staging } public enum MovieApi { case recommended(id:Int) case popular(page:Int) case newMovies(page:Int) case video(id:Int) } extension MovieApi: EndPointType { var environmentBaseURL : String { switch NetworkManager.environment { case .production: return "https://api.themoviedb.org/3/movie/" case .qa: return "https://qa.themoviedb.org/3/movie/" case .staging: return "https://staging.themoviedb.org/3/movie/" } } var baseURL: URL { guard let url = URL(string: environmentBaseURL) else { fatalError("baseURL could not be configured.")} return url } var path: String { switch self { case .recommended(let id): return "\(id)/recommendations" case .popular: return "popular" case .newMovies: return "now_playing" case .video(let id): return "\(id)/videos" } } var httpMethod: HTTPMethod { return .get } var task: HTTPTask { switch self { case .newMovies(let page): return .requestParameters(bodyParameters: nil, urlParameters: ["page":page, "api_key":NetworkManager.MovieAPIKey]) default: return .request } } var headers: HTTPHeaders? { return nil } } 


Moviemodel

Untuk mengurai model data MovieModel dan JSON ke dalam model, protokol Decodable digunakan. Tempatkan file ini di folder Model .

Catatan : untuk perkenalan yang lebih rinci dengan protokol Codable, Decodable, dan Encodable, Anda dapat membaca artikel saya yang lain , yang menjelaskan secara rinci semua fitur untuk bekerja dengannya.

 import Foundation struct MovieApiResponse { let page: Int let numberOfResults: Int let numberOfPages: Int let movies: [Movie] } extension MovieApiResponse: Decodable { private enum MovieApiResponseCodingKeys: String, CodingKey { case page case numberOfResults = "total_results" case numberOfPages = "total_pages" case movies = "results" } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: MovieApiResponseCodingKeys.self) page = try container.decode(Int.self, forKey: .page) numberOfResults = try container.decode(Int.self, forKey: .numberOfResults) numberOfPages = try container.decode(Int.self, forKey: .numberOfPages) movies = try container.decode([Movie].self, forKey: .movies) } } struct Movie { let id: Int let posterPath: String let backdrop: String let title: String let releaseDate: String let rating: Double let overview: String } extension Movie: Decodable { enum MovieCodingKeys: String, CodingKey { case id case posterPath = "poster_path" case backdrop = "backdrop_path" case title case releaseDate = "release_date" case rating = "vote_average" case overview } init(from decoder: Decoder) throws { let movieContainer = try decoder.container(keyedBy: MovieCodingKeys.self) id = try movieContainer.decode(Int.self, forKey: .id) posterPath = try movieContainer.decode(String.self, forKey: .posterPath) backdrop = try movieContainer.decode(String.self, forKey: .backdrop) title = try movieContainer.decode(String.self, forKey: .title) releaseDate = try movieContainer.decode(String.self, forKey: .releaseDate) rating = try movieContainer.decode(Double.self, forKey: .rating) overview = try movieContainer.decode(String.self, forKey: .overview) } } 


Manajer jaringan

Buat file NetworkManager di folder Manajer. Saat ini, NetworkManager hanya berisi dua properti statis: kunci API dan enumerasi yang menjelaskan jenis server yang akan dihubungkan. NetworkManager juga berisi Router yang bertipe MovieApi .



Respon jaringan

Buat enumerasi NetworkResponse di NetworkManager.



Kami menggunakan enumerasi ini ketika memproses tanggapan terhadap permintaan dan kami akan menampilkan pesan yang sesuai.

Hasil

Buat enumerasi Hasil di NetworkManager.



Kami menggunakan Hasil untuk menentukan apakah permintaan itu berhasil atau tidak. Jika tidak, maka kami akan mengembalikan pesan kesalahan dengan alasan.

Meminta Respons Pemrosesan

Buat fungsi handleNetworkResponse . Fungsi ini mengambil satu argumen, seperti HTTPResponse, dan mengembalikan Hasil.



Dalam fungsi ini, tergantung pada kode status yang diterima dari HTTPResponse, kami mengembalikan pesan kesalahan atau tanda permintaan yang berhasil. Biasanya, kode dalam kisaran 200..299 berarti sukses.

Membuat permintaan jaringan

Jadi, kami telah melakukan segalanya untuk mulai menggunakan lapisan jaringan kami, mari kita coba membuat permintaan.

Kami akan meminta daftar film baru. Buat fungsi dan beri nama getNewMovies .



Mari kita lakukan langkah demi langkah:

1. Kami mendefinisikan metode getNewMovies dengan dua argumen: nomor halaman pagination dan handler penyelesaian, yang mengembalikan array opsional model Movie , atau kesalahan opsional.

2. Hubungi Router . Kami melewati nomor halaman dan menyelesaikan proses di penutupan.

3. URLSession mengembalikan kesalahan jika tidak ada jaringan atau tidak mungkin membuat permintaan dengan alasan apa pun. Harap dicatat bahwa ini bukan kesalahan API, kesalahan seperti itu terjadi pada klien dan biasanya terjadi karena kualitas koneksi Internet yang buruk.

4. Kita perlu memberikan tanggapan kepada HTTPURLResponse , karena kita perlu mengakses properti statusCode .

5. Nyatakan hasil dan inisialisasi dengan menggunakan metode handleNetworkResponse

6. Sukses berarti permintaan itu berhasil dan kami menerima respons yang diharapkan. Kemudian kami memeriksa apakah data datang dengan jawabannya, dan jika tidak, maka kami cukup mengakhiri metode ini melalui pengembalian.

7. Jika jawaban datang dengan data, maka perlu mem-parsing data yang diterima ke dalam model. Setelah itu, kami meneruskan array model yang dihasilkan ke penyelesaian.

8. Dalam hal terjadi kesalahan, teruskan kesalahan tersebut sampai selesai .

Itu saja, ini adalah bagaimana lapisan jaringan kita sendiri bekerja pada Swift murni, tanpa menggunakan dependensi dalam bentuk pod dan pustaka pihak ketiga. Untuk membuat permintaan uji api untuk mendapatkan daftar film, buat MainViewController dengan properti NetworkManager dan panggil metode getNewMovies melaluinya.

  class MainViewController: UIViewController { var networkManager: NetworkManager! init(networkManager: NetworkManager) { super.init(nibName: nil, bundle: nil) self.networkManager = networkManager } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .green networkManager.getNewMovies(page: 1) { movies, error in if let error = error { print(error) } if let movies = movies { print(movies) } } } } 


Bonus kecil

Anda mengalami situasi di Xcode ketika Anda tidak mengerti apa jenis placeholder yang digunakan di tempat tertentu? Misalnya, lihat kode yang baru saja kita tulis untuk Router .



Kami menentukan sendiri NetworkRouterCompletion , tetapi bahkan dalam kasus ini mudah untuk melupakan jenisnya dan bagaimana menggunakannya. Tapi Xcode tercinta kami mengurus semuanya, dan cukup dengan mengklik dua kali pada placeholder dan Xcode akan menggantikan tipe yang diinginkan.



Kesimpulan

Sekarang kami memiliki implementasi lapisan jaringan berorientasi protokol, yang sangat mudah digunakan dan yang selalu dapat Anda sesuaikan dengan kebutuhan Anda. Kami memahami fungsinya dan bagaimana semua mekanisme bekerja.

Anda dapat menemukan kode sumber di repositori ini .

Source: https://habr.com/ru/post/id443514/


All Articles