How to find memory leaks with Instruments

Leave a comment

I had a memory leak in my app and after hooking up my iPhone and running ‘Profile without building’ I got a Instruments Leaks screen pop up that looked like this:

Not knowing how to find the offending line of code I found clicking on the calltree stack and selecting ‘Hide System Libraries’ and ‘Show Obj-C Only’ options helped me see what was wrong.

And here was the offending class.

And double clicking this line brought up the actual line of code.

Voila!

Advertisement

Test Automation (UIAutomation) example with XCode Instruments

14 Comments

UIAutomation is the library/tool Apple added in iOS SDK 4.0 to help with test automation on the iOS platform.

Scripts are written in JavaScript and executed through the Instruments Automation Instrument.

Here a quick walkthrough of how to record a script from your iPhone and play it back on your device.

Open Instruments

You open instruments through XCode -> Open Developer Tool -> Instruments.

Add Automation Instrument

There’s lots of instruments to choose from. The one we want is the ‘Automation’ instrument.

Add a script

Choose your target

Your target isn’t your device. It’s the device and your application.

Hit the record button

Record your script by hitting the ‘record’ button at the top or bottom of the tool and then click through your app.

Play it back

To see your script hit the funny looking Trace Log/Editor Log drop down and switch to scrip log view.

You should then see your recorded script.

You can play it back by clicking the ‘play’ button beside the record button at the bottom and you should see the same script played back against your phone. Output should look like this.

Save your script

This simple script doesn’t do too much, but it’s a start and if you want to save it and play it back later you can create a directory and story it in your app like this:

Much more

There’s a lot more we can do with UIAutomation. This is just a start and we’ve only scratched the surface.

But you can do a lot with this tool, and this is merely the first step.

Note: If your tests don’t run, make sure you aren’t connected and running your device from XCode. Can’t run both simultaneously so stop the XCode one then try running your tests again.

Links that help:

http://alexvollmer.com/posts/2010/07/03/working-with-uiautomation/

UIAutomation reference

How to tell if location services are turned on iphone ios

Leave a comment

There’s two things to check when you need to know if ‘Location Services’ are turned on for your application.

1. Is Location Services in general turned on.
2. Is it turned on for your app.

Here’s how you can determine the former.

- (void) checkLocationServicesTurnedOn {
    if (![CLLocationManager locationServicesEnabled]) {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"== Opps! ==" 
  message:@"'Location Services' need to be on."
  delegate:nil
  cancelButtonTitle:@"OK" 
  otherButtonTitles:nil];
        [alert show];      
    }     
}

Here’s how you do the latter.

-(void) checkApplicationHasLocationServicesPermission {
    if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"== Opps! ==" 
message:@"This application needs 'Location Services' to be turned on."
delegate:nil
cancelButtonTitle:@"OK" 
otherButtonTitles:nil];
        [alert show];      
    }    
}

Here’s where I usually call these from.

- (void)viewDidLoad
{
    [super viewDidLoad];    
    [self checkLocationServicesTurnedOn];
    [self checkApplicationHasLocationServicesPermission];    
}

Basically there is no way to force location services on. All you can do is check, and then ask the user nicely to turn them on if they want to use your application.

How to tell your iPhone application that location services are required

Leave a comment

Apple has a thing where they like your app to tell them what services, if any, are required.

They do this by tracking meta data in what are known as plists. A dictionary of configuration items for your app.

My app needs location services. So it wouldn’t make sense for any devices to download my app if they don’t support location services.

The way I tel my app that location services are required is to open my plist file:

and then go to the ‘Required Device Capabilities’ section and add ‘location-services’.

Note – this is a human readable for of what gets stored in the actually plist file. To see the actual property name hold down control and click on item and select the ‘Show Raw Key Values’ option.

Then you will see the actual underlying property configuration name.

Where is NSLog output for OCUnit tests?

4 Comments

If you’re writing out to NSLog() and can’t find the output don’t worry you are not alone.

- (void)testNSLog
{
    NSLog(@"\n\n=============");
    NSLog(@"Logging!");
    NSLog(@"\n\n=============");
    
    STAssertEquals(2, 3, nil);
}

That’s because all logging goes to the build logs which is in another window.

To view build log output click that comic book voice bubble looking thing in the upper right hand corner in XCode

Then click on the line of your latest unit test run.

Then way over on the right hand side of your unit test line you’ll see a stack of lines.

That’s your NSLog() output window.

Click on it and voila! You should see your output.

How to add unit tests to existing iOS project

4 Comments

After struggling with an illogical UI I finally figured out how to add unit tests to an existing project in XCode 4.2.

Open your project.

Goto File -> New -> New Target

Select iOS Cocoa Touch bundle.

Give it a name like ‘LogicTests’

Then click on your blue app area in your navigation.

Then click ‘Build Phases’ and expand the Target Dependency area. See how it’s blank?

That’s a problem. We need to add your application here so your unit test target is linked to your app.

So go ahead and click the ‘+’ sign and add your application.

Now you should be able to run your unit tests (by holding down the build icon until it turns to a test icon).

When you do you’ll see a configure app for testing window pop-up.

Just click ‘Edit Scheme…’ hit the plus sign (making sure the Test/Debug panel is highlighted) and add your unit test scheme as shown below.

Now you can hit that test button and all your unit tests should run.

There’s a gotcha

There’s one more gotcha you need to be aware of. As of XCode 4.2 when you add a new test class

You will get a screen that looks like this:

The defaults here are wrong. The MyApp shouldn’t be selected. We want the unit test target ‘Logic Tests’ to be checked.

If you accept these, when you go to run your tests you’ll get an error like:

<SenTestingKit/SenTestingKit.h> file not found.

This occured because the wizard default added your new test class to your main project and not your test target.

You can manually fix this by going into the build phase of your project and adding your test file to your test target and removing it from your main application target under the ‘Compile Sources’ tab.

Or you can just create the class from scratch and select your test project and not the application one.

Hope this helps. Happy testing!

How to setup OCMock XCode 4 iOS 5

4 Comments

This was harder than I would have liked. Here’s some notes on how to set it up.

Setup OCUnit on your existing project

These notes from Apple are pretty good about how to setup OCUnit on your XCode project.

The only difference was when I ran my unit tests (Product -> Test) I had to manually add my own unit test scheme.

Just hit the ‘Edit Scheme’ button, then hit the ‘+’ sign and click on your target unit tests and click the ‘Add button’.

You should now be able to run your tests against the simulator (not real phone) and see the failure.

Setup OCMock

Create a Libraries directory

Create a ‘Libraries’ directory in your project by right clicking on your project icon and selecting ‘Add Files to …’.

Click the ‘New folder’ button, type the name ‘Libraries’, and click ‘Add’ so your libraries directory is at the same level as your project and unit test target. When your done it should look something like this:

Note: Make sure you select your ‘Test’ target (grey) not the main ‘Project’ target blue. Else you will make all these lovely changes to the wrong config and nothing will work.

Copy in libOCMock.a

Download the libOCMock.a and stick it in the Libraries directory we just created.

Then add it to your project by dragging it into the Libraries directory we just added to your project.

When you do a window will pop up, make sure you select the ‘Copy items’ box at the top and click on your unit test target so it will be linked to OCMock.

Copy OCMock header files

Download the latest OCMock dmg file, located the OCMock directory inside containing the header files, and just like we did before, drag it into the Libraries directory.

Again select ‘Copy’ and click your unit test target and now your directory structure should look like this:

Configure Build Settings

We now need to tell our project how to find these header files we just added.

Click your blue project icon (click somewhere else and then click it again if it doesn’t immediately change) and click the ‘Build Settings’ tab for your project.

In the ‘Search Paths -> Library Search Paths’ section add $(SRCROOT)/Libraries in double quotes to Debug and Release sections. Once you do they will automatically convert to your local machine paths as shown below.

Note: As of XCode 4.3 the Library Search Paths may already be configured for you. The headers ones through aren’t.

And then do the exact same thing for the ‘Search Paths ->Header Search Path’ section (be sure to double click on it after and make sure both are set to ‘recursive’).

Note: As of XCode 4.3 you won’t see the word recursive here – just the box. Click it for both. If you don’t when you run your test you won’t be able to find the OCMock header files.

Next head down to the ‘Linking -> Other Link Flags’ section and add the ‘-all_load’ option.

If you don’t do this you’ll get a “Did you forget to add the -force_load linker flag?” error when you run your tests and you can read about why here.

Add a mock test case

We should now be good to go. Open you the default test case OCUnit created for you and add the following:

#import "UnitTests.h"
#import <OCMock/OCMock.h>

@implementation UnitTests

- (void)setUp
{
    [super setUp];
    
    // Set-up code here.
}

- (void)tearDown
{
    // Tear-down code here.
    
    [super tearDown];
}

- (void)testExample
{
    //STFail(@"Unit tests are not implemented yet in UnitTests");
}

- (void)testOCMockPass {
    id mock = [OCMockObject mockForClass:NSString.class];
    [[[mock stub] andReturn:@"mocktest"] lowercaseString];
    
    NSString *returnValue = [mock lowercaseString];
    STAssertEqualObjects(@"mocktest", returnValue, @"Should have returned the expected string.");
}

@end

If everything worked, you should be able to run your test now and see a nice failure in your log output window:

Links that help:

https://developer.apple.com/library/ios/#documentation/DeveloperTools/Conceptual/UnitTesting/02-Setting_Up_Unit_Tests_in_a_Project/setting_up.html#//apple_ref/doc/uid/TP40002143-CH3-SW1

http://www.raywenderlich.com/3716/unit-testing-in-xcode-4-quick-start-guide

https://developer.apple.com/library/mac/#qa/qa1490/_index.html

http://ocmock.org/

How to configure UITableView for multirow selection

Leave a comment

This is a little harder than it sounds.

UITableView reuses cells – so you can’t just detect with row was selected and then expect that cell to be selected later. That cell will be recycled and your index will be lost.

So what we do is create a separate data structure (say a mutable array) to track which rows were selected, and then turn rows on and off by toggling a check mark.

The data structure

In my app I need to track which photos are selected:

So I am going to create an NSMutableArray to do that:

ViewController.h

@property (nonatomic, strong) NSMutableArray *selectedPhotoTracker;

ViewController.m

@synthesize selectedPhotoTracker = _selectedPhotoTracker;

and I am going to initialize him once my photos have loaded:

ViewController.m

-(void) initializeSelectedPhotoTracker {
    NSLog(@"initializeSelectedPhotoTracker");
    NSMutableArray *tracker = [NSMutableArray array];
    for (int i = 0; i < [[self photos] count] ; i++) {
        [tracker addObject:[NSNumber numberWithBool:NO]];
    }
    
    self.selectedPhotoTracker = tracker;    
}


-(void) refresh {
    
    NSLog(@"refresh");
    
    NSMutableArray *collector = [[NSMutableArray alloc] initWithCapacity:0];
    ALAssetsLibrary *al = [Utils defaultAssetsLibrary];
    
    [al enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos
                      usingBlock:^(ALAssetsGroup *group, BOOL *stop) 
     {
         
         [group enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop)
          {
              if (asset) {
                  
                  // check for tag
                  NSDictionary *metadata = asset.defaultRepresentation.metadata;
                  NSDictionary *tiffDictionary = [metadata objectForKey:(NSString *)kCGImagePropertyTIFFDictionary];
                  NSString *takeMyStuffValue = [tiffDictionary objectForKey:(NSString *)kCGImagePropertyTIFFMake];

                  if ([takeMyStuffValue isEqualToString:TAKEMYSTUFF_METATAG]) {
                      [collector addObject:asset];
                  }
                  
              }  
          }];
         
         self.photos = collector;
         [self initializeSelectedPhotoTracker];
     }
                    failureBlock:^(NSError *error) { NSLog(@"Error refreshing photos.");}
     ];
}

Note: this looks more complicated that it really is but I included it to make that point that when you initialize this array matters.

First time I did this I tried putting :

         [self initializeSelectedPhotoTracker];

in the viewWillAppear view lifecycle method. It didn’t work because the photos (which are loaded in a separate thread in another block) hadn’t finished yet.

So if you are loading your data structure and nothing is there look out for that. Where you do your load and initialization matters.

Multi-row selection

This part is pretty straight forward. Apple doesn’t actually like you using blue highlighted row as an indicator of whether a row is selected (human interface guidelines).

They prefer you use check marks.

So with our datastructure in place we can just turn checkmarks on and off like this:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    int selectedRow = indexPath.row;

    NSDictionary *selectedPhoto = [[self selectedPhotoTracker] objectAtIndex:selectedRow];
    NSString *urlKey = [[selectedPhoto allKeys] objectAtIndex:0];
    
    // add checkmark
    UITableViewCell *thisCell = [tableView cellForRowAtIndexPath:indexPath];
    
    if (thisCell.accessoryType == UITableViewCellAccessoryNone) {
        thisCell.accessoryType = UITableViewCellAccessoryCheckmark;
        [selectedPhoto setValue:[NSNumber numberWithBool:YES] forKey:urlKey];        
    } 
    else 
    {
        thisCell.accessoryType = UITableViewCellAccessoryNone;
        [selectedPhoto setValue:[NSNumber numberWithBool:NO] forKey:urlKey];        
    }

    [[self selectedPhotoTracker] replaceObjectAtIndex:selectedRow withObject:selectedPhoto];
    [[self getSelectedImages] writeToDocumentDirectory:SELECTED_PHOTOS_FILE];
}

This method tracks that a row was selected and updates our model, and then it toggles the checkmark on or off respectively.

Links that help:
http://stackoverflow.com/questions/308081/is-it-possible-to-configure-a-uitableview-to-allow-multiple-selection

http://stackoverflow.com/questions/5370311/uitableview-didselectrowatindexpath-add-additional-checkmark-at-tap

How to programmatically transition between views in Tab Bar Controller

Leave a comment

Say you’ve got a uitabbarcontroller and you want to programatically switch from one to the other. Here’s how you do it:

- (IBAction)test:(id)sender {
    // switch back
    
    int controllerIndex = 1;
    
    UITabBarController *tabBarController = self.tabBarController;
    UIView * fromView = tabBarController.selectedViewController.view;
    UIView * toView = [[tabBarController.viewControllers objectAtIndex:controllerIndex] view];
    
    // Transition using a page curl.
    [UIView transitionFromView:fromView 
                        toView:toView 
                      duration:0.5 
                       options:(controllerIndex > tabBarController.selectedIndex ? UIViewAnimationOptionTransitionCurlUp : UIViewAnimationOptionTransitionCurlDown)
                    completion:^(BOOL finished) {
                        if (finished) {
                            tabBarController.selectedIndex = controllerIndex;
                        }
                    }];
}

The controller index is the view you want to transition to (0 based). So you just pick your index, get your from and to view, and then use the transitionFromView method to switch.

Note: You can’t do this from within a viewWillAppear or other lifecyle event. You may get away with it, but it only works once your view is fully loaded.

Helper link:
http://stackoverflow.com/questions/5161730/iphone-how-to-switch-tabs-with-an-animation

How to attach an image from your photo gallery and send it as an image on iPhone

4 Comments

First the easy part. Sending an email to someone in iOS is pretty easy.

You import

#import <MessageUI/MessageUI.h>
#import <MessageUI/MFMailComposeViewController.h>

You implement this delegate:

-(void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error
{
    [self dismissModalViewControllerAnimated:YES];    
}

And then you actually send the mail doing something like this:

        // send email
        if ([MFMailComposeViewController canSendMail]) {
            
            MFMailComposeViewController *controller = [[MFMailComposeViewController alloc] init];
            controller.mailComposeDelegate = self;
            [controller setToRecipients:[NSArray arrayWithObject:@"rasmus4200@gmail.com"]];
            [controller setSubject:@"Subject Goes Here."];
            [controller setMessageBody:@"Your message goes here." isHTML:NO];
            
            // Attach an image to the email
            NSData *myData = UIImagePNGRepresentation(coolImage);
            [controller addAttachmentData:myData mimeType:@"image/png" fileName:@"coolImage.png"];
            
            [self presentModalViewController:controller animated:YES];
        }
        else {
            NSLog(@"Device is unable to send email in its current state.");
        }

That’s the easy bit. It’s that middle bit that’s hard. How do you get your hands on the image and then set it as an attachment?

Turns out the way you get your to your photo gallery images is through the ALAssetsLibrary. It’s not enough to just know the URL of your asset on your phone

assets-library://asset/asset.JPG?id=1000000003&ext=JPG

and UIImage from this. You need to do it through the library which looks something like this:

    NSURL *asseturl = [NSURL URLWithString:@"assets-library://asset/asset.PNG?id=656FF2D1-E973-4EDD-9A44-0E8304DBFACC&ext=PNG"];
    
    // create library and set callbacks
    ALAssetsLibrary *al = [EmailViewController defaultAssetsLibrary];
    [al assetForURL:asseturl 
        resultBlock:resultblock
       failureBlock:failureblock];

This setups up the URL of the image you want to get to, as well as the callbacks (blocks) for when the library is ready. They look like this:

    ALAssetsLibraryAssetForURLResultBlock resultblock = ^(ALAsset *myasset)
    {
        UIImage *coolImage;
        
        // get the image
        ALAssetRepresentation *rep = [myasset defaultRepresentation];
        CGImageRef iref = [rep fullResolutionImage];
        if (iref) {
            coolImage = [UIImage imageWithCGImage:iref];
        }
        
        // send email
        if ([MFMailComposeViewController canSendMail]) {
            
            MFMailComposeViewController *controller = [[MFMailComposeViewController alloc] init];
            controller.mailComposeDelegate = self;
            [controller setToRecipients:[NSArray arrayWithObject:@"rasmus4200@gmail.com"]];
            [controller setSubject:@"Subject Goes Here."];
            [controller setMessageBody:@"Your message goes here." isHTML:NO];
            
            // Attach an image to the email
            NSData *myData = UIImagePNGRepresentation(coolImage);
            [controller addAttachmentData:myData mimeType:@"image/png" fileName:@"coolImage.png"];
            
            [self presentModalViewController:controller animated:YES];
        }
        else {
            NSLog(@"Device is unable to send email in its current state.");
        }
    };
    
    
    ALAssetsLibraryAccessFailureBlock failureblock  = ^(NSError *myerror)
    {
        NSLog(@"booya, cant get image - %@",[myerror localizedDescription]);
    };

The magic for getting the image happens here:

        UIImage *coolImage;
        
        // get the image
        ALAssetRepresentation *rep = [myasset defaultRepresentation];
        CGImageRef iref = [rep fullResolutionImage];
        if (iref) {
            coolImage = [UIImage imageWithCGImage:iref];
        }

We get the representation of our asset, get it’s original image (fullResolution) and then create a UIImage from that.

With that, we are then free to send in an email. If tested this on gmail and yahoo. Both work. Images come through.

Hears the code in it’s entirety.

EmailViewController.h

#import <UIKit/UIKit.h>
#import <MessageUI/MessageUI.h>
#import <MessageUI/MFMailComposeViewController.h>
#import <AssetsLibrary/AssetsLibrary.h>

@interface EmailViewController : UIViewController <MFMailComposeViewControllerDelegate>
typedef void (^ALAssetsLibraryAssetForURLResultBlock)(ALAsset *asset);
typedef void (^ALAssetsLibraryAccessFailureBlock)(NSError *error);
@end

EmailViewController.m

#import "EmailViewController.h"

@implementation EmailViewController

+ (ALAssetsLibrary *)defaultAssetsLibrary {
    static dispatch_once_t pred = 0;
    static ALAssetsLibrary *library = nil;
    dispatch_once(&pred, ^{
        library = [[ALAssetsLibrary alloc] init];
    });
    return library; 
}




- (IBAction)sendEmail2:(id)sender {

    ALAssetsLibraryAssetForURLResultBlock resultblock = ^(ALAsset *myasset)
    {
        UIImage *coolImage;
        
        // get the image
        ALAssetRepresentation *rep = [myasset defaultRepresentation];
        CGImageRef iref = [rep fullResolutionImage];
        if (iref) {
            coolImage = [UIImage imageWithCGImage:iref];
        }
        
        // send email
        if ([MFMailComposeViewController canSendMail]) {
            
            MFMailComposeViewController *controller = [[MFMailComposeViewController alloc] init];
            controller.mailComposeDelegate = self;
            [controller setToRecipients:[NSArray arrayWithObject:@"rasmus4200@gmail.com"]];
            [controller setSubject:@"Subject Goes Here."];
            [controller setMessageBody:@"Your message goes here." isHTML:NO];
            
            // Attach an image to the email
            NSData *myData = UIImagePNGRepresentation(coolImage);
            [controller addAttachmentData:myData mimeType:@"image/png" fileName:@"coolImage.png"];
            
            [self presentModalViewController:controller animated:YES];
        }
        else {
            NSLog(@"Device is unable to send email in its current state.");
        }
    };
    
    
    ALAssetsLibraryAccessFailureBlock failureblock  = ^(NSError *myerror)
    {
        NSLog(@"booya, cant get image - %@",[myerror localizedDescription]);
    };
    
    
    NSURL *asseturl = [NSURL URLWithString:@"assets-library://asset/asset.PNG?id=656FF2D1-E973-4EDD-9A44-0E8304DBFACC&ext=PNG"];
    
    // create library and set callbacks
    ALAssetsLibrary *al = [EmailViewController defaultAssetsLibrary];
    [al assetForURL:asseturl 
        resultBlock:resultblock
       failureBlock:failureblock];
}


- (IBAction)sendEmail:(id)sender {
    
    if ([MFMailComposeViewController canSendMail]) {
        
        MFMailComposeViewController *controller = [[MFMailComposeViewController alloc] init];
        controller.mailComposeDelegate = self;
        [controller setToRecipients:[NSArray arrayWithObject:@"rasmus4200@gmail.com"]];
        [controller setSubject:@"Subject Goes Here."];
        [controller setMessageBody:@"Your message goes here." isHTML:NO];
                
        // Attach an image to the email
        NSData *coolData = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://edibleapple.com/wp-content/uploads/2009/04/silver-apple-logo.png"]];
        UIImage *coolImage = [UIImage imageWithData:coolData];
        
        //        UIImage *coolImage = [UIImage imageWithContentsOfFile:@"assets-library://asset/asset.PNG?id=656FF2D1-E973-4EDD-9A44-0E8304DBFACC&ext=PNG"];
        NSData *myData = UIImagePNGRepresentation(coolImage);
        [controller addAttachmentData:myData mimeType:@"image/png" fileName:@"coolImage.png"];

        [self presentModalViewController:controller animated:YES];
        
        
    }
    
    else {
        
        NSLog(@"Device is unable to send email in its current state.");
        
    }
    
}

-(void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error
{
    [self dismissModalViewControllerAnimated:YES];    
}
    
@end

Helpful links:

http://stackoverflow.com/questions/3837115/display-image-from-url-retrieved-from-alasset-in-iphone

Older Entries

%d bloggers like this: