Tropical Software Observations

Showing posts with label iphone. Show all posts
Showing posts with label iphone. Show all posts
06 September 2012

Posted by Anonymous

at 9:07 AM

0 comments

Labels: , ,

Breaking ARC Retain Cycle in Objective-C Blocks

In a recent client project, we noticed its iOS app often received low memory warnings. The iOS app is developed with ARC-enabled (Automatic Reference Counting).
When profiling the app using Instruments > Allocations, we found that a lot of unused ViewController objects were not being released from memory.

Retain Cycle

The codebase uses a lot of Objective-C blocks as shown below, and self is often being called within the blocks:




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@interface DetailPageViewController : UIViewController
@property(nonatomic, strong) UIButton *backButton;
...
@end

@implementation DetailPageViewController
@synthesize backButton;

- (void)loadView {
  ...
  [self.doneButton onTouch:^(id sender) {
    [self doSomething];
    self.isDone = YES;
  }];
}
...
@end

In this example code, the controller object holds a strong reference to a doneButton object. But because the doneButton onTouch: block is referencing self, now the button object holds a strong reference back to the controller object.
When object A points strongly to object B, and B points strongly to A, a retain cycle is created, and both objects cannot be released from memory.

Use Lifetime Qualifiers

Apple Developer’s guide on ARC transition suggested a few ways to break the retain cycle using different lifetime qualifiers. It is definitely a must read for iOS development.
If your app is targeted for iOS 5, you can use the __weak lifetime qualifier to break the cycle:




1
2
3
4
5
6
7
8
9
10
@interface DetailPageViewController : UIViewController
- (void)loadView {
  ...
  __weak DetailPageViewController *controller = self;
  [self.doneButton onTouch:^(id sender) {
    [controller doSomething];
    controller.isDone = YES;
  }];
}
@end

Because we are using the __weak qualifier here, the doneButton onTouch: block only has a weak reference to the controller object. Now, the controller object will be released from memory when its reference count drops to 0.

07 February 2011

Posted by Irregular Zero

at 1:12 PM

0 comments

Labels:

A Certain Minimal QR Scanner iPhone app

A QR Code is similar to a barcode, except it contains more information and looks like a pixellated square Rorschach test.

There are a number of free QR readers in the appstore like NeoReader and RedLaser. I especially like the scanning GUI for RedLaser, which seems more streamlined than any of the apps I have tried.

A number of these apps use the open-source ZXing ("Zebra Crossing") scanner library. The library is in Java but has a number of ports which include iPhone.

To start things off, you need to download or checkout the source. Create a new "View-Based Application" project in Xcode. Inside the iphone folder of the source, follow the README on how to include the ZXingWidget project into yours.

Pay attention to the instructions, especially the direct dependency, header search path and the fact the file with the ZXing has to be a .mm instead of .m. If it does not build, you're probably missing something. You can look at the sample projects to see how they include the widget.

One last thing before moving on to the code, put the beep-beep.aiff file from the ScanTest project into your project. This is to get audio confirmation of a scan.

Inside the sole viewController of your project:

#import "ZXingWidgetController.h"
#import "QRCodeReader.h"
#import "ResultParser.h"
#import "URLResultParser.h"
#import "ResultAction.h"

- (void)viewDidLoad {
[super viewDidLoad];
[ResultParser registerResultParserClass:[URLResultParser class]]; 
}

- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
ZXingWidgetController *widController =
[[ZXingWidgetController alloc] initWithDelegate:self showCancel:NO OneDMode:NO];
QRCodeReader *qrcodeReader = [[QRCodeReader alloc] init];
NSSet *readers = [[NSSet alloc] initWithObjects:qrcodeReader,nil];
[qrcodeReader release];
widController.readers = readers;
[readers release];
NSBundle *mainBundle = [NSBundle mainBundle];
widController.soundToPlay =
[NSURL fileURLWithPath:[mainBundle pathForResource:@"beep-beep" ofType:@"aiff"] isDirectory:NO];
[self presentModalViewController:widController animated:YES];
[widController release];
}

#pragma mark -
#pragma mark ZXingDelegateMethods
- (void)zxingController:(ZXingWidgetController*)controller didScanResult:(NSString *)resultString {
[self dismissModalViewControllerAnimated:YES];
ParsedResult *parsedResult = [[ResultParser parsedResultForString:resultString] retain];
NSArray *actions = [[parsedResult actions] retain];

if ([actions count] == 1) {
ResultAction *theAction = [actions objectAtIndex:0];
[theAction performActionWithController:self shouldConfirm:YES]; 
} else {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Text Found:"
message:resultString
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alertView show];
[alertView release];
}
}

The code in viewWillAppear is lifted off from the sample projects. This sets up the scanning video camera with the appropriate reader. If a scan is successful, the delegate method didScanResult will execute. The result is parsed to see if it is a URL. You set which parser to use in viewDidLoad. A parsed result can have default actions associated with it, the URLResultParser opens up the url in Safari as default. Otherwise the result is treated as text and displayed.

This app can now scan QR codes and open up URLs in Safari. There are a number of other things you can add to this, eg you can switch out the ResultParser with a UniversalResultParser that includes all the parser classes. You should take a look in the Classes folder of the ZXingWidget project to see what is available.

17 March 2009

Posted by Irregular Zero

at 6:38 PM

1 comments

Labels:

Delegation for iphone-google-maps-component

The iphone-google-maps-component is a way for you to use Google Maps on the iPhone through the use of an extended UIWebView and an html file on a server somewhere that does the map loading.

The present code overrides the default delegate UIWebViewDelegate with its own, MapWebViewDelegate. You cannot use the setCenterWithLatLng to establish the starting location until the view has finished loading. With the override, the webViewDidFinishLoad delegate method is unavailable.

Going by this post, the following code changes will allow the use of both delegates.

MapWebView.h
1) Change protocol definition.
@protocol MapWebViewDelegate < UIWebViewDelegate >

MapWebView.m
2) Remove the synthesize line.
//@synthesize delegate;

3) Replace with own methods, to hook into super.delegate.

- (id ) delegate {
return (id )super.delegate;
}

- (void) setDelegate :(id ) delegate {
super.delegate = delegate;
}


webViewDidFinishLoad should be accessible, as well as the other methods in the UIWebViewDelegate.

Another way of setting the starting location is through the loadMap method in MapWebView.h. The URL string can accept two more variables, latitude and longitude.

Change this:
NSString *urlStr = [NSString stringWithFormat:
@"http://www.wenear.com/iphone-test?width=%d&height=%d&zoom=%d",
width, height, DEFAULT_ZOOM_LEVEL];


To this:

NSString *urlStr = [NSString stringWithFormat:
@"http://www.wenear.com/iphone-test?width=%d&height=%d&latitude=%f&longitude=%f&zoom=%d",
width, height, location.coordinate.latitude, location.coordinate.longitude, DEFAULT_ZOOM_LEVEL];


Here, location is a CLLocation, part of the CoreLocation framework. The loadMap method is publicly defined in the interface and it accepts location as its variable. The call in didMoveToSuperview should be properly handled.

The latitude and longitude are doubles and can be hardcoded instead.