1

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
Halle
  • 3,584
  • 1
  • 37
  • 53
  • Changed the above slightly to reflect that AnalysisTool warned that there was a call after a release in the original code. The original memory issue remains. – Halle Jul 30 '09 at 14:41
  • I have a workaround of making the picker an instance variable with @property and @synthesize etc, which increases the overall memory footprint but results in there being a cap on how much memory it grabs no matter how many times it is requested. Still curious about the underlying reason for the leak, though. – Halle Jul 30 '09 at 17:15
  • More info: I ran Object Allocations, and in the timeframe in which the footprint is increasing, the big allocation increase is a category called "GeneralBlock-53248", which adds about 50k bytes in the moment that the Address Book is first called and then another 50k when the second page of the Address Book is called in the case of multiple email addresses, with a Net Bytes and Overall Bytes of the same size (i.e. it never releases any bytes). When I click the disclosure arrow from the summary I'm shown that at the time that the bytes were added, libsqlite3.dylib is calling sqlite3_blob_write. – Halle Jul 30 '09 at 17:20

3 Answers3

1

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

James
  • 5,812
  • 5
  • 26
  • 30
  • I personally find it incredibly handy for seeing where I might of missed a release after a set of if statements – James Jul 30 '09 at 12:32
  • 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 Jul 30 '09 at 13:32
  • 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 Jul 30 '09 at 14:12
  • 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 Jul 30 '09 at 14:37
  • Why should autorelease be avoided on the iPhone? Do you mean garbage collection? – Nick Veys Jul 30 '09 at 17:25
  • 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 Jun 24 '10 at 07:31
0

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 Aug 09 '09 at 10:11
  • See this http://stackoverflow.com/questions/5074684/memory-leak-with-abpeoplepickernavigationcontroller – nspire Apr 13 '11 at 16:17
0

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
  • 579
  • 3
  • 9