寫在前面:
在iOS開發的過程中,有很多時候我們都在和數據打交道,最基本的就是數據的下載和上傳了,估計很多很多的小伙伴都在用AFNetworking與后台數據打交道,可有沒有想過,哪天AFNetworking你不能用了或者不會用了怎么辦?可能你心中疑惑了,這三方只要更新,存在怎么會不能用或者我怎么會不會用了,在沒有看Telegram源碼之前,我也是這么想的,看了Telegram源碼就不會再這么想了,以后我會把自己看的Telegram源碼部分的總結和經驗一點點的整理分享出來,整理成這個Telegram學習解析系列,有興趣的同行可以加文章鏈接最后面的telegram開發學習群,一起學習討論Telegram問題,Android和iOS都可以。一起進步!
需求怎樣來的?
先看看這個,在Telegram的安全協議 MtProtoKit中,你可以看到這個Third Party 這個文件,看下面的截圖:
可以看到這里面是有AFNetworking的,這個框架里面的東西有寫就是集成字AF來寫的,但AF這個版本是挺低的,嘗試着自己在這個基礎上去寫上傳那些方法應該是可以,我嘗試過之后放棄了,還是決定利用 NSURLConnection / NSURLSessionDataTask來自己寫,不過這個的話就的涉及到了請求這些東西的一個封裝,以及利用這個上傳圖片或者語音什么的時候,還有里面的參數的一個組裝,接下來就認真的把這部分的東西寫出來,這也是在Telegram的基礎上衍生出來的問題,要是平常的項目中,可能也不會輕易涉及到這些東西,既然用到了就好好總結一下:
一:簡單的數據訪問
先從簡單的開始,就從你給后台Post數據開始,先從NSURLConnection開始,下面的代碼就是具體的實例,每一句都有具體的注釋,看代碼:
-(NSString * )httpRequestWithParameters:(NSDictionary*)dict andURL:(NSURL*)url{ //把參數字典轉化成Data NSData * postData = [NSJSONSerialization dataWithJSONObject:dict options:0 error:NULL]; //把Data利用這個Key加密,這個Key自己設置 NSString * key = @"********"; postData = [postData AES256_Encrypt:key]; //初始化request NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; //設置請求方式 [request setHTTPMethod:@"POST"]; //添加請求體,這里要進行64編碼處理,就是這個newStringInBase64FromData方法 [request setHTTPBody:[[postData newStringInBase64FromData] dataUsingEncoding:NSUTF8StringEncoding]]; //設置請求的報文 [request setValue:@"utf-8" forHTTPHeaderField:@"charset"]; [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; //請求超時時間設置 [request setTimeoutInterval:15.0]; NSOperationQueue * queue = [[NSOperationQueue alloc]init]; [NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse * response, NSData *data, NSError *error){ if(error){ NSLog(@"文本內容上傳失敗"); NSLog(@"%@",data); NSLog(@"%@",response); }else{ // 解析服務器返回的數據(解析成字符串) NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"解析服務器返回的數據====%@", string); } }]; }
注意:關於配置報文下面這篇文章 POST請求的forHTTPHeaderField 感謝作者。
上面方法那些編碼、加密方法,你要有需要的話可以在我首頁找到我Q,我發給你。
上面的方法你可以給后台去POST數據,再說剩下的這個 NSURLSessionDataTask ,其實蘋果是不建議使用前面的 NSURLConnection 了的,這個我們就說的簡單點,你怎么從后台請求數據,下面就但是一個簡單的Get方法,請求Request部分的我們就不說了,和上面的一樣,參考上面的就行,下面就是一個完整的方法,你通過請求獲取到數據回調的方法:
-(void)httpRequestWithURL:(NSURL*)url andHttpRequestSuccess:(HttpRequestSuccess)httpRequestSuccess andHttpRequestFail:(HttpRequestFail)httpRequestFail{ //推薦使用這種請求方法,上面的方已經被廢棄 //下面的方法沒有給Request設置請求頭和內容,有需要參考上面的寫法 NSURLSession * session = [NSURLSession sharedSession]; NSURLSessionDataTask * dataTask = [session dataTaskWithRequest:[NSURLRequest requestWithURL:url] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { if (!error) { //沒有錯誤,返回正確 NSError * jsonError; NSDictionary * dic =[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&jsonError]; if (!jsonError) { httpRequestSuccess(dic); } }else{ //請求出現錯誤 httpRequestFail(@"請求錯誤"); } NSLog(@"response==%@",response); }]; [dataTask resume]; }
上面的這些就把簡單的怎樣和后台進行數據交互就解決了,當然這試試簡單的,涉及到文件下載上傳的我們就下面接着說:
二 :涉及到文件類型的怎么處理
下面這個方法是在處理Telegram消息類型上傳數據給后台的時候添加的,這個方法可能里面納西而判斷等等的東西你用不着,主要的你看里面上傳部分的內容封裝吧,主要的還是這部分的東西,或者對這個方法里面還有什么疑問的,可以問我。方法我直接給出來,里面的注釋真的挺詳細的了。一句一句的過:
/** 上傳Data @param url 上傳DataUrl @param postParems 參數 @param picFilePath 文件路徑 @param picFileName 文件名稱, @param message_Type 消息類型(區分你要上傳的文件是什么類型的,圖片、視頻、語音等等) @param fileName 這是像PDF,TXT等格式問文件的文件名 @return return value description */ + (NSString *)postRequestWithURL: (NSString *)url postParems: (NSMutableDictionary *)postParems picFilePath: (NSString *)picFilePath picFileName: (NSString *)picFileName andMessageType:(Message_Type)message_Type andFileName:(NSString *)fileName{ /** boundary: 是分隔符號,告訴服務器,我的請求體里用的就是就是這個分隔符,而且,拼接請求體也用到這個分隔符 */ NSString *TWITTERFON_FORM_BOUNDARY = @"iOSFileUploaded"; //根據url初始化request NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:10]; //分界線 --AaB03x NSString *MPboundary=[[NSString alloc]initWithFormat:@"--%@",TWITTERFON_FORM_BOUNDARY]; //結束符 AaB03x-- NSString *endMPboundary=[[NSString alloc]initWithFormat:@"%@--",MPboundary]; NSData * data; NSString * format; // 文件上傳的格式 //得到圖片的data if (message_Type == ImageMessage) { format = @" image/jpge,image/gif, image/jpeg, image/pjpeg, image/pjpeg"; UIImage *image=[UIImage imageWithContentsOfFile:picFilePath]; //返回為JPEG圖像 data = UIImageJPEGRepresentation(image, 0.3f); //得到語音或者視頻的data }else if (message_Type == VoiceMessage){ format = @"audio/mp3"; data= [NSData dataWithContentsOfFile:picFilePath]; }else if (message_Type == VedioMessage){ format = @"audio/mp4"; [self convertVideoWithModel:picFilePath andUrl:url andNSDictionary:postParems]; return @"進入了視頻壓縮"; }else if (message_Type == PasterMessage){ format = @"image/webp";//webp圖片格式 data = [NSData dataWithContentsOfFile:picFilePath]; }else if (message_Type == FileMessage){ format = [self GetContentType:fileName]; //判斷文件的上傳格式,利用后綴名判斷 data = [NSData dataWithContentsOfFile:picFilePath]; } // 在這里判斷Data是否存在 if (!data) { NSLog(@"要上傳的data不存在"); return @"data不存在"; } //http body的字符串 NSMutableString *body=[[NSMutableString alloc]init]; //參數的集合的所有key的集合 NSArray *keys= [postParems allKeys]; //遍歷keys for(int i=0;i<(int)[keys count];i++){ //得到當前key NSString *key=[keys objectAtIndex:i]; //添加分界線,換行 [body appendFormat:@"%@\r\n",MPboundary]; //添加字段名稱,換2行 [body appendFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n",key]; //添加字段的值 [body appendFormat:@"%@\r\n",[postParems objectForKey:key]]; } if(picFileName){ ////添加分界線,換行 [body appendFormat:@"%@\r\n",MPboundary]; //聲明pic字段,文件名為boris.png [body appendFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n",FORM_FLE_INPUT,picFileName]; //聲明上傳文件的格式 NSString * formant = [NSString stringWithFormat:@"Content-Type:%@\r\n\r\n",format]; [body appendFormat:@"%@", formant]; } //聲明結束符:--AaB03x-- NSString *end=[[NSString alloc]initWithFormat:@"\r\n%@",endMPboundary]; //聲明myRequestData,用來放入http body NSMutableData *myRequestData=[NSMutableData data]; //將body字符串轉化為UTF8格式的二進制 [myRequestData appendData:[body dataUsingEncoding:NSUTF8StringEncoding]]; if(data){ [myRequestData appendData:data]; } //加入結束符--AaB03x-- [myRequestData appendData:[end dataUsingEncoding:NSUTF8StringEncoding]]; //設置HTTPHeader中Content-Type的值 NSString *content=[[NSString alloc]initWithFormat:@"multipart/form-data; boundary=%@",TWITTERFON_FORM_BOUNDARY]; //設置HTTPHeader [request setValue:content forHTTPHeaderField:@"Content-Type"]; //設置Content-Length [request setValue:[NSString stringWithFormat:@"%lu", (unsigned long)[myRequestData length]] forHTTPHeaderField:@"Content-Length"]; //設置http body [request setHTTPBody:myRequestData]; //http method [request setHTTPMethod:@"POST"]; NSHTTPURLResponse *urlResponese = nil; NSError * error = [[NSError alloc]init]; NSData * resultData = [NSURLConnection sendSynchronousRequest:request returningResponse:&urlResponese error:&error]; NSDictionary * JSONresponseObject = [NSJSONSerialization JSONObjectWithData:resultData options:NSJSONReadingMutableContainers error:nil]; if ([[NSString stringWithFormat:@"%@",JSONresponseObject[@"errorCode"]] isEqualToString:@"0"]) { NSString *string = [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding]; NSLog(@"解析服務器返回的字符串====%@", string); NSLog(@"解析服務器返回的字典 ====%@", JSONresponseObject); return @"200"; } return nil; }
為了不讓博客篇幅太長,上面涉及到的視頻壓縮,還有文件的后綴名的判斷方法就不在發出來了,到時這個消息類型的判斷,這個我覺得是有必要發出來的,不是說這個有多復雜,只是可能找起來沒那么容易能找打一份完整的,既然能看到這,估計可能有伙伴會有需要的:
/*** 根據文件類型判斷上傳的文件格式 ***/ +(NSString*)GetContentType:(NSString*)filename{ // 判斷之前先把文件名稱轉化成小寫 NSString * Filename = [filename lowercaseString]; if ([Filename hasSuffix:@"avi"]) { return @"video/avi"; } else if([Filename hasSuffix:@"bmp"]) { return @"application/x-bmp"; } else if([Filename hasSuffix:@"jpeg"]) { return @"image/jpeg"; } else if([Filename hasSuffix:@"jpg"]) { return @"image/jpeg"; } else if([Filename hasSuffix:@"png"]) { return @"image/x-png"; } else if([Filename hasSuffix:@"mp3"]) { return @"audio/mp3"; } else if([Filename hasSuffix:@"mp4"]) { return @"video/mpeg4"; } else if([Filename hasSuffix:@"rmvb"]) { return @"application/vnd.rn-realmedia-vbr"; } else if([Filename hasSuffix:@"txt"]) { return @"text/plain"; } else if([Filename hasSuffix:@"xsl"]) { return @"application/x-xls"; } else if([Filename hasSuffix:@"xslx"]) { return @"application/x-xls"; } else if([Filename hasSuffix:@"xwd"]) { return @"application/x-xwd"; } else if([Filename hasSuffix:@"doc"]) { return @"application/msword"; } else if([Filename hasSuffix:@"docx"]) { return @"application/msword"; } else if([Filename hasSuffix:@"ppt"]) { return @"application/x-ppt"; } else if([Filename hasSuffix:@"pdf"]) { return @"application/pdf"; } return nil; }