AFNetworking在iOS網路請求第三方庫中占據著半壁江山,前段時間將AFNetworking進行了3.0版本的遷移,運用面向對象的設計將代碼進行封裝整合,這篇文章主要為還在尋找AFNetworking集成代碼或者準備3.0遷移的各位童鞋們提供思路,同時自定義了字典模型轉換方法,需要的朋友也可 ...
AFNetworking在iOS網路請求第三方庫中占據著半壁江山,前段時間將AFNetworking進行了3.0版本的遷移,運用面向對象的設計將代碼進行封裝整合,這篇文章主要為還在尋找AFNetworking集成代碼或者準備3.0遷移的各位童鞋們提供思路,同時自定義了字典模型轉換方法,需要的朋友也可以作為參考,還望各位老司機批評指正!先上代碼框架圖:
1、DB數據訪問層,在AFNetworkingManager中我將AFNetworking的GET/POST/DELETE/PUT方法封裝,提供了以下介面:
1 /** 2 * get方式請求數據 3 * 4 * @param strUrl api地址 5 * @param headers 頭部信息 6 * @param params 可變參數信息 7 * @param class 返回數據模型類 8 * @param block block結果回調 9 * @param blockError block錯誤回調 10 * @param blockTimeOut block超時回調 11 */ 12 -(void)getDataFromUrl:(NSString *)strUrl 13 headers:(NSDictionary *)headers 14 params:(NSDictionary *)params 15 class:(Class)class 16 block:(CompletionLoad)block 17 blockError:(void (^)(JsonCommonResultBase *))blockError 18 blockTimeOut:(TimeOutCompletion)blockTimeOut; 19 20 /** 21 * post方式更新數據 22 * 23 * @param strUrl api地址 24 * @param headers 頭部信息 25 * @param params 可變參數信息 26 * @param class 返回數據模型類 27 * @param block block結果回調 28 * @param blockError block錯誤回調 29 * @param blockTimeOut block超時回調 30 */ 31 - (void)postDataFromUrl:(NSString*)strUrl 32 headers:(NSDictionary*)headers 33 params:(NSDictionary*)params 34 class:(Class)class 35 block:(CompletionLoad)block 36 blockError:(void(^)(JsonCommonResultBase *))blockError 37 blockTimeOut:(TimeOutCompletion)blockTimeOut; 38 39 /** 40 * put方式更新數據 41 * 42 * @param strUrl api地址 43 * @param headers 頭部信息 44 * @param params 可變參數信息 45 * @param class 返回數據模型類 46 * @param block block結果回調 47 * @param blockError block錯誤回調 48 * @param blockTimeOut block超時回調 49 */ 50 - (void)putDataFromUrl:(NSString*)strUrl 51 headers:(NSDictionary*)headers 52 params:(NSDictionary*)params 53 class:(Class)class 54 block:(CompletionLoad)block 55 blockError:(void(^)(id))blockError 56 blockTimeOut:(TimeOutCompletion)blockTimeOut; 57 58 /** 59 * delete方式刪除數據 60 * 61 * @param strUrl api地址 62 * @param headers 頭部信息 63 * @param params 可變參數信息 64 * @param class 返回數據模型類 65 * @param block block結果回調 66 * @param blockError block錯誤回調 67 * @param blockTimeOut block超時回調 68 */ 69 - (void)deleteDataFromUrl:(NSString*)strUrl 70 headers:(NSDictionary*)headers 71 params:(NSDictionary*)params 72 class:(Class)class 73 block:(CompletionLoad)block 74 blockError:(void(^)(JsonCommonResultBase *))blockError 75 blockTimeOut:(TimeOutCompletion)blockTimeOut; 76 77 /** 78 * post方式更新數據(上傳文件如圖片) 79 * 80 * @param strUrl api地址 81 * @param headers 頭部信息 82 * @param params 可變參數信息 83 * @param dataFiles 文件數據 84 * @param class 返回數據模型類 85 * @param block block結果回調 86 * @param progressBlock block進度回調 87 * @param blockError block錯誤回調 88 * @param blockTimeOut block超時回調 89 */ 90 - (void)uploadDataFromUrl:(NSString *)strUrl 91 headers:(NSDictionary *)headers 92 params:(NSDictionary *)params 93 dataFiles:(NSArray *)dataFiles 94 progressBlock:(LoadProgress)progressBlock 95 block:(CompletionLoad)block 96 class:(Class)class 97 blockError:(void (^)(JsonCommonResultBase *))blockError 98 blockTimeOut:(TimeOutCompletion)blockTimeOut;AFNetworking封裝
針對AFNetworking底層封裝AFNetworkingManager後,是不是就可以直接在Service調用GET/POST/DELETE/PUT介面訪問數據了呢?理論上是完全可以的,但是我們在實際開發中往往還需要自定義或者個性化一些效果如菊花等待框、陰影效果,提示文案等,所以本人建議在AFNetworkingManager基礎上再包裝一層專門用於Service對接,這樣的好處是Service層完全不必關心AFNetworking的封裝實現和序列化、授權等等問題,這樣也便於後續的維護與版本的升級,好了我們再看看對接Service的ZTHttpManager:
/** * get方式請求數據(內部封裝菊花等待框) * * @param strUrl api地址 * @param headers 頭部信息 * @param params 可變參數信息 * @param parentView 菊花等待框寄托視圖 * @param showShadow 是否陰影父視圖 * @param blockRtn block結果回調 * @param blockError block錯誤回調 * @param blockTimeOut block超時回調 */ - (void)getDataToUrl:(NSString*)strUrl headers:(NSDictionary*)headers params:(NSDictionary*)params parentView:(UIView*)parentView showShadow:(BOOL)showShadow class:(Class)class blockRtn:(void (^)(id ))blockRtn blockError:(void(^)(JsonCommonResultBase*))blockError blockTimeOut:(TimeOutCompletion)blockTimeOut; /** * post方式提交數據(內部封裝菊花等待框) * * @param strUrl api地址 * @param headers 頭部信息 * @param params 可變參數信息 * @param parentView 菊花等待框寄托父視圖 * @param showShadow 是否陰影父視圖 * @param blockRtn block結果回調 * @param blockError block錯誤回調 * @param blockTimeOut block超時回調 */ - (void)postDataToUrl:(NSString *)strUrl headers:(NSDictionary *)headers params:(NSDictionary *)params parentView:(UIView *)parentView showShadow:(BOOL)showShadow class:(Class)class blockRtn:(void (^)(id))blockRtn blockError:(void(^)(JsonCommonResultBase*))blockError blockTimeOut:(TimeOutCompletion)blockTimeOut; /** * delete方式刪除數據(內部封裝菊花等待框) * * @param strUrl api地址 * @param headers 頭部信息 * @param params 可變參數信息 * @param parentView 菊花等待框寄托視圖 * @param showShadow 是否陰影父視圖 * @param blockRtn block結果回調 * @param blockError block錯誤回調 * @param blockTimeOut block超時回調 */ - (void)deleteDataToUrl:(NSString*)strUrl headers:(NSDictionary*)headers params:(NSDictionary*)params parentView:(UIView*)parentView showShadow:(BOOL)showShadow class:(Class)class blockRtn:(void (^)(id ))blockRtn blockError:(void(^)(JsonCommonResultBase*))blockError blockTimeOut:(TimeOutCompletion)blockTimeOut; /** * put方式提交數據(內部封裝菊花等待框) * * @param strUrl api地址 * @param headers 頭部信息 * @param params 可變參數信息 * @param parentView 菊花等待框寄托視圖 * @param showShadow 是否陰影父視圖 * @param blockRtn block結果回調 * @param blockError block錯誤回調 * @param blockTimeOut block超時回調 */ - (void)putDataToUrl:(NSString*)strUrl headers:(NSDictionary*)headers params:(NSDictionary*)params parentView:(UIView*)parentView showShadow:(BOOL)showShadow class:(Class)class blockRtn:(void (^)(id))blockRtn blockError:(void(^)(JsonCommonResultBase*))blockError blockTimeOut:(TimeOutCompletion)blockTimeOut; /** * post方式上傳文件 * @param strUrl api地址 * @param parentView 菊花等待框寄托視圖 * @param showShadow 是否陰影父視圖 * @param blockRtn block結果回調 * @param blockError block錯誤回調 * @param blockTimeOut block超時回調 */ - (void)uploadImgFromUrl:(NSString*)strUrl fileItems:(NSArray*)fileItems parentView:(UIView*)parentView showShadow:(BOOL)showShadow headers:(NSDictionary *)headers params:(NSDictionary *)params class:(Class)class blockProgress:(void (^)(NSString *))blockProgress blockRtn:(void (^)(id))blockRtn blockError:(void(^)(JsonCommonResultBase*))blockError blockTimeOut:(TimeOutCompletion)blockTimeOut;ZTHttpManager封裝
好了,在這裡完成了DB層的代碼,訪問API就毫無壓力了!
2、模型基類JsonCommonResultBase/SerializationBaseModel
在這裡我要說明一點,這裡的模型基類是按照我們公司後臺返回的API格式自定義,不一定適合每個人,但是可以作為各位的參考,具體的API返回數據結構為:
{ page = { hasMore = 0; totalRows = 1; }; result = ( { annexInfoStatus = Pending; bsBeginTime = "2017-03-16 00:00:00"; bsEndTime = "2018-03-15 23:59:59"; bzBeginTime = "2017-03-16 00:00:00"; bzEndTime = "2018-03-15 23:59:59"; canRenewal = 0; company = { code = alltrust; id = 16; }; totalPremiums = "9632.09"; totalPremiumsText = "\U00a59,632.09"; verifyStatus = Verified; } ); status = 200; }API返回200數據格式
針對上述的數據格式,自定義的模型基類如下(BTW這裡多層級的數據轉化也是毫無壓力的,完全OK):
@interface SerializationBaseModel : NSObject<NSCopying> // 獲取列表字典 - (NSDictionary *)objectClassInArray; @end @implementation SerializationBaseModel - (id)copyWithZone:(NSZone *)zone{ return (id)self; } // 獲取列表字典 (具體result實現在子類中) - (NSDictionary *)objectClassInArray{ return nil; } @end序列化model基類 需要繼承NSCopying
#pragma mark - 分頁數據模型 @interface ApiPage : SerializationBaseModel /** * 總行數 */ @property (nonatomic,assign) NSInteger totalRows; /** * 是否還有數據 */ @property (nonatomic,assign) BOOL hasMore; @end #pragma mark - Json數據模型 @interface JsonCommonResultBase : SerializationBaseModel /** * 錯誤編碼 */ @property (nonatomic,copy) NSString *errCode; /** * 錯誤消息 */ @property (nonatomic,copy) NSString *errMsg; /** * 請求狀態 */ @property (nonatomic,assign) NSInteger status; /** * 分頁信息 */ @property (nonatomic,strong) ApiPage *page; @end //-------------------------線上是基類,線下是子類--------------------------------- #import "JsonCommonResultBase.h" @interface ZTTestModel : SerializationBaseModel @property (nonatomic,assign) NSInteger stuId; @property (nonatomic,copy) NSString *stuName; @property (nonatomic,copy) NSString *stuClassName; @property (nonatomic,copy) NSString *stuScore; @end @interface ZTTestModelResult : JsonCommonResultBase // BTW 實現多層級嵌套或者單數據模型也是沒有問題的,可參照上面代碼“api返回200數據格式”,這裡定義好就行了 @property (nonatomic,strong) NSMutableArray<ZTTestModel*> *result; @end // 重點來了,對於列表格式的result使用NSMutableArray<ZTTestModel*> *result類似定義後就搞定了嗎?那你就想太多了,我們還需要再實現代碼中添加字典轉化代碼,如下: #import "ZTTestModelResult.h" @implementation ZTTestModel @end @implementation ZTTestModelResult // 拿出小本本記好筆記,針對列表格式的result必須添加這段代碼,單對象數據不需要 - (NSDictionary *)objectClassInArray{ return @{@"result" : [ZTTestModel class]}; } @end返回數據基類與具體實現子類 那麼重點來了,我們知道AFNetworking調用API後返回的數據格式流為:NSData -> NSDictionary ,我們需要先將responseObject數據從NSData轉化為NSDictionary,這點在AFNetworkingManager中的已經寫明:
NSDictionary *resultDic = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableLeaves error:nil];
使用JSONObjectWithData後我們得到了NSDictionary格式的數據,但是我們需要的上面自定義對象模型數據啊!所以,你還需要一個序列化的工具在字典和模型間自如的轉化,它就是SerializationTools!
// 轉化為字典 - (NSMutableDictionary *)ToDictionary:(NSObject *)obj; // 獲取屬性數組 - (NSMutableDictionary *)ToKeyDictionary:(NSObject *)obj; // 字典填充對象 - (id)ToObjectOfDictionary:(NSDictionary *)dic class:(Class)class; // 轉化為字典 - (NSData *)ToNSData:(NSObject *)obj; // 字典填充對象 - (id)ToObjectOfData:(NSData *)data class:(Class)class; /** * 字典數組轉換為對象數組 * * @param class 對象類別名稱 * @param array 數組 * * @return 對象數組 */ -(NSMutableArray *)GetObjectListOfArray:(Class)class array:(NSArray *)array; /** * 對象數組轉換為字典數組 * * @param array 對象數組 * * @return 字典數組 */ -(NSArray *)GetDicListOfArray:(NSMutableArray *)array; /** * json格式字元串轉字典 * * @param jsonString <#jsonString description#> * * @return <#return value description#> */ + (NSDictionary *)dictionaryWithJsonString:(NSString *)jsonString; /** * 字典轉json字元串 * * @param dic <#dic description#> * * @return <#return value description#> */ + (NSString*)dictionaryToJson:(NSDictionary *)dic;序列化工具類
值得註意的是這段代碼,利用runtime獲取模型的欄位屬性(代碼段落,全部代碼請移步SerializationTools實現類.m)
// 獲取類成員變數和屬性列表,ivarsCnt為類成員數量 unsigned int ivarsCnt = 0; Ivar *ivars = class_copyIvarList(cls, &ivarsCnt); // 只獲取類屬性列表 // unsigned int outCount = 0; // objc_property_t *properties =class_copyPropertyList(cls, &outCount); // 遍歷成員變數列表,其中每個變數都是Ivar類型的結構體 for (const Ivar *p = ivars; p < ivars + ivarsCnt; ++p) { Ivar const ivar = *p; // 獲取變數名 NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)]; // 若此變數未在類結構體中聲明而只聲明為Property,則變數名加首碼 '_'下劃線 // 比如 @property(retain) NSString *abc;則 key == _abc; id value = [obj valueForKey:key]; if([key characterAtIndex:0]=='_'){ key=[key substringFromIndex:1]; } if (value) { [dictionaryFormat setObject:[value class] forKey:key]; } else { // 獲取類名 NSString *className = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)]; [dictionaryFormat setObject:[self GetClassByName:className] forKey:key]; } }
通過序列化工具我們就可以很輕鬆的將字典轉化為自定義模型了!
id resultJson = [[SerializationTools sharedInstance] ToObjectOfDictionary:self.resultDic class:class];
3、Service邏輯計算與服務提供
service層主要是根據實際業務需求來定義的介面,實現邏輯計算與業務組裝,在這裡我舉一個例子如:
// 返回成功的結果、返回失敗的信息、請求超時的錯誤都能通過block實現反向傳值 .h文件 // GET - (void)getStudentRecords:(NSInteger)offset length:(NSInteger)length parentView:(UIView *)parentView blockRtn:(void (^)(ZTTestModelResult *))blockRtn blockError:(void (^)(JsonCommonResultBase*))blockError blockTimeOut:(TimeOutCompletion)blockTimeOut; -------------------------------分割線------------------------------------- .m文件 - (void)getStudentRecords:(NSInteger)offset length:(NSInteger)length parentView:(UIView *)parentView blockRtn:(void (^)(ZTTestModelResult *))blockRtn blockError:(void (^)(JsonCommonResultBase*))blockError blockTimeOut:(TimeOutCompletion)blockTimeOut { NSString *strUrl = @"對接的api url"; [[ZTHttpManager sharedInstance] getDataToUrl:strUrl headers:nil params:nil parentView:parentView showShadow:YES class:[ZTTestModelResult class] blockRtn:blockRtn blockError:blockError blockTimeOut:blockTimeOut]; }
4、Controller層業務訴求
// Get [service getStudentRecords:0 length:10 parentView:self.view blockRtn:^(ZTTestModelResult *arryRtn) { // 回調成功,處理後續邏輯 } blockError:^(JsonCommonResultBase *error) { // show message about error } blockTimeOut:^{ // show message about timeout }]; // Post [service postTest:@"param1" param2:@"param2" param3:@"param3" parentView:self.view blockRtn:^(JsonCommonResultBase *result) { // 回調成功,處理後續邏輯 } blockError:^(JsonCommonResultBase *error) { // show message about error } blockTimeOut:^{ // show message about timeout }]; // upload [service uploadFileExpImage:[UIImage new] parentView:self.view blockProgress:^(NSString *progress) { // 上傳進度回調成功,處理顯示邏輯,註意刷新UI的操作一定要在主線程 dispatch_async(dispatch_get_main_queue(), ^{ // }); } blockRtn:^(JsonCommonResultBase *rtn) { // 回調成功,處理後續邏輯 } blockError:^(JsonCommonResultBase *error) { // show message about error } blockTimeOut:^{ // show message about timeout }];
綜上所述,一個基本的基於MVC的網路數據訪問框架就完成了!
github地址:https://github.com/BeckWang0912/ZTAFNetworking.git 喜歡的話,請給個星星,您的鼓勵是我寫作的動力,謝謝!
BTW:demo中主要是框架的搭建和AFNetworking的封裝,不保證完全適用每個人的項目,我只提供設計思路,您來個性化,有不足之處還希望各位老司機多多包涵,畢竟我也是在iOS路上奮鬥不久的小白。