views:

622

answers:

3

Hello,

I am getting email addresses out of the Address Book from a Cocoa Touch project and getting some unexpected results in terms of memory usage. The user opens the ABPeoplePicker and if the AB entry they touch has a single email address or no email address it uses

  • (BOOL) peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person

and if the entry has multiple email addresses it moves on to

  • (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier {

In the single email address case, all of the memory used by the picker is released after the email address is selected. In the second multiple email case, about 300k is kept and not released, and this increases every time a multi-email Address Book entry is chosen. I believe that I have manually released everything I need to in the AB methods and I can't track down what is holding on to that memory or how to fix it, and I'm not seeing any other posts about this being a bug so I suspect I have an error. If anyone has any ideas what is going on here, please let me know. I have attached example code below for those who wish to reproduce the problem - it behaves identically in the simulator as on the device so you can run it in the simulator with Activity Monitor to see the memory usage. Thank you for any assistance!

Both AddressBook.framework and AddressBookUI.framework need to be added to a project running this code in order for it to function, and I am using the 3.0 SDK:

testViewController.h:

#import <UIKit/UIKit.h>
#import <AddressBook/AddressBook.h>
#import <AddressBookUI/AddressBookUI.h>
@interface testViewController : UIViewController <ABPeoplePickerNavigationControllerDelegate> {
    UITextView *emailList ;
}

@property (nonatomic, retain) UITextView *emailList ;
@end

testViewController.m:

#import "testViewController.h"

@implementation testViewController

@synthesize emailList;

- (void) showContactPicker:(id)sender {

ABPeoplePickerNavigationController *picker = [[ABPeoplePickerNavigationController alloc] init];
picker.peoplePickerDelegate = self;
[self presentModalViewController:picker animated:YES];
[picker release];
}


- (void) peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker {
[self dismissModalViewControllerAnimated:YES];
}

- (BOOL) peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person {

    BOOL returnState = NO;

    ABMultiValueRef emails = ABRecordCopyValue(person, kABPersonEmailProperty);

    if(ABMultiValueGetCount(emails) <= 0) { // the selected contact has no attached email address

     [self dismissModalViewControllerAnimated:YES];
    }

    else if(ABMultiValueGetCount(emails) == 1) { // the selected contact has exactly one email address

     CFStringRef email = ABMultiValueCopyValueAtIndex(emails, 0);
     NSString *emailString = (NSString *) email;
     self.emailList.text = [self.emailList.text stringByAppendingString:[NSString stringWithFormat:@"%@ ", emailString]];
     [emailString release];
     [self dismissModalViewControllerAnimated:YES];
    }

    else { // the selected contact has many email addresses, continue to the alternate method
     returnState =  YES;
    }

    CFRelease(emails);
    return returnState;
}




- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier {  

    ABMultiValueRef multiEmails = ABRecordCopyValue(person, kABPersonEmailProperty);
    CFStringRef multiEmail = ABMultiValueCopyValueAtIndex(multiEmails, identifier);
    CFRelease(multiEmails);
    NSString *multiEmailString = (NSString *) multiEmail;
    //CFRelease(multiEmail); //AnalysisTool pointed out that this is a double release since multiEmailString is an alias of multiEmail
    self.emailList.text = [self.emailList.text stringByAppendingString:[NSString stringWithFormat:@"%@ ", multiEmailString]];
    [multiEmailString release];
    [self dismissModalViewControllerAnimated:YES];
    return NO;
}


- (void)viewDidLoad {
     [super viewDidLoad];

        NSArray *openContactsTitle = [[NSArray alloc] initWithObjects:@"Add Addresses", nil];
        UISegmentedControl *openContacts = [[UISegmentedControl alloc] initWithItems:openContactsTitle];
    openContacts.frame = CGRectMake(10,10,105,30);
    [openContacts addTarget:self action:@selector(showContactPicker:) forControlEvents:UIControlEventValueChanged];
    openContacts.segmentedControlStyle = UISegmentedControlStyleBar;
    openContacts.momentary = TRUE;
    [self.view addSubview:openContacts];
    [openContacts release];
    [openContactsTitle release];

    emailList = [[UITextView alloc] initWithFrame:CGRectMake(10,60,200,200)];
    [self.view addSubview:emailList];
    emailList.text = @"";
}


- (void)dealloc {
    [emailList release];
    [super dealloc];
 }

@end
+1  A: 

You could try running AnalysisTool on it to see if it can detect any leaks in the code

James Raybould
I'll check it out, thanks James.
Halle
I personally find it incredibly handy for seeing where I might of missed a release after a set of if statements
James Raybould
I'm running it now and it's incredibly useful. The only thing that's a little odd is that it complains about the lack of autorelease after allocations/initializations that are manually memory managed. I was under the impression that autorelease was to be avoided on the iPhone.
Halle
You can turn off that off in the preferences - I actually get rid of quite a few of the extra checks - aka any with the prefix "AT"
James Raybould
OK, most of the AT ones I turned off, but that one I wasn't sure about since it seemed related to my issue. AnalysisTool found something in my code above, but it was unfortunately not the cause of the leak (still useful though!).
Halle
Why should autorelease be avoided on the iPhone? Do you mean garbage collection?
Nick Veys
Hi, sorry to miss this question for a year :) . From the Apple Cocoa Fundamentals Guide: "Because on iPhone OS an application executes in a more memory-constrained environment, the use of autorelease pools is discouraged in methods or blocks of code (for example, loops) where an application creates many objects. Instead, you should explicitly release objects whenever possible."
Halle
A: 

During the development of my iPhone App Serial Mail I had discovered a memory leak in ABPeoplePickerNavigationController. I have filed this as a bug to the Apple Bug Reporter. Feedback from Apple is that his is a known bug (my bug report is closed as a duplicate of ID 6547310).

Hi Christian,I also filed it as a bug and uploaded my sample project with the bug report.
Halle
A: 

One option is to make the picker a readonly property of the class and don't synthesize it. Instead, create a peoplePicker method ensure only a single instance of the picker is instantiated. If that doesn't work with your current view lifecycle one option would be to abstract this into an actual singleton class.

Here's an example I used for the image picker (camera) which has the same leak problem:

- (UIImagePickerController*)pickerController
{
    // pickerController is a readonly property
    if( pickerController == nil )
    {
     pickerController = [[UIImagePickerController alloc] init];
     pickerController.allowsImageEditing = NO;
    }
    return pickerController;
}

For this I placed all the releases in dealloc and didReceiveMemoryWarning (with a nil check to avoid releasing nil). In this case you will effectively limit how often the address book picker is instantiated. In many places Apple recommends using a singleton implementation for the memory-intensive picker APIs.

nessence