NSMutableSet пайдалану кезінде қате

Мен қатені аламын

* Terminating app due to uncaught exception 'NSGenericException', reason: '* Collection <__NSCFSet: 0x6b66390> was mutated while being enumerated.'

менің сыныпыма жаңа делегат қосқанда. Немесе кем дегенде, бұл мәселе деп ойлаймын.

Бұл менің коды: MyAppAPI.m

[...]
static NSMutableSet *_delegates = nil;

@implementation MyAppAPI

+ (void)initialize
{
    if (self == [MyAppAPI class]) {
        _delegates = [[NSMutableSet alloc] init];
    }
}

+ (void)addDelegate:(id)delegate
{
    [_delegates addObject:delegate];
}

+ (void)removeDelegate:(id)delegate
{
    [_delegates removeObject:delegate];
}
[...]

@end

MyAppAPI is a singleton which I can use throughout my application. Wherever I can (or should be able to) do: [MyAppAPI addDelegate:self].
This works great, but only in the first view. This view has a UIScrollView with PageViewController which loads new views within itself. These new views register to MyAppAPI to listen to messages until they are unloaded (which in that case they do a removeDelegate). However, it seems to me that it dies directly after I did a addDelegate on the second view in the UIScrollView.

How could I improve the code so that this doesn't happen?

Update
I'd like to clarify me a bit further. What happens is that view controller "StartPage" has an UIScrollView with a page controller. It loads several other views (1 ahead of the current visible screen). Each view is an instans PageViewController, which registers itself using the addDelegate function shown above to the global singleton called MyAppAPI. However, as I understand this viewcontroller 1 is still reading from the delegate when viewcontroller 2 registers itself, hence the error shows above.

Мен сценарийді анық деп үміттенемін. Мен бірнеше нәрсе жасадым, бірақ ештеңе көмектеспейді. Делегаттардан оқып жүргенде де адвокатқа addDelegate арқылы тіркелуім керек. Мұны қалай істеуге болады?

Update 2 This is one of the reponder methods:

+ (void)didRecieveFeaturedItems:(NSArray*)items
{   
    for (id delegate in _delegates)
    {
        if ([delegate respondsToSelector:@selector(didRecieveFeaturedItems:)])
            [delegate didRecieveFeaturedItems:items];
    }
}
1
@frowing Мен ойлаймын
қосылды автор Paul Peelen, көзі
@NSResponder Мен ASIHttpRequest қалай істегенін және бірнеше тыңдаушылармен делегат классын қалай жасау керектігін қарастырдым. Мұның бәрі ақыры еді.
қосылды автор Paul Peelen, көзі
Ия, бұл мәселе деп қорқамын. Алайда, оны қалай шешуге болады? @AndrewZimmer түсіндіргендей синхрондауды қолдануға тырыстым, бірақ ол көмектескен жоқ.
қосылды автор Paul Peelen, көзі
Сіздің мәселеңіз [delegate didReceiveFeaturedItems] _delegates нысанын өзгертуге болатыны сөзсіз.
қосылды автор Hot Licks, көзі
Хабарландыру үлгісін пайдаланудың орнына неге бірнеше делегат бар?
қосылды автор NSResponder, көзі
Сіз жаңа делегатты қосқанда тырнағыңыз арқылы қайталанасыз ба?
қосылды автор Fran Sevillano, көзі

6 жауаптар

Скотт Хантер дұрыс. Бұл қате, қайталану кезінде тізімді өңдеуге тырысқанда лақтырылады.

Міне, сіз не істей аласыз?

+ (void)iteratingToRemove:(NSArray*)items {   
    for (id delegate in _delegates) {
        if(delegate.removeMePlease) {
          [MyAppAPI removeDelegate:delegate];  //error you are editing an NSSet while enumerating
        }
    }
}

Міне, осылай дұрыс шешуге болады:

+ (void)iteratingToRemove:(NSArray*)items
{   
    NSMutableArray *delegatesToRemove = [[NSMutableArray alloc] init];
    for (id delegate in _delegates) {
        if(delegate.removeMePlease) {
          [delegatesToRemove addObject:delegate];
        }
    }

    for(id delegate in delegatesToRemove) {
         [MyAppAPI removeDelegate:delegate];  //This works better
    }

    [delegatesToRemove release];
}
11
қосылды
Бұл жақсы жұмыс істеуі керек
қосылды автор vodkhang, көзі
Жарайды ма. Сіз менің removeDelegate-ді алмасуымның орнына ауыстыруым керек және өкілетті алып тастау үшін менің сыныпқа айтудың орнына, делегаттың өзінде removeMePlease деп аталатын айнымалы мәнді шын деп белгіледім. Мен дұрыс па?
қосылды автор Paul Peelen, көзі
Бұл мысалдың маңызды бөлігі, массивіңізді өңдегенде, санау кезінде оны жасамаңыз. Уақытша массивте өңделетін элементтерді сақтау арқылы нөмірлеуге болады және содан кейін өңдеуге болады. Бірінші және екінші код блогы арасындағы айырмашылыққа назар аударыңыз.
қосылды автор Andrew Zimmer, көзі

Қате дегеніміз, кейбір код сіздің тізіміңіздің ортасында жүрсе де, сіз тізімді өзгертесіз (бұл addDelegate шақырылғаннан кейінгі апатты түсіндіреді). Егер санауды жасайтын код тізімді өзгертетін болса, онда санауды аяқтағанға дейін (мысалы, оларды басқа тізімге жинау арқылы) модификацияларды өшіру керек. Сандарды санауды жүзеге асыратын код туралы ештеңе білмей-ақ, ол әлдеқайда көп нәрсе айта алмайды.

5
қосылды
Қалайша «санауды аяқтағанға дейін модификацияларды өшіру»? Бұл әрекетті жасаудың бір жолы бар ма, себебі қоңырау шалушы объекті делегатқа тіркелгенде немесе тіркелмегенде менің делегатта қандай да бір қисынсыздықты білмейді. (Менің түсініктеме түсініксіз деп үміттенемін);)
қосылды автор Paul Peelen, көзі
? Түсініктемеге қандай да бір ұсыныстарым бар ма?
қосылды автор Paul Peelen, көзі

Қарапайым шешім, айнымалы жиынды қолданбаңыз. Олар түрлі себептермен, соның ішінде, қауіпті.

NSSet (және көптеген басқа класстар) өзгермейтін және өзгермейтін нұсқалары арасында айырбастау үшін -copy және -mutableCopy пайдалануға болады. Барлық көшіру әдістерінен сақ болыңыз 1-ден (бірдей бөлу сияқты) жаңа объект қалдырыңыз, сондықтан сіз оларды босатыңыз.

Қателерді аз әлеуетке ие болудан басқа, өзгермейтін нысандар жұмыс істеуге жылдамырақ және жадты аз пайдаланады.

[...]
static NSSet *_delegates = nil;

@implementation MyAppAPI

+ (void)initialize
{
    if (self == [MyAppAPI class]) {
        _delegates = [[NSSet alloc] init];
    }
}

+ (void)addDelegate:(id)delegate
{
    NSMutableSet *delegatesMutable = [_delegates mutableCopy];
    [delegatesMutable addObject:delegate];

    [_delegates autorelease];
    _delegates = [delegatesMutable copy];

    [delegatesMutable release];
}

+ (void)removeDelegate:(id)delegate
{
    NSMutableSet *delegatesMutable = [_delegates mutableCopy];
    [delegatesMutable removeObject:delegate];

    [_delegates autorelease];
    _delegates = [delegatesMutable copy];

    [delegatesMutable release];
}
[...]

@end
2
қосылды
себебі _delegates мәні NSSet object болып табылады, ол өзгермейтін болып табылады, оны пайдалану алдында көрсеткішті ұстап (ұстап) ұстағаныңызша ешқашан өзгертпеуге кепілдік беріледі Сіздің жіп.
қосылды автор Abhi Beckert, көзі
ешқашан бұл жақсы болар деп ойлаған емес! .. рахмет.
қосылды автор Ved, көзі
Мен бұл тәсілмен проблеманы ойладым, бұл ағынның қауіпсіздігі емес және бір мезгілде екі қоспа қосылса немесе қосылса, сәйкессіз нәтижеге әкелуі мүмкін. Көшірмені қайталау - жалғыз шешім (құлыпсыз).
қосылды автор Ved, көзі

Скотт Хантер дұрыс: бұл жиынтық элементтері бойынша санап жатқанда, NSSet-ді өзгертуге байланысты мәселе. Қолданба апатқа ұшыраған жерден бетті іздеу керек. Мүмкін, _delegates жиынтығынан қосып/алып тастауға болатын жол бар. Бұл мұнда түрлендіруді жасау керек. Бұл оңай. Топтан қосу/жоюдың орнына келесілерді орындаңыз:

NSMutableSet *tempSet = [_delegates copy];
for (id delegate in _delegates)
{
    //add or remove from tempSet instead
}
[_delegates release], _delegates = tempSet;

Сонымен қатар, NSMutableSet қауіпсіз емес , сондықтан сіз өзіңіздің әдістеріңізді әрдайым негізгі ағыннан шақырыңыз. Егер ешқандай қосымша тақырыпты нақты анықтамасаңыз, сізде ешнәрсе болмайды.

1
қосылды

Objective-C «жылдам санау» туралы әрқашан есте сақтау керек нәрсе «Жылдам санау» және «цикл» арасында үлкен айырмашылық бар.

«жылдам санау» циклге қарағанда жылдамырақ БІРАҚ Сандарды санауды өзгерте алмайсыз.

NSSet-іңізден - (NSArray *) allObjects үшін сұрай аласыз және NSSet-ді өзгерту кезінде сол массив бойынша нөмірлеуге болады.

0
қосылды

Бұл қате, егер басқа ағым қайта бағытталса, ағымдық массивді өзгертуге (қосу, жою) тырысады.

NSLock немесе оны синхрондау арқылы оны шешудің бір жолы. Бұл әдістер қосу, жою және қайталау әдістерін параллельді деп атауға болмайды. Бірақ бұл өнімділікке және/немесе жауап беруге әсер етеді, себебі кез келген қосу/жою массивінде қайталанатын ағын үшін күтуге тура келеді.

Java-ның CopyOnWriteArrayList-ден шабыттанған жақсы шешім массивтің көшірмесін жасайды және көшірудің үстінен қайталанады. Сондықтан кодтың жалғыз өзгерісі болады: -

//better solution
+ (void)didRecieveFeaturedItems:(NSArray*)items
{   
    NSArray *copyOfDelegates = [_delegates copy]
    for (id delegate in copyOfDelegates)
    {
        if ([delegate respondsToSelector:@selector(didRecieveFeaturedItems:)])
            [delegate didRecieveFeaturedItems:items];
    }
}

Өнімділік әсерімен құлыптарды пайдаланып шешім

//not a good solution

+ (void)addDelegate:(id)delegate
{
    @synchronized(self){
        [_delegates addObject:delegate];
    }
}

+ (void)removeDelegate:(id)delegate
{
    @synchronized(self){
        [_delegates removeObject:delegate];
   }
}

+ (void)didRecieveFeaturedItems:(NSArray*)items
{   
    @synchronized(self){
        for (id delegate in _delegates)
        {
            if ([delegate respondsToSelector:@selector(didRecieveFeaturedItems:)])
                [delegate didRecieveFeaturedItems:items];
        }
    }
}
0
қосылды