Array/ObjectControllers: (strong ou weak) IBOutlets?

Bonsoir,


 


Une application qui tourne parfaitement sur mes machines de développement (10.9) se plante systématiquement sur 10.8 avec ce message de la console: Controller cannot be nil.


 


Je me demandais si cela pouvait provenir d'un objet explusé de la mémoire faute de place ou au mauvais moment, et si cela avait une chance de provenir d'un mauvais réglage.


 


Mes outlets sont tous weak. C'est ainsi que le Refactor les a déclarés lorsque j'ai converti mon projet en ARC.


 


Certains de ces outlets sont des contrôleurs, dont le document doit vérifier le contenu. A un moment, l'un de ces contrôleurs est passé en paramètre à  une routine, c'est celle-ci qui semble poser problème.


 


Dès lors, ces contrôleurs (qui disparaissent avec le document) ne devraient-ils pas être déclarés comme strong durant la durée de vie du document?


 


Spécialistes de la mémoire, merci du coup de main.


Mots clés:

Réponses

  • PyrohPyroh Membre
    janvier 2014 modifié #2

    Il faut utiliser strong pour les Controllers.


    C'est marqué clairement dans les release notes ARC:


     



     


     


    Note: In addition, in OS X v10.7, you cannot create weak references to instances of NSFontManager, NSFontPanel, NSImage, NSTableCellView, NSViewController, NSWindow, and NSWindowController. In addition, in OS X v10.7 no classes in the AV Foundation framework support weak references.

  •  


    Turns out that's fine in 10.8, but not 10.7, according to the Transitioning to ARC Release Notes:


     


    Pyroh,


    Merci pour le lien. Cette restriction est-elle toujours valable pour 10.8 (mon OS cible)?


     


    Ok, j'ai passé mes outlets en strong dans le document. Je testerai demain. Entretemps, je vais faire la chasse à  d'éventuels "retain" (strong) cycles...


  • J'ai déjà  eu ce genre de problème sous 10.8 j'ai pas retenté le coup sous 10.9.


    Maintenant je ne pense pas que cela pose un problème, un controller c'est pas non plus 200mo de RAM de bouffé... Si c'est déjà  200ko c'est déjà  beau ^^


  • Mon application entière fait 633K (143K de code), mais elle en fait des choses pour si peu  :P


     


    Je ne pense pas que le système évacue le contrôleur pour sa taille, il a une gestion globale et si le retain d'un objet de 210 octets atteint zéro, hop, poubelle! Et l'objet en question peut être vital...


     


    Mais je note que tu as eu des problèmes de ce type sous 10.8, c'est donc un indice assez fort que l'avertissement reste valable. En tout cas, j'ai vérifié, je n'ai pas de leaks.


     


    Et pas étonnant que la recherche de zombies n'ai rien donné, puisque les weaks sont remis à  NIL une fois purgés...


  • Ce n'était pas ça.


     


    Je commence à  m'échauffer un peu: l'application tourne sur un MacBook Air sous 10.8, mais continue à  se planter sur les iMac dotés du même système (récents et équipés de 8Go de mémoire)...


     


    J'ai généré des sous-classes de tous mes NSManagedObjects, avec des propriétés du bon type d'objet: pas de id qui traà®ne. Plus un seul setValue... forKey avec un risque de variable mal orthographiée (le plus drôle c'est que la taille de l'application a diminué et non augmenté).


     


    J'ai farci mon code de NSLog. Ils m'indiquent que mes objets sont du type souhaité, pas un seul NIL, pas de leaks ni de zombies dans Instruments.


     


    Et toujours ce "Controller cannot be nil" provoqué par les lignes:



    - (id)initWithFrame:(NSRect)frameRect desk: (Desk*)aDesk deskController: (DeskController*)aDeskController studentController: (StudentController*)aStudentController
    {
    self = [super initWithFrame:frameRect];

    self.deskController = aDeskController;
    self.studentController = aStudentController;
    self.desk = aDesk;
    [aDesk setView:self];

    // Now we create and bind the pop up menu, as it can't be made in IB...

    self.popUp = [[NSPopUpButton alloc]initWithFrame:self.bounds];
    NSDictionary *dic = @{@NSInsertsNullPlaceholder: @(YES), @NSNullPlaceholder: @ };
    [self.popUp bind:@content toObject:self.studentController withKeyPath:@arrangedObjects options:dic];
    [self.popUp bind:@contentValues toObject:self.studentController withKeyPath:@arrangedObjects.name options:nil];
    [self.popUp bind:@selectedObject toObject:self.desk withKeyPath:@student options:nil];
    [[self.popUp menu] setDelegate:(id < NSMenuDelegate >)self];

    [self setMenu:[self.popUp menu]]; // We set the menu as contextual for the DeskView.
    #ifdef DEBUG_MONTOLIEU
    NSLog(@In DMDeskView\n Desk Controller : \n%@\n Student Controller : \n%@\n Self Desk :\n%@",self.deskController,self.studentController,self.desk);
    #endif
    return self;
    }

    Si je mets en commentaire les lignes qui concernent le menu, l'app tourne " mais elle n'est pas utilisable. En tout cas l'erreur est quelque part dans ces lignes.


     


    C'est un problème de binding par code.


     


    Qu'est-ce que j'ai bien pu négliger?


  • Fais voir un peu le .h

    C'est intriguant comme soucis...
  • Ecco.



    @class DeskController;

    @interface DMDeskView : NSView

    @property (weak) Desk *desk; // Avoid retain cycle !
    @property NSPopUpButton *popUp;
    @property NSString *info;
    @property (weak) StudentController *studentController;
    @property (weak) DeskController *deskController;

    - (id)initWithFrame:(NSRect)frameRect desk: (Desk*)aDesk deskController: (DeskController*)aDeskController studentController: (StudentController*)aStudentController;
    @end

  • Personnellement quand j'ai des properties qui sont en fait des variables d'instance, je mets strong. Essaie déjà  avec ça pour popUp et info.


    Et est-ce qu'il est possible d'avoir le log d'erreur en entier ?


  • J'ai essayé weak, strong, et par défaut comme ici (je crois que c'est strong le défaut). Même résultat:



    29.01.14 09:02:28.120 Demiurge[5275]: (
    0 CoreFoundation 0x00007fff93dfab06 __exceptionPreprocess + 198
    1 libobjc.A.dylib 0x00007fff9418a3f0 objc_exception_throw + 43
    2 CoreFoundation 0x00007fff93dfa8dc +[NSException raise:format:] + 204
    3 AppKit 0x00007fff990345a1 -[NSBinder addBinding:toController:withKeyPath:valueTransformer:options:] + 299
    4 AppKit 0x00007fff9903068c -[NSObject(NSKeyValueBindingCreation) bind:toObject:withKeyPath:options:] + 628
    5 Demiurge 0x000000010cdd35b4 -[DMDeskView initWithFrame:desk:deskController:studentController:] + 772
    6 Demiurge 0x000000010cdcf22b -[DeskController newObject] + 427
    7 AppKit 0x00007fff99150fc4 -[NSArrayController _executeAdd:didCommitSuccessfully:actionSender:] + 69
    8 AppKit 0x00007fff994eb2fa _NSSendCommitEditingSelector + 58
    9 AppKit 0x00007fff991e9dc4 -[NSController _controllerEditor:didCommit:contextInfo:] + 190
    10 CoreFoundation 0x00007fff93dee09c __invoking___ + 140
    11 CoreFoundation 0x00007fff93dedf37 -[NSInvocation invoke] + 263
    12 CoreFoundation 0x00007fff93dee109 -[NSInvocation invokeWithTarget:] + 57
    13 Foundation 0x00007fff91176d05 __NSFireDelayedPerform + 358
    14 CoreFoundation 0x00007fff93db7804 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 20
    15 CoreFoundation 0x00007fff93db731d __CFRunLoopDoTimer + 557
    16 CoreFoundation 0x00007fff93d9cad9 __CFRunLoopRun + 1529
    17 CoreFoundation 0x00007fff93d9c0e2 CFRunLoopRunSpecific + 290
    18 HIToolbox 0x00007fff97198eb4 RunCurrentEventLoopInMode + 209
    19 HIToolbox 0x00007fff97198b94 ReceiveNextEventCommon + 166
    20 HIToolbox 0x00007fff97198ae3 BlockUntilNextEventMatchingListInMode + 62
    21 AppKit 0x00007fff98ef8533 _DPSNextEvent + 685
    22 AppKit 0x00007fff98ef7df2 -[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:] + 128
    23 AppKit 0x00007fff98eef1a3 -[NSApplication run] + 517
    24 AppKit 0x00007fff98e93bd6 NSApplicationMain + 869
    25 libdyld.dylib 0x00007fff986097e1 start + 0
    )
  • berfisberfis Membre
    janvier 2014 modifié #11

    Un bug de ce type? J'ai tout vérifié...



     


     






    • To replay to my own question...


      It is a Binding / IB bug (will file it next time when using the radar).


      If one creates bindings, and later removes them from the NIB, the

      bindings are removed visually, but the NIB still has traces of them.

      This cause a bad memory leak (the Window Controller is retained many

      times and never deallocated).

      The only solution I found is to redo the entire NIB. Bummer!


      gt



    http://www.cocoabuilder.com/archive/cocoa/122136-strange-nswindowcontroller-memory-leak.html


  • PyrohPyroh Membre
    janvier 2014 modifié #12
    Essaie de binder sur self au lieu de self.popUp
    Après je suis à  court d'idée :(


    C'est dans l'init les autres objets ne sont pas encore tous créés ! Faut le mettre dans awakeFromNib normalement ;)

    J'ai posté ça vite fait avant de me coucher hier, en fait il n'y a que le NSPopupButton qui contient autre chose que Nil. A l'init rien n'assure que les outlets sont déjà  instanciés par le nib (généralement ils ne le sont pas).

    AwakeFromNib est fait pour ça ;)
  • berfisberfis Membre
    janvier 2014 modifié #13

    J'ai trouvé l'un des bugs (because "there is always another bug"...)



    [self.popUp bind:@selectedObject toObject:self.desk withKeyPath:@student options:nil];

    La doc est "claire", enfin, aussi claire que d'habitude:


     



     


    Establishes a binding between a given property of the receiver and the property of a given object specified by a given key path.


    - (void)bind:(NSString *)binding toObject:(id)observableController withKeyPath:(NSString *)keyPath options:(NSDictionary*)options

    Parameters binding

    The key path for a property of the receiver previously exposed using the exposeBinding: method.


    observableController

    The bound-to object.


     



     


    Maintenant, que faut-il entendre par observableController? Les exemples donnés, ainsi que les objets proposés dans la liste des observables dans le cas de IB, sont des objets de type Controller (Array-, Object-, Tree-, View-).


     


    Mon troisième paramètre est un NSManagedObject dérivé. Il ne fait donc pas l'affaire. Dès lors, j'avais (me semble-t-il) deux choix:


     


    a- créer un Controller pour surveiller mon objet.


    b- ne pas lier le menu par binding.


     


    J'ai fini par utiliser ceci :



    self.popUp = [[NSPopUpButton alloc]initWithFrame:self.bounds];
    NSDictionary *dic = @{@NSInsertsNullPlaceholder: @(YES), @NSNullPlaceholder: @ };
    [self.popUp bind:NSContentBinding toObject:self.studentController withKeyPath:@arrangedObjects options:dic];
    [self.popUp bind:NSContentValuesBinding toObject:self.studentController withKeyPath:@arrangedObjects.name options:nil];
    [[self.popUp menu] setDelegate:(id < NSMenuDelegate >)self];
    [self.popUp setTarget:self];
    [self.popUp setAction:@selector(clicked)];
    ...
    ...
    - (void) clicked
    {
    [self.desk setStudent:[[self.popUp selectedItem]representedObject]];
    }

    Comme le disait Céroce dans un autre sujet: "Ce qui complique tout, ce sont les bindings". Mais il se trouve que j'adore le procédé, même si là , il vient de me faire passer de mauvais moments...


     


    Puisque l'erreur ne se situait pas dans une mémoire purgée au mauvais moment, mes questions deviennent:


     


    1. Si bind attend comme troisième paramètre un NSController, pourquoi le compilateur accepte-t-il un NSManagedObject ?


    2. Surtout, étant donné qu'il s'agit d'un bug, et d'un vrai, comment expliquer le comportement conciliant de mon environnement de développement, qui n'a jamais bugué? Ma volonté suffirait-elle à  influencer la machine?


     


    Bref, c'est une solution qui m'amène plus de questions que de réponses. Mais peut-être quelqu'un sait-il?


  • Toi t'as pas lu ce que j'ai posté plus haut ^^


    Il ne faut pas mettre ça dans l'init !


  • Toi non plus  :)


    C'est un initWithFrame, ce qui laisse supposer une initialisation par code, pas un chargement depuis un nib. En fait, cette routine est appelée d'innombrables cycles après le lancement de l'application, lorsque l'utilisateur, de son rythme pesant, décide de rajouter un objet particulier à  une liste. Tous les contrôleurs sont instanciés, leur contenu est chargé, les tableviews remplies.


     


    Tu n'aurais pas une explication au fonctionnement "miraculeux" de mon bug ?


  • Ah oui effectivement. J'ai deux dernières idées :

    Binder sur self ou compiler avec le SDK 10.8


    Après je vois pas :(
  • J'ai fini par télécharger Xcode sur une machine 10.8 et, encore une fois, constaté un comportement différent...


     


    Il y a quelque temps, j'avais lu qu'un développeur Windows était bien mal loti, puisqu'il devait tester son programme sur des tas de systèmes et des tas de machines différentes, alors que le développeur Mac ne connaissait pas ce genre d'inconvénient.


     


    Dommage que j'aie perdu la référence. J'aurais volontiers ajouté un petit bémol à  cette déclaration quelque peu optimiste...


Connectez-vous ou Inscrivez-vous pour répondre.