Jaringan Terpusat dan Terpisah iOS: Tutorial AFNetworking dengan Kelas Tunggal

Diterbitkan: 2022-03-11

Dalam hal pola arsitektur iOS, pola desain Model-View-Controller (MVC) sangat bagus untuk umur panjang dan pemeliharaan basis kode aplikasi. Hal ini memungkinkan kelas untuk dengan mudah digunakan kembali atau diganti untuk mendukung berbagai persyaratan dengan memisahkan mereka dari satu sama lain. Ini membantu memaksimalkan keuntungan Pemrograman Berorientasi Objek (OOP).

Meskipun arsitektur aplikasi iOS ini bekerja dengan baik pada tingkat mikro (setiap layar/bagian aplikasi), Anda mungkin menemukan diri Anda menambahkan fungsi serupa ke beberapa model seiring pertumbuhan aplikasi Anda. Dalam kasus seperti jaringan, memindahkan logika umum dari kelas model Anda ke kelas pembantu tunggal bisa menjadi pendekatan yang lebih baik. Dalam tutorial iOS AFNetworking ini, saya akan mengajari Anda cara menyiapkan objek jaringan tunggal terpusat yang, dipisahkan dari komponen MVC tingkat mikro, dapat digunakan kembali di seluruh aplikasi arsitektur yang dipisahkan.

Tutorial AFNetworking: Jaringan Terpusat dan Terpisah dengan Singleton

Masalah dengan Jaringan iOS

Apple telah melakukan pekerjaan yang baik dalam mengabstraksi banyak kerumitan pengelolaan perangkat keras seluler dengan SDK iOS yang mudah digunakan, tetapi dalam beberapa kasus, seperti jaringan, Bluetooth, OpenGL, dan pemrosesan multimedia, kelas dapat menjadi rumit karena tujuannya untuk tetap SDK fleksibel. Untungnya, komunitas pengembang iOS yang kaya telah menciptakan kerangka kerja tingkat tinggi untuk menyederhanakan kasus penggunaan yang paling umum dalam upaya menyederhanakan desain dan struktur aplikasi. Seorang programmer yang baik, menggunakan praktik terbaik arsitektur aplikasi ios, tahu alat mana yang digunakan, mengapa menggunakannya, dan kapan lebih baik menulis alat dan kelas Anda sendiri dari awal.

AFNetworking adalah contoh jaringan yang bagus, dan salah satu kerangka kerja open source yang paling umum digunakan, yang menyederhanakan tugas sehari-hari pengembang. Ini menyederhanakan jaringan RESTful API dan membuat pola permintaan/respons modular dengan blok penyelesaian sukses, kemajuan, dan kegagalan. Ini menghilangkan kebutuhan akan metode delegasi yang diterapkan pengembang dan pengaturan permintaan/koneksi khusus dan dapat dimasukkan ke dalam kelas apa pun dengan sangat cepat.

Masalah dengan AFNetworking

AFNetworking sangat bagus, tetapi modularitasnya juga dapat menyebabkan penggunaannya dalam cara yang terfragmentasi. Implementasi umum yang tidak efisien dapat mencakup:

  • Beberapa permintaan jaringan menggunakan metode dan properti serupa dalam pengontrol tampilan tunggal

  • Permintaan yang hampir identik di beberapa pengontrol tampilan yang mengarah ke variabel umum terdistribusi yang bisa tidak sinkron

  • Permintaan jaringan di kelas untuk data yang tidak terkait dengan kelas itu

Untuk aplikasi dengan jumlah tampilan terbatas, sedikit panggilan API untuk diimplementasikan, dan aplikasi yang cenderung tidak sering berubah, ini mungkin tidak terlalu menjadi perhatian. Namun, kemungkinan besar Anda berpikir besar dan merencanakan pembaruan selama bertahun-tahun. Jika kasus Anda adalah yang terakhir, kemungkinan besar Anda harus menangani:

  • Pembuatan versi API untuk mendukung beberapa generasi aplikasi

  • Penambahan parameter baru atau perubahan parameter yang ada dari waktu ke waktu untuk memperluas kemampuan

  • Implementasi API yang benar-benar baru

Jika kode jaringan Anda tersebar di seluruh basis kode Anda, ini sekarang berpotensi menjadi mimpi buruk. Mudah-mudahan, Anda memiliki setidaknya beberapa parameter yang didefinisikan secara statis di header umum, tetapi meskipun demikian Anda dapat menyentuh selusin kelas bahkan untuk perubahan yang paling kecil.

Bagaimana Kami Mengatasi Keterbatasan AFNetworking?

Buat jaringan tunggal untuk memusatkan penanganan permintaan, tanggapan, dan parameternya.

Objek tunggal menyediakan titik akses global ke sumber daya kelasnya. Lajang digunakan dalam situasi di mana titik kontrol tunggal ini diinginkan, seperti dengan kelas yang menawarkan beberapa layanan umum atau sumber daya. Anda mendapatkan instans global dari kelas tunggal melalui metode pabrik. - Apel

Jadi, singleton adalah kelas yang hanya akan Anda miliki satu instance dalam aplikasi yang ada selama masa pakai aplikasi. Selain itu, karena kita tahu hanya ada satu instance, instance tersebut mudah diakses oleh kelas lain mana pun yang perlu mengakses metode atau propertinya.

Inilah mengapa kita harus menggunakan singleton untuk jaringan:

  • Ini diinisialisasi secara statis sehingga, setelah dibuat, ia akan memiliki metode dan properti yang sama yang tersedia untuk setiap kelas yang mencoba mengaksesnya. Tidak ada peluang untuk masalah sinkronisasi yang aneh atau meminta data dari instance kelas yang salah.

  • Anda dapat membatasi panggilan API agar tetap di bawah batas kecepatan (misalnya, saat Anda harus mempertahankan permintaan API di bawah lima per detik).

  • Properti statis seperti nama host, nomor port, titik akhir, versi API, jenis perangkat, ID persisten, ukuran layar, dll. dapat ditempatkan bersama sehingga satu perubahan memengaruhi semua permintaan jaringan.

  • Properti umum dapat digunakan kembali di antara banyak permintaan jaringan.

  • Objek tunggal tidak mengambil memori sampai dipakai. Ini dapat berguna untuk lajang dengan kasus penggunaan yang sangat spesifik yang mungkin tidak diperlukan oleh beberapa pengguna, seperti menangani transmisi video ke Chromecast jika mereka tidak memiliki perangkat.

  • Permintaan jaringan dapat sepenuhnya dipisahkan dari tampilan dan pengontrol sehingga mereka dapat melanjutkan bahkan setelah tampilan dan pengontrol dihancurkan.

  • Pencatatan jaringan dapat dipusatkan dan disederhanakan.

  • Peristiwa umum untuk kegagalan seperti peringatan dapat digunakan kembali untuk semua permintaan.

  • Struktur utama dari singleton semacam itu dapat digunakan kembali pada banyak proyek dengan perubahan properti statis tingkat atas yang sederhana.

Beberapa alasan untuk tidak menggunakan lajang:

  • Mereka dapat digunakan secara berlebihan untuk memberikan banyak tanggung jawab dalam satu kelas. Misalnya, metode pemrosesan video dapat digabungkan dengan metode jaringan atau metode status pengguna. Ini kemungkinan akan menjadi praktik desain yang buruk dan mengarah pada kode yang sulit dipahami. Sebaliknya, beberapa lajang dengan tanggung jawab khusus harus dibuat.

  • Lajang tidak dapat disubklasifikasikan.

  • Lajang dapat menyembunyikan dependensi dan dengan demikian menjadi kurang modular. Misalnya, jika singleton dihapus dan kelas kehilangan impor yang diimpor oleh singleton, hal itu dapat menyebabkan masalah di masa mendatang (terutama jika ada dependensi perpustakaan eksternal).

  • Kelas dapat memodifikasi properti bersama di lajang selama operasi panjang yang tidak terduga di kelas lain. Tanpa pemikiran yang tepat dalam hal ini, hasil dapat bervariasi.

  • Kebocoran memori dalam singleton dapat menjadi masalah yang signifikan karena singleton itu sendiri tidak pernah dialokasikan.

Namun, dengan menggunakan praktik terbaik arsitektur aplikasi iOS, hal negatif ini dapat dikurangi. Beberapa praktik terbaik meliputi:

  • Setiap lajang harus menangani satu tanggung jawab.

  • Jangan gunakan singleton untuk menyimpan data yang akan diubah dengan cepat oleh beberapa kelas atau utas jika Anda membutuhkan akurasi tinggi.

  • Bangun lajang untuk mengaktifkan/menonaktifkan fitur berdasarkan dependensi yang tersedia.

  • Jangan menyimpan data dalam jumlah besar di properti tunggal karena akan tetap ada selama masa pakai aplikasi Anda (kecuali jika dikelola secara manual).

Contoh Singleton Sederhana dengan AFNetworking

Pertama, sebagai prasyarat, tambahkan AFNetworking ke proyek Anda. Pendekatan paling sederhana adalah melalui Cocoapods dan instruksi ditemukan di halaman GitHub-nya.

Saat Anda melakukannya, saya sarankan menambahkan UIAlertController+Blocks dan MBProgressHUD (sekali lagi dengan mudah ditambahkan dengan CocoaPods). Ini jelas opsional tetapi ini akan sangat menyederhanakan kemajuan dan peringatan jika Anda ingin menerapkannya di jendela AppDelegate.

Setelah AFNetworking ditambahkan, mulailah dengan membuat Kelas Cocoa Touch baru yang disebut NetworkManager sebagai subkelas dari NSObject . Tambahkan metode kelas untuk mengakses manajer. File NetworkManager.h Anda akan terlihat seperti kode di bawah ini:

 #import <Foundation/Foundation.h> #import “AFNetworking.h” @interface NetworkManager : NSObject + (id)sharedManager; @end

Selanjutnya, terapkan metode inisialisasi dasar untuk singleton dan impor header AFNetworking. Implementasi kelas Anda akan terlihat seperti berikut (CATATAN: Ini mengasumsikan Anda menggunakan Penghitungan Referensi Otomatis):

 #import "NetworkManager.h" @interface NetworkManager() @end @implementation NetworkManager #pragma mark - #pragma mark Constructors static NetworkManager *sharedManager = nil; + (NetworkManager*)sharedManager { static dispatch_once_t once; dispatch_once(&once, ^ { sharedManager = [[NetworkManager alloc] init]; }); return sharedManager; } - (id)init { if ((self = [super init])) { } return self; } @end

Besar! Sekarang kita sedang memasak dan siap untuk menambahkan properti dan metode. Sebagai tes cepat untuk memahami cara mengakses singleton, mari tambahkan yang berikut ini ke NetworkManager.h :

 @property NSString *appID; - (void)test;

Dan berikut ini ke NetworkManager.m :

 #define HOST @”http://www.apitesting.dev/” static const in port = 80; … @implementation NetworkManager … //Set an initial property to init: - (id)init { if ((self = [super init])) { self.appID = @”1”; } return self; } - (void)test { NSLog(@”Testing out the networking singleton for appID: %@, HOST: %@, and PORT: %d”, self.appID, HOST, port); }

Kemudian di file ViewController.m utama kami (atau apa pun yang Anda miliki), impor NetworkManager.h dan kemudian di viewDidLoad tambahkan:

 [[NetworkManager sharedManager] test];

Luncurkan aplikasi dan Anda akan melihat yang berikut di output:

Testing our the networking singleton for appID: 1, HOST: http://www.apitesting.dev/, and PORT: 80

Oke, jadi Anda mungkin tidak akan mencampur #define , static const, dan @property sekaligus seperti ini tetapi hanya menunjukkan untuk kejelasan opsi Anda. "static const" adalah deklarasi yang lebih baik untuk keamanan tipe tetapi #define dapat berguna dalam pembuatan string karena memungkinkan penggunaan makro. Untuk apa nilainya, saya menggunakan #define untuk singkatnya dalam skenario ini. Kecuali Anda menggunakan pointer, tidak ada banyak perbedaan dalam praktik antara pendekatan deklarasi ini.

Sekarang setelah Anda memahami #defines , konstanta, properti, dan metode, kami dapat menghapusnya dan beralih ke contoh yang lebih relevan.

Contoh Jaringan

Bayangkan sebuah aplikasi di mana pengguna harus masuk untuk mengakses apa pun. Setelah peluncuran aplikasi, kami akan memeriksa apakah kami telah menyimpan token otentikasi dan, jika demikian, melakukan permintaan GET ke API kami untuk melihat apakah token telah kedaluwarsa atau tidak.

Di AppDelegate.m , mari daftarkan default untuk token kita:

 + (void)initialize { NSDictionary *defaults = [NSDictionary dictionaryWithObjectsAndKeys:@"", @"token", nil]; [[NSUserDefaults standardUserDefaults] registerDefaults:defaults]; }

Kami akan menambahkan pemeriksaan token ke NetworkManager dan mendapatkan umpan balik tentang pemeriksaan tersebut melalui blok penyelesaian. Anda dapat mendesain blok penyelesaian ini sesuka Anda. Dalam contoh ini, saya menggunakan sukses dengan data objek respons dan kegagalan dengan string respons kesalahan dan kode status. Catatan: Kegagalan dapat diabaikan secara opsional jika tidak masalah bagi pihak penerima seperti menambah nilai dalam analitik.

NetworkManager.h

Di atas @interface :

 typedef void (^NetworkManagerSuccess)(id responseObject); typedef void (^NetworkManagerFailure)(NSString *failureReason, NSInteger statusCode);

Di @interface:

@property (nonatomik, kuat) AFHTTPSessionManager *networkingManager;

 - (void)tokenCheckWithSuccess:(NetworkManagerSuccess)success failure:(NetworkManagerFailure)failure;

Manajer Jaringan.m:

Tentukan BASE_URL kami:

 #define ENABLE_SSL 1 #define HOST @"http://www.apitesting.dev/" #define PROTOCOL (ENABLE_SSL ? @"https://" : @"http://") #define PORT @"80" #define BASE_URL [NSString stringWithFormat:@"%@%@:%@", PROTOCOL, HOST, PORT]

Kami akan menambahkan beberapa metode pembantu untuk membuat permintaan yang diautentikasi lebih sederhana serta kesalahan penguraian (contoh ini menggunakan token web JSON):

 - (AFHTTPSessionManager*)getNetworkingManagerWithToken:(NSString*)token { if (self.networkingManager == nil) { self.networkingManager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:BASE_URL]]; if (token != nil && [token length] > 0) { NSString *headerToken = [NSString stringWithFormat:@"%@ %@", @"JWT", token]; [self.networkingManager.requestSerializer setValue:headerToken forHTTPHeaderField:@"Authorization"]; // Example - [networkingManager.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; } self.networkingManager.requestSerializer = [AFJSONRequestSerializer serializer]; self.networkingManager.responseSerializer.acceptableContentTypes = [self.networkingManager.responseSerializer.acceptableContentTypes setByAddingObjectsFromArray:@[@"text/html", @"application/json", @"text/json"]]; self.networkingManager.securityPolicy = [self getSecurityPolicy]; } return self.networkingManager; } - (id)getSecurityPolicy { return [AFSecurityPolicy defaultPolicy]; /* Example - AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone]; [policy setAllowInvalidCertificates:YES]; [policy setValidatesDomainName:NO]; return policy; */ } - (NSString*)getError:(NSError*)error { if (error != nil) { NSData *errorData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey]; NSDictionary *responseObject = [NSJSONSerialization JSONObjectWithData: errorData options:kNilOptions error:nil]; if (responseObject != nil && [responseObject isKindOfClass:[NSDictionary class]] && [responseObject objectForKey:@"message"] != nil && [[responseObject objectForKey:@"message"] length] > 0) { return [responseObject objectForKey:@"message"]; } } return @"Server Error. Please try again later"; }

Jika Anda menambahkan MBProgressHUD, itu dapat digunakan di sini:

 #import "MBProgressHUD.h" @interface NetworkManager() @property (nonatomic, strong) MBProgressHUD *progressHUD; @end … - (void)showProgressHUD { [self hideProgressHUD]; self.progressHUD = [MBProgressHUD showHUDAddedTo:[[UIApplication sharedApplication] delegate].window animated:YES]; [self.progressHUD removeFromSuperViewOnHide]; self.progressHUD.bezelView.color = [UIColor colorWithWhite:0.0 alpha:1.0]; self.progressHUD.contentColor = [UIColor whiteColor]; } - (void)hideProgressHUD { if (self.progressHUD != nil) { [self.progressHUD hideAnimated:YES]; [self.progressHUD removeFromSuperview]; self.progressHUD = nil; } }

Dan permintaan cek token kami:

 - (void)tokenCheckWithSuccess:(NetworkManagerSuccess)success failure:(NetworkManagerFailure)failure { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSString *token = [defaults objectForKey:@"token"]; if (token == nil || [token length] == 0) { if (failure != nil) { failure(@"Invalid Token", -1); } return; } [self showProgressHUD]; NSMutableDictionary *params = [NSMutableDictionary dictionary]; [[self getNetworkingManagerWithToken:token] GET:@"/checktoken" parameters:params progress:nil success:^(NSURLSessionTask *task, id responseObject) { [self hideProgressHUD]; if (success != nil) { success(responseObject); } } failure:^(NSURLSessionTask *operation, NSError *error) { [self hideProgressHUD]; NSString *errorMessage = [self getError:error]; if (failure != nil) { failure(errorMessage, ((NSHTTPURLResponse*)operation.response).statusCode); } }]; }

Sekarang, dalam metode ViewController.m viewWillAppear, kita akan memanggil metode tunggal ini. Perhatikan kesederhanaan permintaan dan implementasi kecil di sisi View Controller.

 [[NetworkManager sharedManager] tokenCheckWithSuccess:^(id responseObject) { // Allow User Access and load content //[self loadContent]; } failure:^(NSString *failureReason, NSInteger statusCode) { // Logout user if logged in and deny access and show login view //[self showLoginView]; }];

Itu dia! Perhatikan bagaimana cuplikan ini dapat digunakan secara virtual di aplikasi apa pun yang perlu memeriksa autentikasi saat peluncuran.

Demikian pula, kami dapat menangani permintaan POST untuk login: NetworkManager.h:

 - (void)authenticateWithEmail:(NSString*)email password:(NSString*)password success:(NetworkManagerSuccess)success failure:(NetworkManagerFailure)failure;

Manajer Jaringan.m:

 - (void)authenticateWithEmail:(NSString*)email password:(NSString*)password success:(NetworkManagerSuccess)success failure:(NetworkManagerFailure)failure { if (email != nil && [email length] > 0 && password != nil && [password length] > 0) { [self showProgressHUD]; NSMutableDictionary *params = [NSMutableDictionary dictionary]; [params setObject:email forKey:@"email"]; [params setObject:password forKey:@"password"]; [[self getNetworkingManagerWithToken:nil] POST:@"/authenticate" parameters:params progress:nil success:^(NSURLSessionTask *task, id responseObject) { [self hideProgressHUD]; if (success != nil) { success(responseObject); } } failure:^(NSURLSessionTask *operation, NSError *error) { [self hideProgressHUD]; NSString *errorMessage = [self getError:error]; if (failure != nil) { failure(errorMessage, ((NSHTTPURLResponse*)operation.response).statusCode); } }]; } else { if (failure != nil) { failure(@"Email and Password Required", -1); } } }

Kita bisa menjadi mewah di sini dan menambahkan peringatan dengan AlertController+Blocks di jendela AppDelegate atau cukup mengirim objek kegagalan kembali ke pengontrol tampilan. Selain itu, kami dapat menyimpan kredensial pengguna di sini atau membiarkan pengontrol tampilan menanganinya. Biasanya, saya menerapkan singleton UserManager terpisah yang menangani kredensial dan izin yang dapat berkomunikasi dengan NetworkManager secara langsung (preferensi pribadi).

Sekali lagi, sisi pengontrol tampilan sangat sederhana:

 - (void)loginUser { NSString *email = @"[email protected]"; NSString *password = @"SomeSillyEasyPassword555"; [[NetworkManager sharedManager] authenticateWithEmail:email password:password success:^(id responseObject) { // Save User Credentials and show content } failure:^(NSString *failureReason, NSInteger statusCode) { // Explain to user why authentication failed }]; }

Ups! Kami lupa membuat versi API dan mengirim jenis perangkat. Selain itu, kami telah memperbarui titik akhir dari “/checktoken” menjadi “/token”. Karena kami memusatkan jaringan kami, ini sangat mudah untuk diperbarui. Kita tidak perlu menggali kode kita. Karena kami akan menggunakan parameter ini pada semua permintaan, kami akan membuat pembantu.

 #define API_VERSION @"1.0" #define DEVICE_TYPE UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? @"tablet" : @"phone" - (NSMutableDictionary*)getBaseParams { NSMutableDictionary *baseParams = [NSMutableDictionary dictionary]; [baseParams setObject:@"version" forKey:API_VERSION]; [baseParams setObject:@"device_type" forKey:DEVICE_TYPE]; return baseParams; }

Sejumlah parameter umum dapat dengan mudah ditambahkan ke ini di masa mendatang. Kemudian kami dapat memperbarui pemeriksaan token kami dan mengautentikasi metode seperti:

 … NSMutableDictionary *params = [self getBaseParams]; [[self getNetworkingManagerWithToken:token] GET:@"/checktoken" parameters:params progress:nil success:^(NSURLSessionTask *task, id responseObject) { … … NSMutableDictionary *params = [self getBaseParams]; [params setObject:email forKey:@"email"]; [params setObject:password forKey:@"password"]; [[self getNetworkingManagerWithToken:nil] POST:@"/authenticate" parameters:params progress:nil success:^(NSURLSessionTask *task, id responseObject) {

Mengakhiri Tutorial AFNetworking Kami

Kami akan berhenti di sini tetapi, seperti yang Anda lihat, kami telah memusatkan parameter dan metode jaringan umum dalam satu manajer tunggal, yang telah sangat menyederhanakan implementasi pengontrol tampilan kami. Pembaruan di masa mendatang akan sederhana dan cepat, dan yang terpenting, ini memisahkan jaringan kami dari pengalaman pengguna. Lain kali tim desain meminta perbaikan UI/UX, kami akan tahu bahwa pekerjaan kami sudah selesai di sisi jaringan!

Dalam artikel ini kami fokus pada jaringan tunggal tetapi prinsip yang sama ini dapat diterapkan ke banyak fungsi terpusat lainnya seperti:

  • Menangani status dan izin pengguna
  • Perutean tindakan sentuh ke navigasi aplikasi
  • Manajemen Video & Audio
  • Analitik
  • Notifikasi
  • Periferal
  • dan masih banyak lagi…

Kami juga berfokus pada arsitektur aplikasi iOS tetapi ini dapat dengan mudah diperluas ke Android dan bahkan JavaScript. Sebagai bonus, dengan membuat kode yang sangat terdefinisi dan berorientasi fungsi, itu membuat porting aplikasi ke platform baru menjadi tugas yang jauh lebih cepat.

Singkatnya, dengan menghabiskan sedikit waktu ekstra dalam perencanaan proyek awal untuk menetapkan metode kunci tunggal, seperti contoh jaringan di atas, kode masa depan Anda bisa lebih bersih, lebih sederhana, dan lebih mudah dipelihara.