AFNetworking但是iOS網絡開發的神器,大大簡便了操做.不過網絡但是重中之重,不能只會用AFNetworking.我以爲網絡開發首先要懂基本的理論,例如tcp/ip,http協議,以後要了解web的請求和響應,會使用蘋果自帶的NSURLSession,最後是把AFNetworking的源碼啃掉.php
一直以來網絡開發用的都是前面同事基於AFNetworking二次封裝好的框架,一直都沒什麼問題,也就沒往深處去了解.而後公司開始新項目了,iOS端由我負責,這但是個人第一次啊,從零開始,構建整個項目.這是個挑戰,心裏仍是有點小激動的.
python
輪子確定是不用重複造的,網絡框架就拿的老項目的,結果出現了兩個問題.web
爲了解決這個問題,從就沒用過的NSURLSession到http協議,追本溯源,終於解決了.json
關於http協議的理論就很少講了,主要就講使用POST方法傳遞數據時,發送的請求頭和請求體.數組
咱們提交的數據是什麼編碼方式服務端是不知道的,其實咱們徹底能夠自定義格式,只要服務端取到數據後能解析就能夠了.
通常服務端語言如 php、python 等,以及它們的 framework,都內置了自動解析常見數據格式的功能。因此咱們通常都用那幾種常見的數據格式,數據格式就是請求頭裏的Content-Type,服務端根據Content-Type來解析請求體裏的數據.
通常有四種最多見的方式:服務器
country=0&pro=1023&city=102301
--Boundary+A675D0398A56493A Content-Disposition: form-data; name="photoFiles"; filename="photoFiles.jpeg" Content-Type: image/jpeg
{"country":0,"pro":1023,"city":102301}
因此傳文件用multipart/form-data.
application/json和application/x-www-form-urlencoded差很少,可是application/json數據更加直觀,更適合結構更加複雜的數據.
我特意抓包了網易新聞等app,沒有文件的狀況下是application/json網絡
既然知道了請求頭,請求體.那麼給服務器端發送的請求按照上面的格式設置便可.app
//上傳一張圖片爲例 NSMutableURLRequest *request= [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:5.0f]; request.HTTPMethod=@"POST"; //數據體設置 NSMutableData *dataM=[NSMutableData data]; NSString *strTop=[NSString stringWithFormat:@"--%@\nContent-Disposition: form-data; name=\"file1\"; filename=\"%@\"\nContent-Type: %@\n\n",kBOUNDARY_STRING,fileName,@"image/jpg"]; NSString *strBottom=[NSString stringWithFormat:@"\n--%@--",kBOUNDARY_STRING]; NSString *filePath=[[NSBundle mainBundle] pathForResource:fileName ofType:nil]; NSData *fileData=[NSData dataWithContentsOfFile:filePath]; [dataM appendData:[strTop dataUsingEncoding:NSUTF8StringEncoding]]; [dataM appendData:fileData]; [dataM appendData:[strBottom dataUsingEncoding:NSUTF8StringEncoding]]; //經過請求頭設置 [request setValue:[NSString stringWithFormat:@"%lu",(unsigned long)dataM.length] forHTTPHeaderField:@"Content-Length"]; [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@",kBOUNDARY_STRING] forHTTPHeaderField:@"Content-Type"]; //設置數據體 request.HTTPBody=dataM;
AFNetworking就是基於NSMutableURLRequest,NSURLSession的封裝,很是好用.
以POST爲例,它有兩個方法:框架
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString parameters:(nullable id)parameters progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; - (nullable NSURLSessionDataTask *)POST:(NSString *)URLString parameters:(nullable id)parameters constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
區別就在於constructingBodyWithBlock,這也是我遇到的兩個問題的根源.tcp
往下看,會發現,第一個方法:
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
而第二個方法:
//先執行咱們傳進去的block if (block) { block(formData); } return [formData requestByFinalizingMultipartFormData]; //若是參數爲空,則content-type爲默認的,不爲空則爲multipart/form-data - (NSMutableURLRequest *)requestByFinalizingMultipartFormData { if ([self.bodyStream isEmpty]) { return self.request; } // Reset the initial and final boundaries to ensure correct Content-Length [self.bodyStream setInitialAndFinalBoundaries]; [self.request setHTTPBodyStream:self.bodyStream]; [self.request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", self.boundary] forHTTPHeaderField:@"Content-Type"]; [self.request setValue:[NSString stringWithFormat:@"%llu", [self.bodyStream contentLength]] forHTTPHeaderField:@"Content-Length"]; return self.request; }
這樣個人問題就迎刃而解了.
前同事爲了統一處理,全部的請求都是經第二個函數,因此即便不是傳文件,若是有參數, Content-Type仍是multipart/form-data.
而block是統一處理的:
if ([obj isKindOfClass:[UIImage class]]) { UIImage *image = obj; if (image.size.height > 1080 || image.size.width > 1080) { image = [image imageWithMaxSide:1080]; [formData appendPartWithFileData:UIImageJPEGRepresentation(image, 0.3f) name:key fileName:[NSString stringWithFormat:@"%@.jpeg",key] mimeType:@"image/jpeg"]; } else if (image.size.height > 600 || image.size.width > 600) { [formData appendPartWithFileData:UIImageJPEGRepresentation(image, 0.5f) name:key fileName:[NSString stringWithFormat:@"%@.jpeg",key] mimeType:@"image/jpeg"]; } else { [formData appendPartWithFileData:UIImageJPEGRepresentation(image, 1) name:key fileName:[NSString stringWithFormat:@"%@.jpeg",key] mimeType:@"image/jpeg"]; } }
只處理了傳一張照片,沒有考慮到傳數組裏面放多張圖片的狀況,因此服務器不知道這是圖片啊,怎麼解析呢!
上一個項目也沒有傳多張圖片的狀況,因此一直沒有發現這個問題.
最後的解決方法是:
.h (繼承AFHTTPSessionManager) //這個函數調用AF的第一個方法,不傳文件時調用 + (void)requestDataWithHTTPPath:(NSString *)path parameters:(NSDictionary *)parameters success:(RequestSuccessBlock)success failure:(RequestFailureBlock)failure; //這個函數調用AF的第二個方法,傳文件時用 + (void)uploadFileWithHTTPPath:(NSString *)path parameters:(NSDictionary *)parameters success:(RequestSuccessBlock)success failure:(RequestFailureBlock)failure; .m + (void)requestDataWithHTTPPath:(NSString *)path parameters:(NSDictionary *)parameters success:(RequestSuccessBlock)success failure:(RequestFailureBlock)failure { [KGAPIClient sharedRequestDataClient].isUploadFile = NO; [[KGAPIClient sharedRequestDataClient] requestDataWithHTTPPath:path parameters:parameters success:success failure:failure]; } + (void)uploadFileWithHTTPPath:(NSString *)path parameters:(NSDictionary *)parameters success:(RequestSuccessBlock)success failure:(RequestFailureBlock)failure { [KGAPIClient sharedRequestDataClient].isUploadFile = YES; [[KGAPIClient sharedRequestDataClient] requestDataWithHTTPPath:path parameters:parameters success:success failure:failure]; } //統一處理一些錯誤 #pragma mark - Request Method - (void)requestDataWithHTTPPath:(NSString *)path parameters:(NSDictionary *)parameters success:(RequestSuccessBlock)success failure:(RequestFailureBlock)failure { [self requestWithHTTPPath:path parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) { NSString *status = [responseObject valueForKey:kHTTPResponseStatsusKey]; if (status.length) { if ([status isEqualToString:kHTTPSuccessCode0000]) { if (success) { success(task, responseObject); } } else if ([status isEqualToString:kHTTPErrorCode0001]) { if (failure) { [SVProgressHUD showErrorWithStatus:[responseObject valueForKey:kHTTPResponseMessageKey]]; failure(task, [self requestErrorWithDomin:[responseObject valueForKey:kHTTPResponseMessageKey] errorCode:nil]); } }//.......還有別的一些狀態碼 } else { if (failure) { NSString *message = [responseObject valueForKey:kHTTPResponseMessageKey]; failure(task, [NSError errorWithDomain:message code:[status integerValue] userInfo:responseObject]); } } } failure:^(NSURLSessionDataTask *task, NSError *error) { if (failure) { NSError *aError = [NSError errorWithDomain:@"網絡不給力" code:error.code userInfo:error.userInfo]; failure(task, aError); } }]; } //區分兩種POST方式 - (void)requestWithHTTPPath:(NSString *)path parameters:(NSDictionary *)parameters success:(RequestSuccessBlock)success failure:(RequestFailureBlock)failure { NSDictionary *tmpDict = [KGAPIClient getNewParamsWithOldParams:parameters.mutableCopy]; if (_isUploadFile) { [self POST:path parameters:tmpDict constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) { [self appendPartWithparameters:parameters formData:formData]; } progress:^(NSProgress * _Nonnull uploadProgress) { NSLog(@"******progress***"); } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { if (success) { success(task, responseObject); } } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { if (failure) { failure(task, error); } }]; } else { [self POST:path parameters:tmpDict progress:^(NSProgress * _Nonnull uploadProgress) { NSLog(@"******progress***"); } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { if (success) { success(task, responseObject); } } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { if (failure && ![path isEqualToString:APIPathWithUserReportGeo] && ![path isEqualToString:APIPathWithUserNickExsits]) { failure(task, error); } }]; } } //用於設置data的type,還不全,暫時考慮image,image數組,.mp3文件. - (void)appendPartWithparameters:(NSDictionary *)parameters formData:(id<AFMultipartFormData> _Nonnull)formData { if (parameters) { [parameters enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { if ([obj isKindOfClass:[NSString class]] && ([obj hasSuffix:@".png"] || [obj hasSuffix:@".jpg"] || [obj hasSuffix:@".jpeg"])) { [formData appendPartWithFileURL:obj name:key fileName:[NSString stringWithFormat:@"%@.jpg",key] mimeType:@"image/jpeg" error:nil]; } else if ([obj isKindOfClass:[UIImage class]]) { UIImage *image = obj; if (image.size.height > 1080 || image.size.width > 1080) { image = [image imageWithMaxSide:1080]; [formData appendPartWithFileData:UIImageJPEGRepresentation(image, 0.3f) name:key fileName:[NSString stringWithFormat:@"%@.jpeg",key] mimeType:@"image/jpeg"]; } else if (image.size.height > 600 || image.size.width > 600) { [formData appendPartWithFileData:UIImageJPEGRepresentation(image, 0.5f) name:key fileName:[NSString stringWithFormat:@"%@.jpeg",key] mimeType:@"image/jpeg"]; } else { [formData appendPartWithFileData:UIImageJPEGRepresentation(image, 1) name:key fileName:[NSString stringWithFormat:@"%@.jpeg",key] mimeType:@"image/jpeg"]; } } else if ([obj isKindOfClass:[NSArray class]]) { NSArray *array = parameters[key]; for (int i = 0; i < array.count; i++) { id subvalue = array[i]; if ([subvalue isKindOfClass:[UIImage class]]) { UIImage *image = subvalue; if (image.size.height > 1080 || image.size.width > 1080) { image = [image imageWithMaxSide:1080]; [formData appendPartWithFileData:UIImageJPEGRepresentation(image, 0.3f) name:key fileName:[NSString stringWithFormat:@"%@.jpeg",key] mimeType:@"image/jpeg"]; } else if (image.size.height > 600 || image.size.width > 600) { [formData appendPartWithFileData:UIImageJPEGRepresentation(image, 0.5f) name:key fileName:[NSString stringWithFormat:@"%@.jpeg",key] mimeType:@"image/jpeg"]; } else { [formData appendPartWithFileData:UIImageJPEGRepresentation(image, 1) name:key fileName:[NSString stringWithFormat:@"%@.jpeg",key] mimeType:@"image/jpeg"]; } } else if ([subvalue isKindOfClass:[NSString class]] && ([subvalue hasSuffix:@".png"] || [subvalue hasSuffix:@".jpg"] || [subvalue hasSuffix:@".jpeg"])) { [formData appendPartWithFileURL:obj name:key fileName:[NSString stringWithFormat:@"%@.jpg",key] mimeType:@"image/jpeg" error:nil]; } } } else if ([obj isKindOfClass:[NSData class]]) { [formData appendPartWithFileData:obj name:key fileName:[NSString stringWithFormat:@"%@.mp3",key] mimeType:@"audio/mp3"]; } }]; } }
對於AF的二次封裝不少,不過追本溯源就是NSURLSession,就是http協議.
此次獨立帶項目,真的學到了不少,明白了本身的不少不足.更讓我明白理論真的很重要.大學的課程真的有他的必要性.