Question

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
Was it helpful?

Solution

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).

OTHER TIPS

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

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top