16 berichten aan het bekijken - 1 tot 16 (van in totaal 16)
  • Q:
    Bijdrager
    Jakko Westerbeke

    AppleScriptObjC: Geheugengebruik NSImageView

    Een AppleScriptObjC-programma dat ik geschreven heb heeft een NSImageView waarin d.m.v. een NSImage afbeeldingsbestanden geladen worden van de harde schijf. Dit lijkt geheugen te lekken, maar ik kom er niet uit hoe ik dat op moet lossen.

    Wat speurwerk levert op dat het zo te zien aan de NSImageView ligt, en niet aan de NSImage: als ik namelijk een NSImage met het bestand (of de inhoud daarvan) initialiseer, maar die níét door een NSImageView laat weergeven, blijft het geheugengebruik van het programma constant. Zodra de NSImageView wél de afbeelding laat zien, gaat het geheugengebruik omhoog bij elke nieuwe afbeelding die geladen wordt. Hoe lang ik het ook aan laat staan, naar beneden gaat het nooit.

    Afgaande op wat ik hier vanavond over heb zitten lezen, lijkt het erop dat de data van NSImageView niet automatisch gereleaset wordt wanneer de afbeeldingen in een loop geladen worden, omdat dat pas zou gebeuren nadat de loop ten einde is. In mijn programma gebruik ik een NSTimer om regelmatig een nieuwe afbeelding te laden, wat hetzelfde effect lijkt te hebben als een eenvoudige eindeloze loop.

    De enige manier om dat op te lossen lijkt te zijn om gebruik te maken van een @autorelease-blok in Objective C … waarvoor geen equivalent is in AppleScriptObjC, voor zover ik begreep/kon vinden.

    Iemand enig idee wat ik hieraan kan doen?

    Bijdrager
    dj bazzie wazzie

    Een NSTimer wordt op een andere thread wordt uitgevoerd en moet een nieuwe autorelease pool krijgen, dit zou het AppleScriptObjC framework voor jou moeten doen maar helaas. AppleScript is single threaded en je zou AppleScriptObjC ook zo moeten zien (maar is niet zo). Taken die op een andere thread uitgevoerd moeten wordt over het algemeen aangeraden om in Objective-C te schrijven en vanuit AppleScriptObjC aan te roepen. Heb zelf ook voorbeeldcode op MacScripter gezet hoe je met threads moest werken maar dat was nog in de tijd van GC en niet van ARC.

    Bijdrager
    Jakko Westerbeke

    De hele reden dat ik AppleSctipt gebruik is omdat ik aan Objective C geen touw vast kan knopen:) Misschien dat dit een goede reden is om m’n programma maar eens te herschrijven in Swift …

    Bijdrager
    dj bazzie wazzie
    Jakko op 01 maart 2016

    De hele reden dat ik AppleSctipt gebruik is omdat ik aan Objective C geen touw vast kan knopen:) Misschien dat dit een goede reden is om m’n programma maar eens te herschrijven in Swift …

    Het was niet mijn bedoeling om AppleScriptObjC af te schrijven maar af te zien van een NSTimer class.

    AppleScript

    OSA and AppleScript are now thread-safe: they may be safely called on a non-main thread or from multiple threads without any locking in the client code. This also applies to NSAppleScript. This does not mean that AppleScript is totally concurrent: AppleScript uses locking to ensure that any single connection (a ComponentInstance) will only run on one thread at a time. Because of the size of the locking granularity, trying to manipulate the same script from multiple threads at once may still be subject to race conditions, and is not recommended.

    De vraag is in hoeverre dat mogelijk is met de code die je geschreven hebt. Zelf zit ik te denken aan het gebruik van performSelector:withObject:afterDelay:. Geen retain, niet op threads zonder run loop en geen autorelease pool nodig.

    Mocht je erop staan een NSTimer te gebruiken dan raad ik aan een NSTimer te maken en maar één keer uit te voeren met een delay en een selector naar zichzelf (die vervolgens deze timer weer destroyed). Hierdoor wordt er telkens een nieuwe NSTimer object aangemaakt en ontvangen de objecten gebruikt door NSTimer een release (hiervoor heb je normaliter de autorelease pool voor nodig). Hierdoor zullen je leaks verdwijnen maar hiervoor is toch eigenlijk de performSelector:withObject:afterDelay: method van NSObject.

    Bijdrager
    Jakko Westerbeke
    dj op 02 maart 2016

    Het was niet mijn bedoeling om AppleScriptObjC af te schrijven maar af te zien van een NSTimer class.

    Ik loop al langer rond met het idee om de huidige code te vervangen door Swift, dit is misschien het zetje dat ik nodig heb om dat ook te doen:)

    dj op 02 maart 2016

    performSelector:withObject:afterDelay:

    Ik wist van het bestaan daarvan überhaupt niet af — zoekend naar hoe je iets om de zoveel tijd kunt laten uitvoeren kwam ik uit bij NSTimer. Als ik de documentatie ervan doorlees, lijkt dat inderdaad te doen wat ik wil, maar ik heb zo snel nog geen idee van hoe ik het ook echt moet gebruiken:)

    Bijdrager
    dj bazzie wazzie

    Hier een klein voorbeeld hoe je dit kan gebruiken:

     
    script myAppDelegate
    	property parent : class "NSObject"
     
    	on applicationWillFinishLaunching:aNotification
    		my performSelector:"nextImage:" withObject:30 afterDelay:30
    	end applicationWillFinishLaunching:
     
    	on applicationShouldTerminate:sender
    		return current application's NSTerminateNow
    	end applicationShouldTerminate:
     
    	on nextImage:interval
    		-- update hier je NSImage
     
    		-- roep deze handler openieuw aan met vertraging
    		my performSelector:"nextImage:" withObject:interval afterDelay:interval
    	end nextImage:
    end script
     
    Bijdrager
    Jakko Westerbeke

    Bedankt, ik zal er binnenkort eens mee aan de slag gaan:)

    Bijdrager
    Jakko Westerbeke

    Eindelijk eraan toegekomen om hieraan verder te knutselen, en het werkt keurig als vervanging van de timer — maar het doet niks aan het geheugenprobleem. Als ik het programma met dezelfde serie foto’s laat draaien, dan is het geheugengebruik eigenlijk hetzelfde of ik nu een NSTimer neem of performSelector:withObject:afterDelay::(

    Ik denk dat ik m’n heil zal gaan zoeken in het vernietigen en opnieuw aanmaken van de NSImageView voordat het volgende plaatje geladen wordt. Wat later: zo te zien maakt dat óók al niks uit … afbeelding weghalen van z’n superview, dan op missing value zetten (wat, voor zover ik weet, zou moeten zorgen dat de NSImageView opgeruimd wordt), en het geheugengebruik blijft toch altijd maar stijgen. Het geeft aan mij toch heel sterk de indruk dat het geheugen van afbeeldingen, als ze eenmaal in een NSImageView geladen zijn, nooit vrijgemaakt wordt totdat het programma stopt.

    Bijdrager
    rsluman

    Jakko, ik heb je programmacode natuurlijk niet gezien, maar dit lijkt een probleem met een strong-pointer. Gebruik je ergens in je NSTimer-code een strong pointer naar je NSImage-object? Zo ja, maak hem eens weak…

    Je NSTimer-block heeft een strong pointer naar een object ‘erbuiten’ (het NSImage-object): dat betekent dat je een retain-cycle zou kunnen hebben gemaakt. Als je vanuit een block naar objecten buiten dat block verwijst, zul je dat via weak-pointers willen doen.

    Nogmaals, het is een beetje blind varen zonder programmacode. Maar wie weet helpt dit je. Succes!

    Bijdrager
    Jakko Westerbeke

    En hoe doe je dat in AppleScriptObjC?:) In Objective C en Swift kun je aangeven of iets weak of strong is, maar in AppleScriptObjC is die optie er voor zover ik weet niet. Ik heb een NSImageView, gemaakt in het Interface Builder-deel van Xcode (dus niet programmatisch toegevoegd aan het venster), en daarin laad ik op deze manier een afbeelding:

    set mijnPlaatje to NSImage's alloc()'s initByReferencingFile_(mijnBestandsnaam)
    mijnImageView's setImage_(mijnPlaatje)

    Let trouwens op: het is zo te zien niet de NSImage die het probleem veroorzaakt, maar de NSImageView. Als ik namelijk de afbeeldingen uit de bestanden in een NSImage inlees maar niet door een NSImageView laat tonen, blijft het geheugengebruik grofweg constant (afhankelijk van de afbeelding, natuurlijk) maar wanneer ik ze ook in de NSImageView zet dan stijgt het geheugengebruik elke keer als er een nieuw plaatje geladen wordt.

    Bijdrager
    rsluman

    Jakko, ik ben geen AppleScriptObjC-expert. Maar nu je zegt dat je een NSImageView via Interface Builder hebt toegevoegd: de pointer naar die NSImageView is dus waarschijnlijk strong!

    En dan heb je dus ook al een retain cycle. Zodra je die NSImageView vanuit je NSTimer-object benadert, creëer je de tweede (en derde, vierde enzovoort) cycle.

    Ik heb even via Google gezocht, maar ik kan nergens iets vinden over het declareren van weak-pointers in AppleScriptObjC. Wellicht zou je een vraag daarover via StackOverflow kunnen stellen…

    Bijdrager
    dj bazzie wazzie
    rsluman op 21 maart 2016

    Jakko, ik ben geen AppleScriptObjC-expert. Maar nu je zegt dat je een NSImageView via Interface Builder hebt toegevoegd: de pointer naar die NSImageView is dus waarschijnlijk strong!

    Je hebt in AppleScriptObjC geen beheer over strong of weak pointers. Het probleem voor Jakko is dat er geen autorelease pool is. Er is waarschijnlijk geen geheugenlek maar hij krijgt een enorme memory footprint waardoor steeds nieuw geheugen wordt aangeroepen. Omdat je in AppleScriptObjC ook geen autorelease pools kan aanmaken is het gebruik van NSTimer niet echt aan te raden, helemaal niet wanneer je AppleScriptObjC methods gaat gebruiken (lees: AppleScript zelf is single threaded).

    rsluman

    Ik heb even via Google gezocht, maar ik kan nergens iets vinden over het declareren van weak-pointers in AppleScriptObjC. Wellicht zou je een vraag daarover via StackOverflow kunnen stellen…

    Stackoverflow is uiterst beperkt in support op gebied van AppleScriptObjC. Aanrader voor mainstream programmeertalen, AppleScript support is ronduit slecht en sluit daar ook veel antwoorden omdat ze vaak niet kloppen. Het is niet verkeerd om in de AppleScript mailing list je vraag te stellen maar zullen dan minstens voorbeeldcode willen zien. In de mailing list kom je ook in contact komt met de AppleScript ontwikkelaars die je uit leggen waarom bepaalde dingen zo gaan in AppleScriptObjC in tegenstelling tot bijv PyObjC. Je kan je vraag ook stellen op MacScripter waar Shane Stanley, auteur van AppleScriptObjC boeken (eerste boeken nog mee geholpen) heeft geschreven, daar elke dag wel een keer online komt en je vraag zal beantwoorden.

    Bijdrager
    dj bazzie wazzie
    Jakko

    set mijnPlaatje to NSImage’s alloc()’s initByReferencingFile_(mijnBestandsnaam)
    mijnImageView’s setImage_(mijnPlaatje)

    Als het echt een lek zou zijn kun je prima schrijven:

     
    set mijnPlaatje to NSImage's alloc()'s initByReferencingFile_(mijnBestandsnaam)
    mijnImageView's setImage_(mijnPlaatje)
    mijnPlaatje's release()
     

    Als dit werkt, is er iets fout, als dit wel goed functioneert krijg je een BAD_EXEC error (dubbele release) en crasht te programma of springt naar debugger. Misschien de moeite van het testen waard.

    Bijdrager
    Jakko Westerbeke
    rsluman op 21 maart 2016

    ik ben geen AppleScriptObjC-expert.

    Ik heb veel te veel interesses om expert te zijn in meer dan één of twee ervan — helaas is dit er eentje van de rest:)

    rsluman op 21 maart 2016

    Maar nu je zegt dat je een NSImageView via Interface Builder hebt toegevoegd: de pointer naar die NSImageView is dus waarschijnlijk strong!

    Wat ik gisteravond ook nog heb geprobeerd is om — zoals ik al zei — de NSImageView elke keer te vernietigen en opnieuw te maken, precies met de bedoeling om het geheugen vrij te krijgen: het zou op dat punt toch niet uit moeten maken of de pointer weak of strong is, zou ik denken? Toch maakte het helemaal niks uit qua geheugengebruik.

    rsluman op 21 maart 2016

    En dan heb je dus ook al een retain cycle. Zodra je die NSImageView vanuit je NSTimer-object benadert, creëer je de tweede (en derde, vierde enzovoort) cycle.

    Maar ook als ik het probeer met performSelector:withObject:afterDelay: zoals dj bazzie wazzie aanraadde in plaats van de NSTimer. Ik denk dat ik maar eens een kort vergelijkbaar programmaatje moet maken in Swift om te kijken of zich daar hetzelfde probleem voordoet, of dat het aan AppleScriptObjC ligt.

    dj op 21 maart 2016

    Als het echt een lek zou zijn kun je prima schrijven:

     
    set mijnPlaatje to NSImage's alloc()'s initByReferencingFile_(mijnBestandsnaam)
    mijnImageView's setImage_(mijnPlaatje)
    mijnPlaatje's release()
     

    Als dit werkt, is er iets fout, als dit wel goed functioneert krijg je een BAD_EXEC error (dubbele release) en crasht te programma of springt naar debugger.

    Met „als dit werkt” bedoel je: als het programma gewoon doorloopt?

    dj op 21 maart 2016

    Misschien de moeite van het testen waard.

    Ik kan het nu zo snel niet proberen, maar wat later vanavond als het goed is wel. Ik zal er in elk geval zo snel mogelijk eens naar kijken wat dit precies doet.

    Bijdrager
    dj bazzie wazzie
    Jakko
    rsluman

    Maar nu je zegt dat je een NSImageView via Interface Builder hebt toegevoegd: de pointer naar die NSImageView is dus waarschijnlijk strong!

    Wat ik gisteravond ook nog heb geprobeerd is om — zoals ik al zei — de NSImageView elke keer te vernietigen en opnieuw te maken, precies met de bedoeling om het geheugen vrij te krijgen: het zou op dat punt toch niet uit moeten maken of de pointer weak of strong is, zou ik denken? Toch maakte het helemaal niks uit qua geheugengebruik.

    Correct! Het AppleScriptObjC.framework moet dit voor je doen. Het AppleScriptObjC framework biedt objecten (AppleEvent «class ocid») aan die als content het pointer adres naar een Objective-C class/object bevat. Deze objecten in AppleScript zijn via het AppleScriptObjC.framework dan weer bruikbaar in de Objective-C runtime. Hierdoor kan een message (lees: class en instance methods) verzonden worden naar het Objective-C object via dezelfde runtime. Hierdoor maakt AppleScript zelf nooit gebruik van strong of weak pointers maar zijn altijd strong pointers naar objecten toe maar is irrelevant omdat het eigenlijk pointer pointers zijn. Nogmaals het AppleScriptObjC.framework bepaald voor jou of het een weak of strong pointer gebruikt. Of simpeler gezegd, het framework bepaald of er wel of geen retain() nodig is.

    Daarnaast wanneer je een NSTimer gebruikt, waar je probleem al opduikt moet je een strong pointer gebruiken omdat anders je object al gereleased wordt voordat het gebruikt wordt door NSTimer.

    Jakko
    rsluman

    En dan heb je dus ook al een retain cycle. Zodra je die NSImageView vanuit je NSTimer-object benadert, creëer je de tweede (en derde, vierde enzovoort) cycle.

    Maar ook als ik het probeer met performSelector:withObject:afterDelay: zoals dj bazzie wazzie aanraadde in plaats van de NSTimer. Ik denk dat ik maar eens een kort vergelijkbaar programmaatje moet maken in Swift om te kijken of zich daar hetzelfde probleem voordoet, of dat het aan AppleScriptObjC ligt.

    performSelector:withObject:afterDelay: raadde ik aan om meerdere redenen inclusief de retain-cycle die rsluman benoemd maar ook het niet nodig hebben van een autorelease pool (waar ik dacht dat je fout zat). Ik gebruik zelf deze handler om een idle handler te simuleren zonder geheugenlekken. Hoewel NSTimer en performSelector beide de runloop gebruiken en voor velen hetzelfde zijn is er voor AppleScriptObjC wel degelijk verschil.

    performSelector:withObject:afterDelay:

    dj

    Als het echt een lek zou zijn kun je prima schrijven:

     
    set mijnPlaatje to NSImage's alloc()'s initByReferencingFile_(mijnBestandsnaam)
    mijnImageView's setImage_(mijnPlaatje)
    mijnPlaatje's release()
     

    Als dit werkt, is er iets fout, als dit wel goed functioneert krijg je een BAD_EXEC error (dubbele release) en crasht te programma of springt naar debugger.

    Met „als dit werkt” bedoel je: als het programma gewoon doorloopt?
    [/quote]

    Nu ik het weer leest klinkt het raar inderdaad. Wat ik daarmee bedoelde was wanneer je een extra retain toevoegd en je programma heeft dan ineens geen lek meer (je programma werkt goed) betekend dat er toch iets fout zit in het AppleScriptObjC.framework.

    Jakko
    dj op 21 maart 2016

    Misschien de moeite van het testen waard.

    Ik kan het nu zo snel niet proberen, maar wat later vanavond als het goed is wel. Ik zal er in elk geval zo snel mogelijk eens naar kijken wat dit precies doet.

    Het doel hiervan is om uit te sluiten dat het framework een retain teveel toepast waar je in AppleScript geen controle over hebt. Ik ga er alsnog vanuit dat je programma vastloopt omdat je het object direct vernietigd en dat dit niet helpt. Dit is puur een process van het uitsluiten van problemen. Ook zou je kunnen kijken of initWthContentsOfFile: wel goed gaat omdat op deze manier niet de NSImage een tweede image data object aanmaakt.

    Het is uiteraard vanaf hier een beetje giswerk en helemaal lastig omdat ik een soortgelijk probleem niet heb kunnen realiseren. Ik ga nu, omdat ik zelf hier heel erg benieuwd naar ben, met Xcode 6.2 en Mavericks even snel een project in elkaar zetten en kijken hoe het zich gedraagt. Ik kom hier dan nog op terug.

    update:
    Ik heb het volgende kleine programma geschreven, denk dat de code voor zich spreekt en heb even naar geheugengebruik gekeken (map geselecteerd met paar duizend foto’s).

     
    property NSMutableDictionary : class "NSMutableDictionary"
    property NSImage : class "NSImage"
     
    script AppDelegate
    	property parent : class "NSObject"
     
        property listOfFiles : {}
        property imageIndex : 0
     
    	-- IBOutlets
    	property theWindow : missing value
        property theImageView : missing value
     
    	on applicationWillFinishLaunching_(aNotification)
    		-- Insert code here to initialize your application before any files are opened
            set theFolder to choose folder
            tell application "System Events"
                set imageFiles to POSIX path of every disk item of theFolder whose name ends with ".jpg"
            end tell
            set theDict to NSMutableDictionary's alloc()'s init()
            theDict's setValue:imageFiles forKey:"theFiles"
            theDict's setValue:0 forKey:"currentImage"
            my nextImage:theDict
    	end applicationWillFinishLaunching_
     
    	on applicationShouldTerminate_(sender)
    		-- Insert code here to do any housekeeping before your application quits 
    		return current application's NSTerminateNow
    	end applicationShouldTerminate_
     
        on nextImage:userInfo
            set idx to (userInfo's valueForKey:"currentImage") as integer
            set fileList to userInfo's valueForKey:"theFiles"
     
            if idx = count of fileList then
                set idx to 1
            else
                set idx to idx + 1
            end
     
            userInfo's setValue:idx forKey:"currentImage"
     
            set theImage to NSImage's alloc()'s initWithContentsOfFile:fileList's objectAtIndex:(idx -1)
            theImageView's setImage:theImage
     
            my performSelector:"nextImage:" withObject:userInfo afterDelay:0.5
        end
    end script
     

    Wegens gebrek aan een autoreleasepool binnen de selector heb ik een grote memory footprint. Het geheugen loopt op tot 800mb en dan wordt ineens alles gereleased. Maar ik heb ik géén geheugenlek.

    Bijdrager
    Jakko Westerbeke

    Ik zal eens een update geven over dit onderwerp:) Na de AppleScript-code vervangen te hebben door Swift, maar op precies dezelfde manier te werk gaand (met een NSTimer en twee NSImageViews) heb ik nu een programma waarvan het geheugengebruik grofweg constant blijft op iets van 35–40 MB ook al staat het uren aan, terwijl hetzelfde programma in AppleScript al na een uurtje of zo aan meerdere gigabytes kwam. Het is volgens mij wel duidelijk dat het probleem bij AppleScriptObjC ligt.

16 berichten aan het bekijken - 1 tot 16 (van in totaal 16)

Je moet ingelogd zijn om een reactie op dit onderwerp te kunnen geven.