Re: [Pyobjc-dev] Memory management bug using .new() -- and another one?
Brought to you by:
ronaldoussoren
From: Dirk S. <dir...@ma...> - 2009-06-23 08:33:41
|
Hi Ronald, I use objc.IBOutlet() all over the place, so I've tried a couple of things. Short summary is: It definitely changes behavior, but it doesn't solve the problem. I've tried the following things: 1. Add an objc.ivar() definition (with the same name) after an outlet definition to a top-level object that has an init method implemented in Python. 2. Add an objc.ivar() definition (with the same name) after an outlet definition to a top-level object that's a vanilla Cocoa class. 3. Add an objc.ivar() definition (again, the same name) after an outlet definition to a non-top-level object that's implemented with Python, but doesn't override the designated initializer. The results are: 1. The object is now released properly without using my hacky workaround. 2. The object is over-released and the app crashes. 3. The object's dealloc method is called twice (I'm logging something in an overridden dealloc method in that class), but the app does not crash. So, sadly, I'm afraid that objc.IBOutlet's special casing of memory management is still needed. -- Aside from this, the bigger problem remains: I don't necessarily have outlets to all top-level objects in all of my ViewController and WindowController classes. I have just re-affirmed that the same issue (top-level object not being released by view/ windowController if the object has been initialized through an init method implemented in python) happens with top-level objects that don't have an outlet connected to them. Because to top-level objects that aren't connected to outlets are also not released properly (if their non-native init's have been called) I think that the issue doesn't have much to do with the implementation of objc.IBOutlet. I'm guessing that NSViewController's and NSWindowController's internal dealloc methods call something akin to: > [_topLevelObjects makeObjectsPerformSelector:@selector(release)]; And for some mysterious reason, this doesn't have the expected effect on objects that have been initialized in Python. I am concerned that, without seeing what actually happens in NSView/WindowController, we can't do very much to fix this. - Dirk On Jun 20, 2009, at 6:39 PM, Ronald Oussoren wrote: > Dirk, > > Do you have an 'outlet = objc.IBOutlet()' in your class definition? If > so, could you test if the issue goes away if you do the following: > > class MyClass (NSObject): > outlet = objc.IBOutlet() > outlet = objc.ivar() > > That is, duplicate all IBOutlet definitions with an objc.ivar > definition in the same class definition below the IBOutlet > definitions. > > Objc.IBOutlet definitions are treated rather specially by PyObjC and I > fairly recently read something about outlet handling on iPhone as > compared to regular MacOSX that made me wonder if the way PyObjC > handles outlets is correct. > > If the above hack fixes the memory leak you're seeing I'm going to > write a unittest that displays the same behaviour and fix the issue > (that last bit should be easy and would remove some code, which is > always a good thing). > > Ronald > > On 19 Jun, 2009, at 13:53, Dirk Stoop wrote: > >> For anyone else who might be running into this top-level objects >> thing, here's a workaround. >> >> I don't recommend anyone to put this to use in any production code, >> but at least it's right now allowing me to go hunting for leaks >> relatively reliably before this bug is fixed, or someone convinces >> me it's not a bug. ;) >> >> ---------- >> >> # my 'global' to switch this all off when the issue's been fixed >> >> CH_WORKAROUND_NON_NATIVE_INIT_TLOBJECTS = True >> >> ---------- >> >> # in my viewController, identically also in my windowController >> >> class MyViewController(NSViewController): >> def dealloc(self): >> if not hasattr(self, >> '_chWindowControllerRemoveObservationsCalled'): >> raise RuntimeError, 'Call to super(%s, >> self).removeObservations() is MISSING in %s\'s removeObservations >> implementation' % (self.className(), self.className()) >> >> if CH_WORKAROUND_NON_NATIVE_INIT_TLOBJECTS: >> topLevelObjects = objc.getInstanceVariable(self, >> '_topLevelObjects') >> objectsToRelease = CHNonNativeInitializedObjectsInList_ >> (topLevelObjects) >> if len(objectsToRelease): >> NSLog(u'\tThe following objects with non-native init >> methods will be released:') >> for o in objectsToRelease: >> NSLog(u'\t\t%s' % o) >> o.release() >> >> super(MyViewController, self).dealloc() >> >> ---------- >> >> # the method that filters the set of objects >> >> def CHNonNativeInitializedObjectsInList_(listOfObjects): >> """ >> Iterates through the supplied listOfObjects, and returns the >> subset of those >> that have an 'init*' method that's implemented in Python. >> """ >> nonNativeInitObjects = [] >> >> for someObject in listOfObjects: >> hasNativeInit = True >> allAttributes = (a for a in dir(someObject) if a.startswith >> ('init')) >> for attributeName in allAttributes: >> exec("method = someObject.%s" % attributeName) >> if hasattr(method, 'callable'): >> hasNativeInit = False >> break >> if not hasNativeInit: >> nonNativeInitObjects.append(someObject) >> >> return nonNativeInitObjects >> >> ---------- >> >> Once more, this is prone to breakage, >> 1. since we're accessing the private ivar '_topLevelObjects', which >> might be renamed at any time, although I doubt it will; hard to come >> up with a better name for that needed private ivar.. >> 2. And of course, checking the entire dir(someObject) to look for >> any attribute that starts with 'init' is pretty ridiculous too. >> >> But at least, I'm able to continue my hunt for memory leaks now, >> plus I have a boolean that I can throw out the window when this >> isn't needed anymore. >> >> Cheers, >> - Dirk >> >> >> >> On Jun 19, 2009, at 12:06 PM, Dirk Stoop wrote: >> >>> Hi Ronald, >>> >>> Thanks for the clarification. :) >>> >>> I found another weird memory management quirk that might be related. >>> >>> Summary: >>> Objects that implement the designated initializer are not >>> automatically released as top-level objects in nibs owned by view- >>> and >>> windowcontrollers. >>> >>> Example: >>> I have the following two classes: >>> >>> ---------------- >>> class SFTestViewVanilla(NSView): >>> >>> def dealloc(self): >>> NSLog(u'yay, dealloc in %s' % self) >>> super(SFTestViewVanilla, self).dealloc() >>> >>> class SFTestViewWithInit(NSView): >>> >>> def initWithFrame_(self, frame): >>> NSLog(u'initWithFrame_ called in %s' % self) >>> self = super(SFTestViewWithInit, self).initWithFrame_(frame) >>> if self: >>> pass >>> return self >>> >>> def dealloc(self): >>> NSLog(u'yay, dealloc in %s' % self) >>> super(SFTestViewWithInit, self).dealloc() >>> ---------------- >>> >>> Instances of each of these have been put as top-level objects in a >>> nib >>> file that's owned by an NSViewController subclass. Actually, to be >>> entire correct, I dragged in 'custom views' and set the class of one >>> of them to SFTestViewVanilla, and another one to SFTestViewWithInit. >>> So those custom view placeholders are replaced with my custom >>> subclasses when the nib is loaded, causing initWithFrame: to be >>> called >>> on both of them. >>> >>> When the CHViewController is released, the SFTestViewVanilla >>> instance >>> is deallocced and produces a log entry, the SFTestViewWithInit >>> instance however, is not being deallocced. The only difference >>> between both is the implementation of them pasted above. >>> >>> More Testing: >>> I've also tried not assigning to self in SFTestViewWithInit's >>> initWithFrame: implementation but that makes no difference >>> whatsover. >>> >>> As a next step of figuring stuff out: If I set up outlets to each, >>> and >>> check their retainCount in awakeFromNib, they both start out with a >>> retainCount of 1. >>> >>> Initially I suspected that there's something weird going on with >>> NSViewController's memory management of its top-level objects, if >>> those objects are implemented in Python. This weirdness however >>> only >>> happens with objects that implement the designated initializer. >>> Next >>> I've added these same views as top-level objects in a different nib >>> that's owned by an NSWindowController subclass, where they exhibit >>> the >>> exact same behavior. In both cases there are no outlets to the >>> views >>> (just added those at some point to look at retainCounts for >>> additional >>> clues) and the views aren't part of any view hierarchy. >>> >>> The same seems to happen with other objects that have their own >>> init, >>> like my NSArrayController subclass and other things that don't >>> inherit >>> from NSView. (I have not isolated that behavior yet, but from my >>> app's >>> code I suspect this is going on) >>> >>> Right now, I'm special casing every viewController's and >>> windowController's dealloc to manually release these views, but I >>> completely fail to understand why something is going haywire with >>> the >>> 'automatic' releasing of top-level objects that NSViewController and >>> NSWindowController are supposed to do.. This seems to be a bug, >>> that >>> I'd rather fix in one spot than work around everywhere. >>> >>> I'd love to look into this and fix it myself, write a regression >>> test, >>> and submit a patch. Could you help me out with some pointers on >>> where >>> to start looking in the PyObjC source? >>> >>> Thanks! >>> - Dirk >>> >>> PS: I'm using pyobjc_core-2.2b2-py2.5, installed from a >>> downloaded .tar from pypi.python.org, because installing from trunk >>> didn't work out on my machine (10.5.7) >>> >>> On Jun 19, 2009, at 10:47 AM, Ronald Oussoren wrote: >>> >>>> >>>> On 18 Jun, 2009, at 22:47, Orestis Markou wrote: >>>> >>>>> Hi Ronald, >>>>> >>>>> does that stand for other arbitrary class methods, like >>>>> CALayer.layer >>>>> () as well? Is there a way to inform PyObjC of constructor class >>>>> methods? Or should we change our code to use alloc().init() >>>>> instead? >>>> >>>> No. The only class methods that behave like alloc are alloc itself >>>> and anything that starts with 'new'. Regular class methods return >>>> autoreleased objects. >>>> >>>> The behaviour of method names that start with 'new' is newish, the >>>> last time I check (which was a couple of years ago), only +alloc >>>> returned a retained value. >>>> >>>> Ronald >>>> >>>> >>>> ------------------------------------------------------------------------------ >>>> Crystal Reports - New Free Runtime and 30 Day Trial >>>> Check out the new simplified licensing option that enables >>>> unlimited >>>> royalty-free distribution of the report engine for externally >>>> facing >>>> server and web deployment. >>>> http://p.sf.net/sfu/businessobjects >>>> _______________________________________________ >>>> Pyobjc-dev mailing list >>>> Pyo...@li... >>>> https://lists.sourceforge.net/lists/listinfo/pyobjc-dev >>> >>> >>> ------------------------------------------------------------------------------ >>> Crystal Reports - New Free Runtime and 30 Day Trial >>> Check out the new simplified licensing option that enables unlimited >>> royalty-free distribution of the report engine for externally facing >>> server and web deployment. >>> http://p.sf.net/sfu/businessobjects >>> _______________________________________________ >>> Pyobjc-dev mailing list >>> Pyo...@li... >>> https://lists.sourceforge.net/lists/listinfo/pyobjc-dev >> > > > ------------------------------------------------------------------------------ > Are you an open source citizen? Join us for the Open Source Bridge > conference! > Portland, OR, June 17-19. Two days of sessions, one day of > unconference: $250. > Need another reason to go? 24-hour hacker lounge. Register today! > http://ad.doubleclick.net/clk;215844324;13503038;v?http://opensourcebridge.org > _______________________________________________ > Pyobjc-dev mailing list > Pyo...@li... > https://lists.sourceforge.net/lists/listinfo/pyobjc-dev |