iOS Merkezileştirilmiş ve Ayrılmış Ağ Oluşturma: Singleton Sınıfıyla AFNetworking Eğitimi
Yayınlanan: 2022-03-11iOS mimari desenleri söz konusu olduğunda, Model-View-Controller (MVC) tasarım deseni, bir uygulamanın kod tabanının uzun ömürlülüğü ve sürdürülebilirliği için harikadır. Sınıfları birbirinden ayırarak çeşitli gereksinimleri desteklemek için sınıfların kolayca yeniden kullanılmasına veya değiştirilmesine olanak tanır. Bu, Nesne Yönelimli Programlamanın (OOP) avantajlarını en üst düzeye çıkarmaya yardımcı olur.
Bu iOS uygulama mimarisi mikro düzeyde (bir uygulamanın ayrı ekranları/bölümleri) iyi çalışsa da, uygulamanız büyüdükçe kendinizi birden çok modele benzer işlevler eklerken bulabilirsiniz. Ağ oluşturma gibi durumlarda, ortak mantığı model sınıflarınızdan tekil yardımcı sınıflara taşımak daha iyi bir yaklaşım olabilir. Bu AFNetworking iOS eğitiminde, mikro düzeyde MVC bileşenlerinden ayrıştırılmış, ayrıştırılmış mimari uygulamanız boyunca yeniden kullanılabilen merkezi bir tekli ağ oluşturma nesnesinin nasıl kurulacağını öğreteceğim.
iOS Ağı İle İlgili Sorun
Apple, kullanımı kolay iOS SDK'larında mobil donanımı yönetmenin karmaşıklıklarının çoğunu soyutlama konusunda harika bir iş çıkardı, ancak ağ oluşturma, Bluetooth, OpenGL ve multimedya işleme gibi bazı durumlarda, sınıfları tutma amaçları nedeniyle hantal olabilir. SDK'lar esnek. Neyse ki, zengin iOS geliştirici topluluğu, uygulama tasarımını ve yapısını basitleştirmek amacıyla en yaygın kullanım durumlarını basitleştirmek için üst düzey çerçeveler oluşturmuştur. ios uygulama mimarisinin en iyi uygulamalarını kullanan iyi bir programcı, hangi araçları kullanacağını, neden kullanacağını ve kendi araçlarınızı ve sınıflarınızı sıfırdan yazmanın ne zaman daha iyi olduğunu bilir.
AFNetworking harika bir ağ oluşturma örneğidir ve geliştiricinin günlük görevlerini basitleştiren en yaygın kullanılan açık kaynak çerçevelerinden biridir. RESTful API ağını basitleştirir ve başarı, ilerleme ve başarısızlık tamamlama bloklarıyla modüler istek/yanıt kalıpları oluşturur. Bu, geliştirici tarafından uygulanan temsilci yöntemlerine ve özel istek/bağlantı ayarlarına olan ihtiyacı ortadan kaldırır ve herhangi bir sınıfa çok hızlı bir şekilde dahil edilebilir.
AFNetworking ile İlgili Sorun
AFNetworking harikadır, ancak modülerliği, parçalanmış şekillerde kullanılmasına da yol açabilir. Yaygın verimsiz uygulamalar şunları içerebilir:
Tek bir görünüm denetleyicisinde benzer yöntemleri ve özellikleri kullanan birden çok ağ isteği
Birden çok görünüm denetleyicisinde, senkronizasyondan çıkabilen dağıtılmış ortak değişkenlere yol açan neredeyse aynı istekler
Bir sınıfla ilgili olmayan veriler için bir sınıftaki ağ istekleri
Sınırlı sayıda görüntüleme, uygulanması gereken birkaç API çağrısı ve sık sık değişmesi muhtemel olmayan uygulamalar için bu çok önemli olmayabilir. Ancak, büyük olasılıkla büyük düşünüyorsunuz ve uzun yıllar boyunca planlanmış güncellemeleriniz var. Durumunuz ikincisiyse, muhtemelen aşağıdakileri halletmeniz gerekecek:
Bir uygulamanın birden çok neslini desteklemek için API sürümü oluşturma
Yeteneği genişletmek için yeni parametrelerin eklenmesi veya mevcut parametrelerde zaman içinde değişiklikler
Tamamen yeni API'lerin uygulanması
Ağ oluşturma kodunuz, kod tabanınızın her yerine dağılmışsa, bu artık potansiyel bir kabustur. Umarım en azından bazı parametreleriniz ortak bir başlıkta statik olarak tanımlanmış olur, ancak o zaman bile en küçük değişiklikler için bir düzine sınıfa dokunabilirsiniz.
AFNetworking Sınırlamalarını Nasıl Ele Alırız?
İsteklerin, yanıtların ve parametrelerinin işlenmesini merkezileştirmek için bir ağ tektonu oluşturun.
Tek bir nesne, sınıfının kaynaklarına genel bir erişim noktası sağlar. Singleton'lar, bazı genel hizmet veya kaynaklar sunan sınıflar gibi, bu tek kontrol noktasının istendiği durumlarda kullanılır. Global örneği bir fabrika yöntemiyle bir singleton sınıfından elde edersiniz. - Elma
Bu nedenle, bir singleton, uygulamanın ömrü boyunca var olan bir uygulamada yalnızca bir örneğine sahip olacağınız bir sınıftır. Ek olarak, yalnızca bir örnek olduğunu bildiğimiz için, yöntemlerine veya özelliklerine erişmesi gereken diğer herhangi bir sınıf tarafından kolayca erişilebilir.
İşte bu yüzden ağ oluşturmak için bir singleton kullanmalıyız:
Statik olarak başlatıldığından, bir kez oluşturulduktan sonra, ona erişmeye çalışan herhangi bir sınıf için mevcut olan aynı yöntemlere ve özelliklere sahip olacaktır. Garip senkronizasyon sorunları veya bir sınıfın yanlış örneğinden veri isteme şansı yoktur.
API çağrılarınızı bir hız sınırının altında kalacak şekilde sınırlayabilirsiniz (örneğin, API isteklerinizi saniyede beşin altında tutmanız gerektiğinde).
Ana bilgisayar adı, bağlantı noktası numaraları, uç noktalar, API sürümü, cihaz türü, kalıcı kimlikler, ekran boyutu vb. gibi statik özellikler, tek bir değişikliğin tüm ağ isteklerini etkilemesi için bir arada bulunabilir.
Ortak özellikler, birçok ağ isteği arasında yeniden kullanılabilir.
Singleton nesnesi, somutlaştırılıncaya kadar hafızada yer kaplamaz. Bu, cihaza sahip olmayan bir Chromecast'e video yayınlama gibi bazı kullanıcıların asla ihtiyaç duymayabileceği çok özel kullanım durumlarına sahip tekil kullanıcılar için yararlı olabilir.
Ağ istekleri, görünümlerden ve denetleyicilerden tamamen ayrılabilir, böylece görünümler ve denetleyiciler yok edildikten sonra bile devam edebilirler.
Ağ günlüğü, merkezileştirilebilir ve basitleştirilebilir.
Uyarılar gibi yaygın başarısızlık olayları, tüm istekler için yeniden kullanılabilir.
Böyle bir singleton'ın ana yapısı, basit üst düzey statik özellik değişiklikleriyle birden fazla projede yeniden kullanılabilir.
Singleton kullanmamak için bazı nedenler:
Tek bir sınıfta birden fazla sorumluluk sağlamak için aşırı kullanılabilirler. Örneğin, video işleme yöntemleri, ağ oluşturma yöntemleriyle veya kullanıcı durumu yöntemleriyle karıştırılabilir. Bu muhtemelen zayıf bir tasarım uygulaması olacaktır ve anlaşılması zor bir koda yol açacaktır. Bunun yerine, belirli sorumluluklara sahip birden çok singleton oluşturulmalıdır.
Singleton'lar alt sınıflanamaz.
Singleton'lar bağımlılıkları gizleyebilir ve böylece daha az modüler hale gelebilir. Örneğin, bir singleton kaldırılırsa ve bir sınıfta singletonun içe aktardığı bir içe aktarma eksikse, bu ileride sorunlara yol açabilir (özellikle harici kitaplık bağımlılıkları varsa).
Bir sınıf, başka bir sınıfta beklenmeyen uzun işlemler sırasında paylaşılan özellikleri tekil olarak değiştirebilir. Bu konuda uygun şekilde düşünülmeden, sonuçlar değişebilir.
Singleton'daki bellek sızıntıları, singleton'ın kendisi hiçbir zaman ayrılmadığından önemli bir sorun haline gelebilir.
Ancak, iOS uygulama mimarisi en iyi uygulamaları kullanılarak bu olumsuzluklar azaltılabilir. Birkaç en iyi uygulama şunları içerir:
Her singleton tek bir sorumluluğu üstlenmelidir.
Yüksek doğruluğa ihtiyacınız varsa, birden çok sınıf veya iş parçacığı tarafından hızla değiştirilecek verileri depolamak için tekilleri kullanmayın.
Kullanılabilir bağımlılıklara dayalı olarak özellikleri etkinleştirmek/devre dışı bırakmak için tekil tonlar oluşturun.
Uygulamanızın ömrü boyunca devam edeceğinden (manuel olarak yönetilmedikçe) büyük miktarda veriyi tekil özelliklerde saklamayın.
AFNetworking ile Basit Bir Singleton Örneği
İlk olarak, ön koşul olarak projenize AFNetworking'i ekleyin. En basit yaklaşım Cocoapod'lardır ve talimatlar GitHub sayfasında bulunur.
Hazır başlamışken, UIAlertController+Blocks
ve MBProgressHUD
(yine CocoaPods ile kolayca eklenir) eklemenizi öneririm. Bunlar açıkça isteğe bağlıdır, ancak bu, ilerlemeyi ve uyarıları AppDelegate penceresinde tekli olarak uygulamak istemeniz durumunda büyük ölçüde basitleştirecektir.
AFNetworking
eklendikten sonra, NSObject
öğesinin bir alt sınıfı olarak NetworkManager
adlı yeni bir Cocoa Touch Sınıfı oluşturarak başlayın. Yöneticiye erişmek için bir sınıf yöntemi ekleyin. NetworkManager.h
dosyanız aşağıdaki kod gibi görünmelidir:
#import <Foundation/Foundation.h> #import “AFNetworking.h” @interface NetworkManager : NSObject + (id)sharedManager; @end
Ardından, singleton için temel başlatma yöntemlerini uygulayın ve AFNetworking başlığını içe aktarın. Sınıf uygulamanız aşağıdaki gibi görünmelidir (NOT: Bu, Otomatik Referans Sayımı kullandığınızı varsayar):
#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
Harika! Şimdi pişiriyoruz ve özellikler ve yöntemler eklemeye hazırız. Bir singleton'a nasıl erişileceğini anlamak için hızlı bir test olarak, NetworkManager.h
aşağıdakileri ekleyelim:
@property NSString *appID; - (void)test;
Ve NetworkManager.m
için aşağıdakiler:
#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); }
Ardından ana ViewController.m
dosyamızda (veya sahip olduğunuz her neyse), NetworkManager.h
dosyasını içe aktarın ve ardından viewDidLoad
ekleyin:
[[NetworkManager sharedManager] test];
Uygulamayı başlatın ve çıktıda aşağıdakileri görmelisiniz:
Testing our the networking singleton for appID: 1, HOST: http://www.apitesting.dev/, and PORT: 80
Tamam, muhtemelen #define
, static const ve @property
aynı anda bu şekilde karıştırmazsınız, ancak seçeneklerinizi netlik için göstermeniz yeterlidir. "static const", tür güvenliği için daha iyi bir bildirimdir ancak #define
, makro kullanımına izin verdiği için dize oluşturmada yararlı olabilir. Değeri ne olursa olsun, bu senaryoda kısalık için #define
kullanıyorum. İşaretçiler kullanmadığınız sürece, bu bildirim yaklaşımları arasında pratikte çok fazla fark yoktur.
Artık #defines
, sabitler, özellikler ve yöntemleri anladığınıza göre, bunları kaldırabilir ve daha alakalı örneklere geçebiliriz.
Bir Ağ Örneği
Kullanıcının herhangi bir şeye erişmek için oturum açması gereken bir uygulama düşünün. Uygulama başlatıldığında, bir kimlik doğrulama jetonu kaydedip kaydetmediğimizi kontrol edeceğiz ve eğer öyleyse, jetonun süresinin dolup dolmadığını görmek için API'mize bir GET isteği gerçekleştireceğiz.

AppDelegate.m
, belirtecimiz için bir varsayılan kaydedelim:
+ (void)initialize { NSDictionary *defaults = [NSDictionary dictionaryWithObjectsAndKeys:@"", @"token", nil]; [[NSUserDefaults standardUserDefaults] registerDefaults:defaults]; }
NetworkManager'a bir belirteç kontrolü ekleyeceğiz ve tamamlama blokları aracılığıyla kontrol hakkında geri bildirim alacağız. Bu tamamlama bloklarını dilediğiniz gibi tasarlayabilirsiniz. Bu örnekte, yanıt nesnesi verileriyle başarıyı ve hata yanıtı dizesiyle ve bir durum koduyla başarısızlığı kullanıyorum. Not: Analitikte bir değerin arttırılması gibi alıcı taraf için önemli değilse, başarısızlık isteğe bağlı olarak dışarıda bırakılabilir.
NetworkManager.h
@interface
üstünde:
typedef void (^NetworkManagerSuccess)(id responseObject); typedef void (^NetworkManagerFailure)(NSString *failureReason, NSInteger statusCode);
@interface'de:
@property (atomik olmayan, güçlü) AFHTTPSessionManager *networkingManager;
- (void)tokenCheckWithSuccess:(NetworkManagerSuccess)success failure:(NetworkManagerFailure)failure;
NetworkManager.m:
BASE_URL'mizi tanımlayın:
#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]
Kimliği doğrulanmış isteklerin yanı sıra ayrıştırma hatalarını daha basit hale getirmek için birkaç yardımcı yöntem ekleyeceğiz (bu örnek bir JSON web belirteci kullanır):
- (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"; }
MBProgressHUD eklediyseniz, burada kullanılabilir:
#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; } }
Ve jeton kontrol talebimiz:
- (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); } }]; }
Şimdi ViewController.m viewWillAppear yönteminde bu tekil yöntemi çağıracağız. İsteğin basitliğine ve Görünüm Denetleyicisi tarafındaki küçük uygulamaya dikkat edin.
[[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]; }];
Bu kadar! Bu parçacığın, başlatma sırasında kimlik doğrulamasını kontrol etmesi gereken herhangi bir uygulamada sanal olarak nasıl kullanılabileceğine dikkat edin.
Benzer şekilde, oturum açmak için bir POST isteğini işleyebiliriz: NetworkManager.h:
- (void)authenticateWithEmail:(NSString*)email password:(NSString*)password success:(NetworkManagerSuccess)success failure:(NetworkManagerFailure)failure;
NetworkManager.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); } } }
Burada süslü olabilir ve AppDelegate penceresindeki AlertController+Blocks ile uyarılar ekleyebilir veya hata nesnelerini görünüm denetleyicisine geri gönderebiliriz. Ek olarak, kullanıcı kimlik bilgilerini buraya kaydedebilir veya bunun yerine görünüm denetleyicisinin bunu halletmesine izin verebiliriz. Genellikle, NetworkManager ile doğrudan iletişim kurabilen kimlik bilgilerini ve izinleri işleyen ayrı bir UserManager singleton'u uygularım (kişisel tercih).
Bir kez daha, görünüm denetleyicisi tarafı çok basittir:
- (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 }]; }
Hata! API'yi sürümlendirmeyi ve cihaz türünü göndermeyi unuttuk. Ek olarak, “/checktoken” olan uç noktayı “/token” olarak güncelledik. Ağımızı merkezileştirdiğimiz için, bunu güncellemek çok kolay. Kodumuzu incelememize gerek yok. Bu parametreleri tüm isteklerde kullanacağımız için bir yardımcı oluşturacağız.
#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; }
Gelecekte buna kolayca herhangi bir sayıda ortak parametre eklenebilir. Ardından, belirteç kontrolümüzü güncelleyebilir ve aşağıdaki gibi yöntemleri doğrulayabiliriz:
… 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) {
AFNetworking Eğitimimizi Tamamlıyoruz
Burada duracağız, ancak gördüğünüz gibi, ortak ağ oluşturma parametrelerini ve yöntemlerini tek bir yöneticide merkezileştirdik, bu da görünüm denetleyicisi uygulamalarımızı büyük ölçüde basitleştirdi. Gelecekteki güncellemeler basit ve hızlı olacak ve en önemlisi, ağımızı kullanıcı deneyiminden ayıracak. Tasarım ekibi bir dahaki sefere bir UI/UX revizyonu istediğinde, işimizin ağ oluşturma tarafında zaten yapıldığını bileceğiz!
Bu makalede bir ağ bağlantısına odaklandık, ancak aynı ilkeler aşağıdakiler gibi diğer birçok merkezi işleve uygulanabilir:
- Kullanıcı durumunu ve izinlerini işleme
- Dokunma eylemlerinin uygulama navigasyonuna yönlendirilmesi
- Video ve Ses Yönetimi
- Analitik
- Bildirimler
- çevre birimleri
- ve çok daha fazlası…
Ayrıca bir iOS uygulama mimarisine de odaklandık ancak bu, Android ve hatta JavaScript'e kadar kolayca genişletilebilir. Bonus olarak, yüksek düzeyde tanımlanmış ve işlev odaklı kod oluşturarak, uygulamaları yeni platformlara taşımayı çok daha hızlı bir iş haline getirir.
Özetlemek gerekirse, yukarıdaki ağ oluşturma örneği gibi temel tekil yöntemleri oluşturmak için erken proje planlamasında biraz fazladan zaman harcayarak, gelecekteki kodunuz daha temiz, daha basit ve daha sürdürülebilir olabilir.