
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 proyekSetiap 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 akhirPertama-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 HTTPEndPointType kami berisi beberapa protokol yang perlu kami buat permintaan. Mari kita lihat apa protokol-protokol ini.
HTTPMetodeBuat file,
beri nama
HTTPMetode dan letakkan di folder Layanan. Daftar ini akan digunakan untuk mengatur metode HTTP permintaan kami.
HTTPTaskBuat 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
HTTPHeadersHTTPHeaders hanyalah typealias untuk kamus. Anda dapat membuatnya di bagian atas file
HTTPTask Anda.
public typealias HTTPHeaders = [String:String]
Parameter & PengkodeanBuat 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.
JSONParameterEncoderBuat 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.
NetworkrouterBuat 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 .
RouterBuat 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.
MintaDi 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 permintaanKami 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 ParameterBuat 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 TambahanBuat fungsi
addAdditionalHeaders di Router.

Tambahkan saja semua header yang diperlukan ke permintaan.
BatalkanFungsi
batal akan terlihat sangat sederhana:
Contoh penggunaanSekarang mari kita coba menggunakan layer jaringan kita pada contoh nyata. Kami akan terhubung ke
TheMovieDB untuk menerima data untuk aplikasi kami.
MovieEndPointBuat 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:
MoviemodelUntuk 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 jaringanBuat 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 jaringanBuat enumerasi NetworkResponse di NetworkManager.

Kami menggunakan enumerasi ini ketika memproses tanggapan terhadap permintaan dan kami akan menampilkan pesan yang sesuai.
HasilBuat 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 PemrosesanBuat 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 jaringanJadi, 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
handleNetworkResponse6.
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 kecilAnda 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.
KesimpulanSekarang 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 .