Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

thundersteele

macrumors 68030
Original poster
Oct 19, 2011
2,984
9
Switzerland
I'm working on a small app that provides information similar to coconut battery, but plugs into the menu bar. Not exactly groundbreaking, it's more a hobby for me right now ;)

The app relies on IOKit to access the battery information.

After launching the app, it uses about 10 MB of memory. However I noticed that after about a week of running, the memory usage is up to about 100 MB, and keeps growing at a very slow rate. Steps taken:

Enabled ARC (automatic reference counting) and adjusted the code accordingly. No change.
Used Instruments and found that IOkit leaked a few kb per minute - the app checks the battery status every 10 seconds, and each time it would leak some memory.
Disabled ARC, enabled garbage collection. This apparently closed most leaks, the app is down to leaking 20 KB per hour. At this point a small leakage occurs every few minutes, and doesn't seem to be correlated with any particular action inside the program.

At this point I should probably stop worrying... a full month of running would make the app use 20 MB. For comparison, I ran coconut battery through instruments, and it turns out to leak about the same amount of memory.

Below is a screenshot of instruments - it seems to indicate that the leak is somewhere deep within IOKit. However I'm well aware in most cases it's still a user error.
Also below is the relevant snipplet of the method that is called every 10 seconds:

Code:
 - (void) updateBatteryInfo
{
    // go to battery entry!    
    ioRegistryRoot = 
        IORegistryEntryFromPath(kIOMasterPortDefault, "IOService:/AppleACPIPlatformExpert/SMB0/AppleECSMBusController/AppleSmartBatteryManager/AppleSmartBattery");
    
    // read out properties
    tester=IORegistryEntryCreateCFProperties(ioRegistryRoot, &properties, kCFAllocatorDefault,0);
    
    // set above values!
    CFNumberGetValue((CFNumberRef) CFDictionaryGetValue(properties, CFSTR("CycleCount")), 9, &CycleCount);
    CFNumberGetValue((CFNumberRef) CFDictionaryGetValue(properties, CFSTR("CurrentCapacity")), 9, &CurrentCharge);

    // more of the same commands

}

I should probably try to manually release stuff (when implementing ARC, you have to remove all retain/release cycles).
 

Attachments

  • Screen Shot 2012-02-20 at 3.29.32 PM.png
    Screen Shot 2012-02-20 at 3.29.32 PM.png
    152.6 KB · Views: 182

thundersteele

macrumors 68030
Original poster
Oct 19, 2011
2,984
9
Switzerland
Do you ever release that dictionary?

No. I was following the ARC guide in the OSX developer documentation, which tells you to remove all release commands. Of course then I stopped using ARC, so maybe I should put back manual releases.

The way things work is that I have a class whose properties are the battery information as well as the (mutable) dictionary "properties" and the io_registry_entry_t "ioRegistryRoot."

Upon program launch, I create an instance of the class. The update method then updates the contents of the dictionary, and updates the values of the battery charge etc. In this sense, the program always uses the same dictionary.
 

subsonix

macrumors 68040
Feb 2, 2008
3,551
79
No. I was following the ARC guide in the OSX developer documentation, which tells you to remove all release commands. Of course then I stopped using ARC, so maybe I should put back manual releases.

The way things work is that I have a class whose properties are the battery information as well as the (mutable) dictionary "properties" and the io_registry_entry_t "ioRegistryRoot."

Upon program launch, I create an instance of the class. The update method then updates the contents of the dictionary, and updates the values of the battery charge etc. In this sense, the program always uses the same dictionary.

ARC relates to Cocoa, not CoreFoundation which is a C api. The "Create" rule should apply, ie function names which has the word "create" in them creates objects that you own, and you must release with CFRelease.

https://developer.apple.com/library...nceptual/CFMemoryMgmt/Concepts/Ownership.html

Edit:

Upon program launch, I create an instance of the class. The update method then updates the contents of the dictionary, and updates the values of the battery charge etc. In this sense, the program always uses the same dictionary.

The class is something different, because inside the class you have methods like your updateBatteryInfo, each time you call this method you create a new CFDictionary object, if your reference to this object is an instance variable in your class then the value of that variable will contain a new address for each call to updateBatteryInfo, ie you will lose reference to the last one.
 
Last edited:

thundersteele

macrumors 68030
Original poster
Oct 19, 2011
2,984
9
Switzerland
ARC relates to Cocoa, not CoreFoundation which is a C api. The "Create" rule should apply, ie function names which has the word "create" in them creates objects that you own, and you must release with CFRelease.

https://developer.apple.com/library...nceptual/CFMemoryMgmt/Concepts/Ownership.html

Edit:



The class is something different, because inside the class you have methods like your updateBatteryInfo, each time you call this method you create a new CFDictionary object, if your reference to this object is an instance variable in your class then the value of that variable will contain a new address for each call to updateBatteryInfo, ie you will lose reference to the last one.

Thanks for the explanations and for the link. This gives me something to do for the weekend!
I was not aware that ARC is restricted to Cocoa, although upon thinking about it, it's hardly a surprise.
 

Sydde

macrumors 68030
Aug 17, 2009
2,552
7,050
IOKWARDI
According to Apple's documentation, CFDictionary is "toll-free bridged" to NSDictionary: if you declare tester as a NSDictionary, you may be able to simply cast the call that creates the dictionary and let ARC manage the object for you. If tester is a strong ivar, ARC will release the previous dictionary before assigning the new value.
 

subsonix

macrumors 68040
Feb 2, 2008
3,551
79
According to Apple's documentation, CFDictionary is "toll-free bridged" to NSDictionary: if you declare tester as a NSDictionary, you may be able to simply cast the call that creates the dictionary and let ARC manage the object for you. If tester is a strong ivar, ARC will release the previous dictionary before assigning the new value.

But tester is not a CFDictionary, it's a kern_return_t type that is part of IOKit which is embedded C++. The CFDictionary is the pointer called "properties", as you can see it's address is passed as an argument which means that it's modified by IORegistryEntryCreateCFProperties. This make the whole casting idea non-obvious IMO, although __bridge_transfer seems to be the ticket.

Also related to IOKit, there might of course be more memory that needs to be taken care of that is not shown here.

I'm assuming that the procedure that happens here is something like this: the method to get information from IOKit is called periodically, after which the GUI is updated (I suppose all objects could be release here), rinse repeat.
 

Sydde

macrumors 68030
Aug 17, 2009
2,552
7,050
IOKWARDI
Also related to IOKit, there might of course be more memory that needs to be taken care of that is not shown here.

There appears to be a function IOObjectRelease() that might be called for WRT the ioRegistryRoot variable at some appropriate point. Perhaps it could safely be disposed of right after the dictionary is created?
 

subsonix

macrumors 68040
Feb 2, 2008
3,551
79
There appears to be a function IOObjectRelease() that might be called for WRT the ioRegistryRoot variable at some appropriate point. Perhaps it could safely be disposed of right after the dictionary is created?

I don't know, it might even be safe to call this function once at program startup then keep the reference for this object for the lifetime of the app since this relates to battery info. Might be wrong about that though, but the release of this object should by done by the caller it seems.
 

thundersteele

macrumors 68030
Original poster
Oct 19, 2011
2,984
9
Switzerland
ARC relates to Cocoa, not CoreFoundation which is a C api. The "Create" rule should apply, ie function names which has the word "create" in them creates objects that you own, and you must release with CFRelease.

The class is something different, because inside the class you have methods like your updateBatteryInfo, each time you call this method you create a new CFDictionary object, if your reference to this object is an instance variable in your class then the value of that variable will contain a new address for each call to updateBatteryInfo, ie you will lose reference to the last one.

This was exactly the reason I was leaking memory. I added a simple CFRelease(properties) in the update function, before a new dictionary is created.
ARC takes care of the remaining retain/release cycles, so that I have zero leaks now without garbage collection.

Thanks a lot!!!
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.