NSInvocationOperation обратно извикване твърде скоро

Знам, че подобни въпроси са задавани няколко пъти, но се боря да разбера как може да се реши този конкретен проблем. Досега всичко, което съм правил, е извършено на основния протектор. Сега откривам, че трябва да извърша операция, която ще отнеме известно време, и искам да добавя HUD към моя дисплей за времетраенето на операцията и да го избледнявам, когато операцията приключи.

След като прочетох много за GCD (и се обърках доста), реших, че най-простият начин е да извикам моя отнемащ време метод с NSInvocationOperation и да го добавя към новосъздадена NSOperationQueue. Ето какво имам:

        [self showLoadingConfirmation]; // puts HUD on screen

        // this bit takes a while to draw a large number of dots on a MKMapView            
        NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
                                                                                selector:@selector(timeConsumingOperation:)
                                                                                  object:[self lotsOfDataFromManagedObject]];

        // this fades the HUD away and removes it from the superview
        [operation setCompletionBlock:^{ [self performSelectorOnMainThread:@selector(fadeConfirmation:) withObject:loadingView waitUntilDone:YES]; }];

        NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
        [operationQueue addOperation:operation];

Очаквам това да покаже HUD, да започне да рисува точките върху картата и след това след като тази операция приключи, да избледнее HUD.

Вместо това той показва HUD, започва да рисува точките върху картата и избледнява по пътя на HUD, докато все още рисува точките. Според моите NSLogs, има около една четвърт от секундата забавяне преди извикването на метода за избледняване на HUD. Междувременно рисуването на точките продължава още няколко секунди.

Какво мога да направя, за да го накарам да изчака, докато чертежът върху картата завърши, преди да избледнее HUD?

Благодаря

РЕДАКТИРАНО ЗА ДОБАВЯНЕ:

Почти постигам успех, след като направих следните промени:

        NSInvocationOperation *showHud = [[NSInvocationOperation alloc] initWithTarget:self
                                                                              selector:@selector(showLoadingConfirmation)
                                                                                object:nil];

        NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
                                                                                selector:@selector(timeConsumingOperation:)
                                                                                  object:[self lotsOfDataFromManagedObject]];

        NSInvocationOperation *hideHud = [[NSInvocationOperation alloc] initWithTarget:self
                                                                              selector:@selector(fadeConfirmation:)
                                                                                object:loadingView];

        NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];

        NSArray *operations = [NSArray arrayWithObjects:showHud, operation, hideHud, nil];
        [operationQueue addOperations:operations waitUntilFinished:YES];

Странно, изглежда, че първо извиква timeConsumingOperation, след това showLoadingConfirmation, след това fadeConfirmation. Това е според моите NSLogs, които се задействат в тези методи.

Поведението, което виждам на екрана, е следното: точките се изчертават и картата коригира мащаба си съответно (част от timeConsumingOperation), след това HUD се появява на екрана, след това нищо. И трите NSLogs се появяват незабавно, въпреки че showLoadingConfirmation не се случва, докато timeConsumingOperation не приключи, а fadeConfirmation не изглежда да се случва изобщо.

Това изглежда много странно, но също така изглежда предполага, че има начин да накарате нещо да се случи при завършване на timeConsumingOperation.

Опитах се да добавя това:

[operationQueue setMaxConcurrentOperationCount:1];

а също и това:

[showHud setQueuePriority:NSOperationQueuePriorityVeryHigh];
[operation setQueuePriority:NSOperationQueuePriorityNormal];
[hideHud setQueuePriority:NSOperationQueuePriorityVeryLow];

но те изглежда не правят никаква разлика.


person beev    schedule 16.11.2012    source източник
comment
Така че completionBlock се извиква преди timeConsumingOperation да е завършена? Това ли е проблемът?   -  person iDev    schedule 19.11.2012
comment
Да, това е проблемът.   -  person beev    schedule 19.11.2012


Отговори (2)


Бъдете наясно какво всъщност правите, когато задавате манипулатор на завършване за NSInvocationOperation: Когато такава операция приключи, се случва следното (От Справочник за клас NSOperation):

Точният контекст на изпълнение за вашия блок за завършване не е гарантиран, но обикновено е вторична нишка. Следователно не трябва да използвате този блок, за да извършвате работа, която изисква много специфичен контекст на изпълнение. Вместо това трябва да прехвърлите тази работа към основната нишка на вашето приложение или към конкретната нишка, която може да я направи. Например, ако имате персонализирана нишка за координиране на завършването на операцията, можете да използвате блока за завършване, за да пингвате тази нишка.

Така че, първо, блокът се изпълнява на вторична нишка (така че ще влезе в опашката на тази нишка и когато най-накрая дойде неговият ред, той просто изпраща друга задача към опашката на основната нишка). Това означава, че ще се смеси в такава опашка с други чакащи задания, като последните актуализации за пинове, които са били изпратени до главната опашка от вашия timeConsumingOperation: селектор.

И тук идва проблемът: Ако не зададете приоритети за различни задачи, няма начин да кажете реда, в който тези задачи ще бъдат окончателно обработени, дори ако знаете, че са били изпратени преди време . Освен това във вашия случай фактът, че вашата NSInvocationOperation е приключила не означава непременно, че всичките ви обекти са били изчертани на екрана до момента на извикване на блока, това само означава, че са били изпратени до Нишка за актуализиране на потребителския интерфейс, която ще бъде обработена, когато дойде техният ред.

Имайки това предвид и като се има предвид, че не искате да тръгнете по пътя на GCD (което бих препоръчал да опитате още веднъж, тъй като знам, че не е лесно в началото, но когато го приемете, осъзнавате, че е прекрасно решение за почти всички многонишкови неща, които бихте искали да правите на iPhone) Бих създал NSOperationQueue и бих изпратил всички задачи там (тази, която премахва включен HUD, но с по-нисък приоритет от другите). По този начин се уверявате, че премахването на HUD се обработва в главната опашка, след като всички задачи за „закрепване“ са изпълнени, което беше първата ви цел.

person Alejandro Benito-Santos    schedule 19.11.2012
comment
Благодаря. Опитах да направя всичко в една и съща тема и да управлявам приоритетите. Моля, вижте допълнителния код, който добавих към въпроса. Все още се държи по начин, който няма смисъл за мен, но се чувствам малко по-близо до решение. - person beev; 19.11.2012
comment
@beev опитайте да използвате -(void)addDependency:(NSOperation *)operation, няма нужда да задавате приоритети по този начин. И не забравяйте да рисувате в основната нишка. - person Alejandro Benito-Santos; 19.11.2012
comment
Благодаря, това изглежда го оправи. Сега всичко се случва в правилния ред. Един въпрос: защо казвате, че трябва да направя цялата рисунка на основната нишка? Това е, което правя, но има ефект на изобразяване на потребителския интерфейс да не реагира, докато чертежът е готов. Не би ли било по-добре да направите това в отделна нишка, така че потребителят да може да се откаже, ако промени решението си, докато данните се зареждат? - person beev; 20.11.2012
comment
@beev Напълно си прав. Просто исках да се уверя, че правите точно това във вашия timeConsumingOperation:селектор, който код не беше публикуван! Ако искате повече информация, проверете този SO въпрос:връзка - person Alejandro Benito-Santos; 20.11.2012
comment
@beev Можете ли да отбележите това като правилен отговор, ако ви е помогнало? Благодаря - person Alejandro Benito-Santos; 20.11.2012
comment
Благодаря за вашата помощ. Радвам се, че вече всичко върви по правилния ред. Разочароващо е, че не мога да го накарам да работи с отделна нишка, но предполагам, че ще ми трябва GCD за това. Опитвам се да разбера GCD, но все още се чувствам объркан от синтаксиса. - person beev; 20.11.2012

Ако HUD изчезва, тогава вашият блок за завършване е бил извикан, което означава, че вашият timeConsumingOperation: метод е върнат.

Ако все още виждате да се рисуват точки, това означава, че има транзакции за анимация или рисуване, които все още се изпълняват или все още са на опашка, дори след като timeConsumingOperation: се върне. Решението зависи от техниката, която използвате, за да рисувате. Използвате ли основна анимация? MapKit с анотации?

person Community    schedule 19.11.2012
comment
Използвам mapKit и рисувам няколкостотин точки, като добавям наслагвания на MKCirle. Подозирам, че има по-ефективен начин за начертаване на тези точки, но не можах да го намеря. - person beev; 19.11.2012
comment
Е, в такъв случай изглежда, че MapKit добавя/чертае щифтовете асинхронно, което означава, че те все още се появяват дори след като вашият метод се е върнал. За съжаление вероятно ще бъде много трудно да разберете кога точно са се появили на екрана. - person Mike Weller; 19.11.2012
comment
Благодаря. Направих някои редакции на кода и промених въпроса, за да ги покажа. Резултатите изглежда предполагат, че може да се накара да задейства метод в края на рисуването на всички тези точки. Просто е странно, че не извиква метода, който очаквам! - person beev; 19.11.2012