Posts tagged with objective-c

Debugging retain cycles in Objective-C: four likely culprits

Your iPhone app seems to be working fine. But suddenly, it starts to run slow and crashes! You suspect it's memory-related. Here's how to track down and fix issues with retain cycles, which can cause memory not to be released properly.

I'm assuming you are using ARC and iOS5.0+. While ARC simplifies a lot of memory management, it won't spot all retain cycles for you!

First, check you're genuinely dealing with a memory condition. Try to reproduce the crash on a device, and look for crash reports using Xcode > Organiser > Device Logs.

Low memory crashes don't look like normal crash reports, you won't see a stack trace! Instead you'll probably just see Process = Unknown, and the crash report will contain a list of processes which were running at the time of the crash.

Screen Shot 2013-04-16 at 14.01.59

In this example, you can see that Biblegram was using over 25000 pages of memory. 1 page of memory is 4KB, so that's 100MB of memory, which seems much too high.

Next, fire up Instruments via Product > Profile and select the "Allocations" template. Playing around with the app, you should easily be able to locate places where the memory allocation keeps going up and up.

Screen Shot 2013-04-16 at 14.12.13

This "staircase" pattern is a giveaway. I'm repeatedly pushing and popping one view controller, but the memory goes up and up.

Try putting a breakpoint in the dealloc method of the problematic view controller. Most likely, this will never get hit, showing that the view controller is never released.

Screen Shot 2013-04-16 at 14.30.22

This is a strong indication that we have a retain cycle. When the view controller is dismissed there are still some strong references to it, so it doesn't get dealloced.

As one last verification, enter the name of the class into the search box in the top right of Instruments, and check the "# living" column. This is showing 3, when we'd expect it to show 1.

Screen Shot 2013-04-16 at 14.15.38

You can drill down into the instances of the view controller by tapping the small arrow to the right of the class name, and drill down further to see every place that the view controller is retained and released.

This view can be rather overwhelming: the system frameworks do a lot of retaining and releasing on your behalf! For example, instantiating a NIB can easily increase the retain count to 40 temporarily.

So: here are four common errors to look out for that may cause your retain count to be higher than expected.

1. NSTimer

If you create an NSTimer object on a view controller, be sure that invalidate is called on it when dismissing the view controller, otherwise it will retain self.

2. Observers/NSNotificationCenter

If you add an observer to NSNotificationCenter, make sure you remove all observers when dismissing the view controller, otherwise they will retain self.

3. Blocks

You should not call [self doSomething] from inside a block, this can easily lead to the block capturing a reference to self. Instead, make a weak reference to self:

BAD:

  1.  
  2. dispatch_async(queue, ^{
  3. [self doSomething];
  4. });
  5.  

GOOD

  1.  
  2. __weak MyViewController *safeSelf = self;
  3. dispatch_async(queue, ^{
  4. [safeSelf doSomething];
  5. });
  6.  

4. Delegates

if you use

  1.  
  2. someObj.delegate = self;
  3.  

inside the view controller, check the delegate property on someObj is weak.

  1.  
  2. @property (nonatomic, weak) id delegate;
  3.  

Once you've made your fixes, check that dealloc is getting hit and the allocations no longer increase endlessly.

Sharing images to Weixin/WeChat from an iOS app

Weixin (微信) is Tencent's mobile messaging product. It's highly popular in China, with hundreds of millions of users, as well as many international users who may know it by its English name WeChat. It includes both a chat feature, similar to WhatsApp, and a timeline feature similar to Path.

Tencent have a developer site in English, but the documentation is still quite sparse.

For a recent project, we needed to integrate sharing of images to Weixin.

The workflow for the user will be:

1. Create an image in the app
2. Tap a button to share to Weixin
3. The Weixin app will launch, and the user confirms they want to add the image to their "Moments".
4. The user taps a button in the Weixin app to return to our app.

STEP 1: Download and add the iOS SDK

1. Download the latest iOS SDK from the WeChat developer site. After unzipping you should have four files:

2. If you're building with the latest Xcode and targeting iPhone 5, you'll probably want to use the version of the library which supports armv7s, so copy WXApi.hWXApiObject.h and libWeChatSDK_armv7_armv7s.a to your project folder. Rename libWeChatSDK_armv7_armv7s.a to libWeChatSDK.a.

3. Now in your XCode project go to File > Add Files and add the three files to your project.

STEP 2: Register for an app id

1. Register as a WeChat developer using the signup form. Once you've confirmed your email address, head to the My Apps page and tap "Register my apps"

2. Fill out the name of your app and other details. You can skip some optional fields like icon for now. Make sure you choose "Mobile app" as the app type, not Web app.

3. At the end of the process you will be given an app id which looks like wx123456789012

STEP 3: Integrate with the SDK

1. In your application:didFinishLaunchingWithOptions: method, add code to register your app. Be sure to replace wx123456789012 with the code you obtained earlier.

  1. if (![WXApi registerApp:@"wx123456789012"]) {
  2. NSLog(@"Failed to register with Weixin");
  3. }

2. After the Weixin app is launched it needs a way to re-launch your app. It does this by trying to open a URL with your app id as the protocol, for example wx123456789012://. We need to ensure we can handle these URLs, so implement these two methods:

  1. - (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
  2. return [WXApi handleOpenURL:url delegate:self];
  3. }
  4. - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
  5. return [WXApi handleOpenURL:url delegate:self];
  6. }

3. We need to ensure that our app delegate implements WXApiDelegate, so first add WXApiDelegate to your AppDelegate.h file:

  1. @interface AppDelegate : UIResponder<WXApiDelegate>

and then add empty implementations of these two callbacks in the .m:

  1. - (void) onReq:(BaseReq*)req {
  2. }
  3. - (void) onResp:(BaseResp*)resp {
  4. }

4. We also need to make a change in the Info.plist to ensure we can handle these kind of URLs. Go to your target, select the Info tab and add a new URL type as follows. The identifier can be "weixin" and the URL scheme should match your app id.

5. Now let's add a method to send an image.

  1. - (void) sendImageContentToWeixin:(UIImage *)image {
  2. //if the Weixin app is not installed, show an error
  3. if (![WXApi isWXAppInstalled]) {
  4. UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:@"The Weixin app is not installed" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil];
  5. [alert show];
  6. return;
  7. }
  8. //create a message object
  9. WXMediaMessage *message = [WXMediaMessage message];
  10. //set the thumbnail image. This MUST be less than 32kb, or sendReq may return NO.
  11. //we'll just use the full image resized to 100x100 pixels for now
  12. [message setThumbImage:[image resizedImage:CGSizeMake(100,100) interpolationQuality:kCGInterpolationDefault]];
  13. //create an image object and set the image data as a JPG representation of our UIImage
  14. WXImageObject *ext = [WXImageObject object];
  15. ext.imageData = UIImageJPEGRepresentation(image, 0.8);
  16. message.mediaObject = ext;
  17. //create a request
  18. SendMessageToWXReq* req = [[SendMessageToWXReq alloc] init];
  19. //this is a multimedia message, not a text message
  20. req.bText = NO;
  21. //set the message
  22. req.message = message;
  23. //set the "scene", WXSceneTimeline is for "moments". WXSceneSession allows the user to send a message to friends
  24. req.scene = WXSceneTimeline;
  25. //try to send the request
  26. if (![WXApi sendReq:req]) {
  27. UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:@"Error" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil];
  28. [alert show];
  29. }
  30. }

6. Add callback code. This will be called once control is returned to our app after returning from Weixin.

  1. - (void) onResp:(BaseResp*)resp {
  2. if([resp isKindOfClass:[SendMessageToWXResp class]]) {
  3. NSString *strMsg = [NSString stringWithFormat:@"Result:%d", resp.errCode];
  4. NSLog(@"Response from Weixin was: %@",strMsg);
  5. }
  6. }

STEP 4: Test!

All being well, you should now be able to share an image from your code, using something like:

  1.  
  2. UIImage *test = [UIImage imageNamed:@"test.png"];
  3. [self sendImageContentToWeixin:test];
  4.  

After sharing the image, you should be returned to your app.

If you found this tutorial useful, or have any suggestions, do leave a comment!

Constants.h considered harmful

It's common to use a centralised "Constants" class where you gather together lots of constants for your app. For example, in Objective-C you'll often have a big file Constants.h full of lines like this.

  1.  
  2. #define DEBUG_MODE NO
  3. #define APP_URL @"http://itunes.com/apps/mycoolapp"
  4. #define CHARACTER_ANIMATION_TIME 2.0f
  5. #define FOOTER_IMAGE @"mycoolapp-footer.png"
  6. #define FONT @"Helvetica-Bold"
  7.  

Don't do this.

Why?

1. It increases compile times significantly. Because lots of other classes depend on Constants.h (it's common to include it in the .pch header file so all files have access to it), any small change to Constants.h means that every class in your application will need to be recompiled. And ironically, during development at least, constants are often variable :)

2. It makes unit testing more complex, adds additional dependencies and discourages re-use. Because many classes depend on Constants.h, its harder to test them in isolation. Moreover, you can't easily transfer classes between projects, without manually checking which parts of Constants.h needs to be copied too.

Instead, it's much better to place constants in the class which they are used in. If you have a true need for a global constant, but it is for a value which you might want to frequently change during development (such as the DEBUG_MODE boolean above), consider using a macro to load the value from a config file, for example you could define a key in the Info.plist:

  1.  
  2. #define DEBUG_MODE ([[[[NSBundle mainBundle] infoDictionary] objectForKey:@"APP_MODE"] isEqualToString:@"DEBUG"])
  3.  

iOS5 Notification Center disables Local Notifications by default on your existing apps

If your iOS app uses UILocalNotifications, you (and your users) may be wondering why you haven't been receiving them after updating iOS 5. It seems that due to a bug (or is it undocumented feature?) in iOS 5, Local Notifications now behave a lot more like Push Notifications.

By default, if your app does not register for push notifications, it will not be in the Notification Center and thus unable to send notifications (remote or local). Of course your users can always manually go into Settings -> Notifications and hunt down your app, but not all users will realize this and - as in our case - many may think your app is 'broken' in iOS 5.

Until Apple (hopefully) releases an update to fix this, a workaround is to register your app to receive Remote/Push Notifications (even if you don't use them) so that your app is "in" the Notification Center and able to receive Local Notifications. To do this, in your App Delegate's application didFinishLaunchingWithOptions method, add the following:

  1.  
  2. // Workaround for iOS5 bug. We need to register for remote notifications,
  3. // otherwise all notifications are disabled by default in the Notification Center
  4.  
  5. [[UIApplication sharedApplication]
  6. registerForRemoteNotificationTypes:
  7. UIRemoteNotificationTypeBadge |
  8. UIRemoteNotificationTypeAlert |
  9. UIRemoteNotificationTypeSound];
  10.  

At startup, your app will now prompt the user to receive push notifications and when they accept, join the ranks of other Push Notification apps in the Notification Center.