
前提:近期做的項目中,用到了七牛云上傳圖片,七牛云的介紹就不多說了,安全,低成本(具體的集成查看七牛官網)。移動端的圖片上傳到七牛,服務器再去七牛拽取圖片,拽取的時候可以設置參數以便拽下來的圖片就是裁剪(等操作)好的。
與服務器定的流程:
第一步:客戶端請求服務器,拿到服務器生成的七牛token
第二步:客戶端上傳圖片到七牛,成功后拿到七牛返回的圖片地址
第三步:將圖片地址再次上傳到服務器
先得到token,再上傳七牛,最后上傳服務器圖片地址,順序不可亂。
引入問題:
以上三步網絡請求為異步,回調的時機不一樣,你發(fā)了3個請求,有可能回來的時候是2請求先完成了,但是這三個步驟需要依次執(zhí)行。
- 存在多個請求,需要依次執(zhí)行
通過NSOperationQueue進行實現(xiàn)(GCD提供的dispatch_group_t group也可實現(xiàn),因人而異吧)
//第一步:獲取token
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
[self task_1];
}];
//第二步:上傳七牛
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
[self task_2];
}];
//第三步:圖片地址上傳服務器
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
[self task_3];
}];
//設置依賴
[operation2 addDependency:operation1]; //任務二依賴任務一
[operation3 addDependency:operation2]; //任務三依賴任務二
//創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];
這里需要注意:上面的情況只實用于普通情況,即單個線程任務,同步 ??蓪嶋H項目中,任務為網絡請求。而網絡請求通常為異步回調,這樣就不適用了。舉個例子,A、B,兩個人是按順序排隊點餐的(發(fā)網絡請求),A點了牛排,B點了煎蛋,很可能就是B的煎蛋先上菜了(回調)。
- 這里我們就要借助GCD
GCD中的信號量dispatch_semaphore進行實現(xiàn),即營造線程同步情況。
dispatch_semaphore信號量為基于計數器的一種多線程同步機制。用于解決在多個線程訪問共有資源時候,會因為多線程的特性而引發(fā)數據出錯的問題。
如果semaphore計數大于等于1,計數-1,返回,程序繼續(xù)運行。如果計數為0,則等待。
dispatch_semaphore_signal(semaphore)為計數+1操作。dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER)為設置等待時間,這里設置的等待時間是一直等待。
我們可以通俗的理解為,單柜臺排隊點餐(計數默認為0),每當有顧客點餐(計數+1),點餐結束(-1歸零),繼續(xù)等待下一位顧客。
//AFN網絡請求
// task_2,task_3,任務也是這樣的
//創(chuàng)建信號量并設置計數默認為0
- (void)task_1
{
//創(chuàng)建信號量并設置計數默認為0
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[self.entityRequest postGetQiNiuTokenSuccess:^(NS_M_RequestOutput *json) {
//請求成功 計數+1操作
dispatch_semaphore_signal(sema);
} failure:^(NSString *msg) {
//請求失敗 計數+1操作
dispatch_semaphore_signal(sema);
} error:^(NSError *error) {
//請求異常 計數+1操作
dispatch_semaphore_signal(sema);
}];
//若計數為0則一直等待
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
在dispatch_semaphore_wait位置打個斷點

通過信號量dispatch_semaphore完美的解決了此問題,并且網絡請求仍為異步,不會堵塞當前主線程。
再次引入問題:
上文說了,task_2為發(fā)送圖片到七牛,修改頭像這種一張圖片上傳沒問題。
但是項目需求,需要一次上傳很多張圖片。七牛并沒有提供多圖片上傳,最后希望達到效果,task_2任務包含大量的圖片的并發(fā)上傳。
希望實現(xiàn):
1、2、3...10張圖片同時上傳,回調肯定不是依次回來的,使用NSOperationQueue依次添加任務? 這不太可能。由于我們的服務器小哥要求我們上傳圖片地址的時候,數組中的順序要正確,服務器不進行排序操作。
對于這問題,可以上傳服務器的時候每張圖片帶個順序標記,讓服務器進行排序,但是我們這邊種種原因沒這么做,需要客戶端自己進行排序。
即:
//這里 content 中的對象是順序的,對應用戶添加照片的順序
{
"system":"ios",
"content":[
Object{...},
Object{...},
Object{...},
Object{...}
],
"uid":"123456789"
}
準備實現(xiàn):
1、大量請求并發(fā)執(zhí)行
2、回調同時,順便搞下順序
3、等這些請求都回調了,再執(zhí)行任務三。
下面說下我的做法 (for循環(huán)中存在block回調):
for (int i = 0; i < images.count; i++) {
//七牛 發(fā)送圖片請求 10張圖片 上傳成功block回調均為異步
^() {
// KVC 操作 key == i | value == 圖片名字
}
}
那么問題來了,for循環(huán)中發(fā)了很多請求,但是kvc賦值的時候,回調還沒回來,此時i++操作已經完了,結果就是,第一張照片對應的i 不是0 ,而是非0的某個數字,那順序就亂了。該方式放棄。
- 這里引入enumerateObjectsWithOptions遍歷

上面的截圖大概知道了,并發(fā)執(zhí)行,順序無所謂 ,最后都執(zhí)行完了,才輸出Done。
// NSEnumerationConcurrent
// 枚舉過程中,各個Block是同時開始執(zhí)行的。這樣枚舉的完成順序是不確定的。
// NSEnumerationReverse
// 以反序方式枚舉。
[images enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
// idx 在代碼塊中不會改變
}];
這樣就感覺,在這里添加網絡請求 ,再kvc, key : value 就是一一對應的,而且,等Done完成了,我們再進行任務三。
思考:
enumerateObjectsWithOptions里面是網絡請求的話,問題還是一樣,請求并發(fā)執(zhí)行了,輸出Done了,此時回調才來。那么這里也用信號量呢?
//開始上傳圖片 要按順序添加 進新數組
[upDataImages enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[網絡請求:{
成功:dispatch_semaphore_signal(sema);
失?。篸ispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}];
NSLog(@"Done...........");
結果:
很顯然, dispatch_semaphore_wait等待回調完成,才算這次任務完成,也就是說,最終enumerateObjectsWithOptions中任務全部執(zhí)行完成,并且回調了,才輸出Done。
最后還剩下排序,方法很多,這里說下我的。
1、先將圖片data添加進數組(upDataImages),然后上傳
2、回調后,再使用idx進行替換數組中的元素,因為idx在代碼塊中不會改變,所以上傳第一張照片時,idx就是0。[imagesArr replaceObjectAtIndex:idx withObject:resp[@"key"]];
注意:這里upDataImages數組遍歷時,不要再去進行操作(刪除,添加,替換),由于upDataImages(NSMutableArray)為線程不安全,會造成不同的線程同時操作了某塊內存,導致崩潰。
以上就是這些...實現(xiàn)方法因人而異,項目急,暫時就這樣算是解決了,收工~