[résolu] détruire une fenêtre par programmation

mybofymybofy Membre
août 2013 modifié dans API AppKit #1

Bonjour


 


Je crée une application cocoa :



#import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject <NSApplicationDelegate>
@property (assign) IBOutlet NSWindow *window;
- (IBAction)closeWindow:(id)sender;
@end

#import "AppDelegate.h"
@implementation AppDelegate
@synthesize window=_window;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
}
- (IBAction)closeWindow:(id)sender {
NSLog(@window.0 : %@", _window);
[_window setReleasedWhenClosed:YES];
[_window close];
NSLog(@window.1 : %@", _window);
}
@end


Résultat :



2013-08-28 11:58:27.062 CloseWindow[3345:303] window.0 : <NSWindow: 0x10061a550>
2013-08-28 11:58:27.064 CloseWindow[3345:303] window.1 : <NSWindow: 0x10061a550>


La fenêtre disparaà®t de l'écran, mais l'instance n'est pas détruite.


J'attendais window.1 à  null, après "setReleasedWhenClosed:YES" !


 


Comment faire pour que _window soit à  null (détruit) après un close ?


 


 Merci.


Réponses

  • AliGatorAliGator Membre, Modérateur
    août 2013 modifié #2
    Ton pointeur contient toujours l'adresse, mais c'est pas pour cela que l'adresse est toujours valide.
    L'objet à  cette adresse a certainement été effectivement released et détruit, mais ce n'est pas pour autant que la variable/propriété que tu as appelée "window" va automatiquement changer de valeur en voyant que l'adresse ne pointe plus sur un objet valide !

    Du coup si tu utilises ta variable (ton pointeur) _window ou ta propriété self.window après cela, ça va planter avec un EXC_BADACCESS car tu auras tenté d'accéder à  une zone mémoire qui ne contient plus rien (ou en tout cas ne contiendra plus l'objet que tu attends)... Ou alors tu auras un coup de bol et tu verras encore l'objet (comme c'est le cas avec ton NSLog dans ton exemple je suppose) parce que rien n'a été encore réécrit par dessus la zone mémoire donc tu as de la chance qu'il soit encore dans les parages.

    ---

    Si tu utilises ARC et déploie pour OSX 10.7 ou supérieur, change ton stockage de "assign" en "weak". En effet, quand le mécanisme de Zeroing Weak Reference est disponible (10.7+), les variables ayant le type de stockage "weak" sont automatiquement remises à  0 (à  nil) quand l'objet vers lequel elles pointent est détruit.

    ARC est disponible depuis OSX 10.6 (et iOS4) mais le mécanisme de "Zeroing Weak Reference", permettant de remettre les variables __weak à  nil, n'est disponible que depuis 10.7 (et iOS5), et donc n'est actif que quand ton Deployment Target est au moins égal à  ces versions, mais ne sera pas actif si tu supportes les versions d'OSX précédentes et dans ce cas ce sera alors à  toi de faire attention à  ne pas utiliser une variable pointant vers une zone déallouée ou mieux à  penser à  remettre à  nil toi-même les variables pour éviter les erreurs)

    ---

    Une autre possibilité qui pourrait expliquer que ta variable _window n'est pas nil, c'est si jamais qqun d'autre (un autre objet) retient ta NSWindow (un NSWindowController ?) et qu'il n'a pas été released ou bien qu'il y a un retain cycle l'empêchant d'être released.
  • OSX supporte le Zeroing-Weak Reference depuis la version 10.7 il suffit juste de changer assign en weak comme l'a dit AliGator.


    Maintenant ça sera pas fait directement. C'est marqué comme "a supprimer" mais ça ne sera effectif qu'à  la prochaine boucle de l'Autorelease Pool. Donc pas d'inquiétude ça sera détruit tôt ou tard ;)


  • AliGatorAliGator Membre, Modérateur
    En effet, comme l'a dit pyroh, en plus y'a des chances en plus que quand tu fermes la fenêtre ça fasse un autorelease et pas un release, et donc que tant que tu es encore dans le même cycle de RunLoop ça n'ait pas reçu le release final...
  • tabliertablier Membre
    août 2013 modifié #5

    Question à  10 cents, pas plus!


    Sous 10.6, on ne peut plus utiliser le retain-count pour savoir si la fenêtre est ou non released?


  • AliGatorAliGator Membre, Modérateur
    Ca a toujours été mal d'utiliser la propriété retainCount.
    En effet, cette propriété ne veut pas dire grand chose, enfin plutôt c'est trompeur de se baser dessus

    Tu peux avoir un objet avec un retainCount de 3 alors que tu n'as appelé qu'une seule fois "retain" dessus, mais parce qu'en interne il y a d'autres objets dont tu n'as pas du tout connaissance qui ont fait un "retain" aussi. Tu peux avoir un retainCount de 1 alors que l'objet va être supprimé parce qu'il a reçu un autorelease et que tu t'attends à  avoir 0...

    Pour les NSString & co c'est encore pire, car vu que les instances sont mutualisées autant que possible par le compilateur, tu peux avoir fait un copy sur ta chaà®ne et au lieu de t'attendre à  avoir du coup 2 instances avec un retainCount de 1 chacune, comme ça serait le cas habituellement avec n'importe quel objet, tu te retrouves avec une seule instance ayant un retainCount de 2, parce que pour les objets immutable il évite de faire des copies pour rien, ou il va utiliser le COW ou des shadow pointers pour ça...

    Bref utiliser retainCount peut parfois mener à  des conclusions erronnées car on ne sait pas ce qui se passe sous le capot. En pure théorie c'est bien pour expliquer le fonctionnement du mécanisme de Reference Counting, en pratique vu ce qui se passe sous le capot et les optimisations internes du compilo et du runtime, c'est parfois traitre...


    Dans tous les cas, dès que tu actives ARC tu n'as plus le droit d'utiliser retain, release, autorelease et retainCount. Ce n'est pas vraiment lié à  la version d'OSX mais à  si tu actives ARC (qui certes n'est dispo que depuis 10.6, mais ce n'est pas obligatoire de l'activer). En bref sous 10.6 tu peux toujours les utiliser si tu n'as pas activé ARC sur ton projet pour autant (même si pour retainCount ça reste une mauvaise idée), mais pas si tu as activé ARC.
  • OSX 10.7.5 - Xcode 4.6.2 - ARC actif


     


    1 - Si je change "assign" en "weak", j'obtiens un joli EXEC_BAD_instruction ...



    objc[7104]: cannot form weak reference to instance (0x1035002f0) of class NSWindow
    (lldb)

    2 - J'ai regardé Autorelease Pool et RunLoop, mais dans une prochaine vie peut être ?


    Comment faire pour faire tourner l'un ou l'autre avant l'exécution de NSLog ?


    C'est quand même pas si rapide un NSLog.


     


    Quid ?


  • tabliertablier Membre
    août 2013 modifié #8

    Ca a toujours été mal d'utiliser la propriété retainCount.


    En effet, cette propriété ne veut pas dire grand chose, enfin plutôt c'est trompeur de se baser dessus

    Cela aurait du être précisé clairement dans la documentation! 


     


    D'autre part si je veux faire une application qui marche de 10.5 à  10.8, je ne valide pas ARC.


  • AliGatorAliGator Membre, Modérateur
    1) Après une petite recherche Google, en effet en 10.7 toutes les classes ne supportaient pas le ZWR

    Il est du coup impossible de créer une weak reference sur les classes suivantes en 10.7. Cela ne pose plus de problème en 10.8 où les classes ont été rendues compatibles.

    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.


    2) Si tu ne comptes pas t'informer sur les AutoreleasePool et les RunLoop dans cette vie, cela va être difficile de t'expliquer le point 2...

    C'est pas une question du fait que le NSLog est rapide à  s'exécuter ou pas, c'est le fait qu'il s'exécute dans le même cycle de Runloop. Et que la RunLoop du MainThread a une ARP par défaut qui est créée à  chaque début de Runloop et purgée à  la fin de la Runloop. Donc les objets à  qui il a été envoyé "autorelease" ne recevront le vrai "release" que lors de cette purge de l'ARP donc qu'à  la fin du cycle courant de la RunLoop, qui ne se terminera qu'une fois que tes lignes de code courantes seront arrivées à  la fin et qu'il attendra un nouvel évènement (rafraà®chissement écran, clic souris, ...)

    Ce que tu peux faire c'est de mettre ton code qui close (les lignes après le premier NSLog et avant le dernier NSLog) dans un bloc @autoreleasepool { ... } comme ça tu vas créer une ARP interne et à  la sortie tous les objets qui auront reçu un autorelease vont être releasés. Ca te permettra de savoir si ta NSWindow continue d'exister parce qu'elle a eu un autorelease et qu'elle sera releasée qu'à  la fin, ou si c'est parce qu'elle ne sera pas releasée du tout (car retain cycle ou car retained par qqun d'autre)
  • mybofymybofy Membre
    août 2013 modifié #10

    Le @autoreleasepool { ... }, que j'avais vu sans trop savoir comment l'utiliser, et  le (car retain cycle ou car retained par qqun d'autre) m'ont alerté.


    Ma fenêtre était observer d'une notification et donc était retained.


    Un removeObserver a libéré les choses.


     


    Grand merci à  tous.


     


    Au fait mon "[_window setReleasedWhenClosed:YES];" est-il indispensable ?

  • AliGatorAliGator Membre, Modérateur
    août 2013 modifié #11
    Bizarre... Normalement les observer ne sont pas retenus, comme l'indique la doc ici :

    Note: The key-value observing addObserver:forKeyPath:options:context: method does not maintain strong references to the observing object, the observed objects, or the context. You should ensure that you maintain strong references to the observing, and observed, objects, and the context as necessary.

    Donc ça ne devrait pas créer de retain cycle ou retenir ta fenêtre même si elle est observée, en théorie...

    Par contre si tu t'ajoutes en observeur d'un objet, il faut en effet absolument retirer l'observer quand ce dernier disparait et est détruit (car sinon c'est une adresse mémoire vers un objet qui n'existe plus qui sera tenté d'être notifié, et du coup, crash assuré).
Connectez-vous ou Inscrivez-vous pour répondre.