Wednesday, November 4, 2009
to infinity, and beyond!
I am pleased to report that my pet-project iPhone app, iTravelFree, has passed the stern inspection of Apple's App Store and is now available for download worldwide. For app links, a screenshot-laden tutorial, and help and FAQ files, see here: www.wetravelright.com.
(Yeah, crappy URL, I know, but all the good ones were taken.)
Since this is my tech blog let me wax about its architecture a bit. The iPhone app is pretty straightforward: basically, it's a bunch of TableViewControllers, many of which include WebViews, along with a MapViewController, all pointing to a bunch of CoreData records. Nothing extraordinarily fancy by any means.
The server side is more interesting: it's a Google App Engine service, written in Python, that fetches, caches, and parses Wikitravel pages for the app. This gives me a single point of access to the data flow, lets me do things like convert addresses to lat/long location, cuts down on bandwidth for both Wikitravel (thanks to the caching) and the phone app (thanks to the parsing and stripping out of extraneous info.)
The general architecture - phone app plus App Engine service - is actually really powerful and easy to work with. Basically, it's a distributed version of the classic Model-View-Controller architecture, where the phone is the view, the App Engine service is the controller, and whatever data you're accessing is the model. This lets you do all the heavy-lifting computation on the server side, which is where it belongs, and keep the phone (and its puny processor) focused almost purely on the UI.
I do have some reservations about the BigTable data store that App Engine uses, but they don't apply to projects like this, with relatively simple storage requirements and no data mining.
I wrote it in, hrmm, about six weeks all told, starting in July. (Obviously it's been much more than six weeks since then, but I had full-time work starting August so could only work on this in fits and spurts on the side.)
Anyway - the app is in pretty good shape, but there's more work to be done on the server side, so it's still basically in beta test. Take a look, download it, play around, and let me know what you think -
(Yeah, crappy URL, I know, but all the good ones were taken.)
Since this is my tech blog let me wax about its architecture a bit. The iPhone app is pretty straightforward: basically, it's a bunch of TableViewControllers, many of which include WebViews, along with a MapViewController, all pointing to a bunch of CoreData records. Nothing extraordinarily fancy by any means.
The server side is more interesting: it's a Google App Engine service, written in Python, that fetches, caches, and parses Wikitravel pages for the app. This gives me a single point of access to the data flow, lets me do things like convert addresses to lat/long location, cuts down on bandwidth for both Wikitravel (thanks to the caching) and the phone app (thanks to the parsing and stripping out of extraneous info.)
The general architecture - phone app plus App Engine service - is actually really powerful and easy to work with. Basically, it's a distributed version of the classic Model-View-Controller architecture, where the phone is the view, the App Engine service is the controller, and whatever data you're accessing is the model. This lets you do all the heavy-lifting computation on the server side, which is where it belongs, and keep the phone (and its puny processor) focused almost purely on the UI.
I do have some reservations about the BigTable data store that App Engine uses, but they don't apply to projects like this, with relatively simple storage requirements and no data mining.
I wrote it in, hrmm, about six weeks all told, starting in July. (Obviously it's been much more than six weeks since then, but I had full-time work starting August so could only work on this in fits and spurts on the side.)
Anyway - the app is in pretty good shape, but there's more work to be done on the server side, so it's still basically in beta test. Take a look, download it, play around, and let me know what you think -
Labels: AppEngine, Apple, AppStore, BigTable, iPhone, iTravel, iTravelFree, python, Wikitravel
Monday, August 3, 2009
if you love some source code, set it free
So I have gone and
To be honest I would have liked another few days for cleanup and further testing, but I've gone and gotten a real job (iPhone app development, again) that will occupy me for at least a couple of months, so I had to hurry things up. I apologize for the ugliness of the code.
Some of that ugliness is due to its extremely crude testing framework. Unit testing, the panacea of modern software development, is bizarrely difficult to integrate with the iPhone SDK. There are various tools to help you - one created by Google - but they're all far more complex and kludgy than I'd like, especially compared to the elegant joy that is GAEUnit.
It turns out, however, that it is possible to seamlessly integrate unit tests and test-driven development into iPhone development with XCode. There are a few caveats, but overall, these instructions work very well, if you follow them very carefully.
The caveat is that you can only run the unit tests on an actual physical device, not in the simulator. Now, in some ways this is actually a bonus - you avoid the "but it works in the simulator!" pitfall, and I have learned that the only way to get a feel for what your UI should be is to work on a real iPhone or iPod Touch, the simulator is disastrously misleading - but it does mean that you'll have to go ahead and shell out the $100 to join the iPhone Developer Program so that you can generate the certificates needed to sign your app and install it on your device.
Which in turn is a bit of a hassle. But it's a necessary hassle, and one you should get out of the way as soon as possible. "Build early, build often," is a motto that has served me well for many years.
The device-testing is also limited in that you have to specifically select a test target, so you can't automatically run your unit tests every time you build your app. But as long as you remember to unit-test at least daily, and immediately after making major changes, I can't see this as that big a shortcoming.
Here's some code to run tests in their own separate database, so that you don't pollute your application's data.
Your test case's .h file looks something like this:
and its .m:
- a) officially submitted my Wikitravel-editor iPhone app to the App Store
- b) released its source code under a BSD open-source license for all to see and play with, at code.google.com/p/itravelwrite.
To be honest I would have liked another few days for cleanup and further testing, but I've gone and gotten a real job (iPhone app development, again) that will occupy me for at least a couple of months, so I had to hurry things up. I apologize for the ugliness of the code.
Some of that ugliness is due to its extremely crude testing framework. Unit testing, the panacea of modern software development, is bizarrely difficult to integrate with the iPhone SDK. There are various tools to help you - one created by Google - but they're all far more complex and kludgy than I'd like, especially compared to the elegant joy that is GAEUnit.
It turns out, however, that it is possible to seamlessly integrate unit tests and test-driven development into iPhone development with XCode. There are a few caveats, but overall, these instructions work very well, if you follow them very carefully.
The caveat is that you can only run the unit tests on an actual physical device, not in the simulator. Now, in some ways this is actually a bonus - you avoid the "but it works in the simulator!" pitfall, and I have learned that the only way to get a feel for what your UI should be is to work on a real iPhone or iPod Touch, the simulator is disastrously misleading - but it does mean that you'll have to go ahead and shell out the $100 to join the iPhone Developer Program so that you can generate the certificates needed to sign your app and install it on your device.
Which in turn is a bit of a hassle. But it's a necessary hassle, and one you should get out of the way as soon as possible. "Build early, build often," is a motto that has served me well for many years.
The device-testing is also limited in that you have to specifically select a test target, so you can't automatically run your unit tests every time you build your app. But as long as you remember to unit-test at least daily, and immediately after making major changes, I can't see this as that big a shortcoming.
Here's some code to run tests in their own separate database, so that you don't pollute your application's data.
Your test case's .h file looks something like this:
// Dependent unit tests mean unit test code depends on an application to be injected into.
// Setting this to 0 means the unit test code is designed to be linked into an independent executable.
#define USE_DEPENDENT_UNIT_TEST 1
#import <SenTestingKit/SenTestingKit.h>
#import <UIKit/UIKit.h>
#import "MyAppDelegate.h"
@interface MyTestCase : SenTestCase {
NSManagedObjectContext *context;
}
@end
and its .m:
-(void) setUp {
MyAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSURL *storeUrl = [NSURL fileURLWithPath: [[appDelegate applicationDocumentsDirectory] stringByAppendingPathComponent: @"MyAppTest.sqlite"]];
NSError *error;
NSPersistentStoreCoordinator *persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [appDelegate managedObjectModel]];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error]) {
// Handle error
NSLog(@"Error creating persistent store MyAppTest.sqlite %@, %@", error, [error userInfo]);
}
context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator: persistentStoreCoordinator];
}
-(void) tearDown {
[context release];
}
Labels: Apple, iPhone, testing, tests, unit, XCode
Thursday, July 2, 2009
Don't call me an Objectivist...
...but I speak a new language now.
Well, sorta. I write to you on my shiny new MacBook, which I purchased in part because you can't write iPhone applications on non-Macs. I guess I don't resent that. Much. It is a nice machine, although I'm still getting used to all of OS X's quirks.
But never mind the OS: writing an iPhone version of my app means mastering the iPhone SDK and a whole new language, Objective-C. Which was kind of frustrating for a week. Imagine, for a moment, that you're a car mechanic. Now imagine that you move to a new garage, where the cars are totally different, not just the engines but even the doors and the dashboard controls are laid out in some bizarre new arrangement, and instead of your old tools, there's a wall full of all these curvy New Age things that look more like vacuum cleaner parts and art objects than hex wrenches and hammers. That's kind of what diving into the iPhone SDK was like.
But. You are a car mechanic. And ultimately, we're still talking about wheeled vehicles powered by internal combustion engines; so after a bewildering week, during which you often don't even know what questions to ask, you begin to realize that in fact these cars are quite elegantly if counterintuitively designed, and that these tools are well suited to them.
Yeah, so the metaphor got out of hand, sue me. Anyway, I didn't know Objective-C, but I've been writing software professionally for nigh on twenty years now, and it's kind of a weird mixture of C and Smalltalk, both of which I have worked with before. It turns out that it is a powerful and flexible language, and the iPhone SDK's tools and libraries do indeed ultimately make sense.
Don't get me wrong. There are still things I hate. Mostly the hangovers from C. Just like C, object you create requires two files: one where all the code goes, and a "header file" that describes the code. It was a bad idea then, and it's a bad idea now, which is why no modern languages require it. The resulting profusion of files is messy, annoying, completely unnecessary, and provides more places for things to go wrong.
Also, you have to allocate memory by hand, and then be extremely careful to de-allocate it, lest you leak memory and consume all your phone's resources. I shake my head with bewildered contempt. Manual memory management is archaic, clumsy, tedious, and perpetually prone to disaster. Making it part of the iPhone SDK is like having a beautifully renovated marble-and-gold bathroom built atop lead plumbing.
(My understanding is that if you're writing for Macs, you can have automatic garbage collection, like the rest of the civilized world, but such is strongly discouraged on the iPhone. Scarce resources, apparently. But I note that Java development for similarly specced Android phones, with garbage collection - and sans useless header files - works just fine...)
I have also learned that there are subtle pitfalls in the Objective-C environment. I'll concede that this can be true of Java as well, though usually that's when you're dealing with classpaths, less of an issue when doing Android development. But, well, see for yourself. Here's what looks like a garden-variety call to a Settings class:
which should call the "getSearchPageURL" class method on my "Settings" class, which right now is pretty much just a bundle of constants. Here's a simplified Settings.h:
and Settings.m:
Nothing to it, right? Compiled just fine. Did it run? Did it hell. Instead I got
OK, points for the amusing "trouble ahead" warning, but what the hell? I'm just calling a very simple function that does nothing but return a hardcoded string - so what could possibly have gone wrong?
Turns out that function automatically gets abstracted up at runtime into a selector - and for that to work, Settings must extend NSObject. Which is trivially done, of course, but far from intuitive.
Wacko problem number 2: I changed a variable from an NSArray to an NSDictionary, and reworked the code appropriately - no big deal - and suddenly the app was dying with an EXC_BAD_ACCESS call, meaning that I was trying to release the memory of an object I had already released. Muttering foul imprecations about the whole notion of manual garbage collection, I went carefully through my code and found ... nothing. So I Googled. (Which is how I ultimately solved the previous problem, too; but in both cases I had to go well past the first page of results, which is one reason I'm writing this with copious details. Maybe future sufferers will find their way to this post.)
Here are the relevant parts of the header file:
and it turns out that the problematic line in the implementation was this one:
Looks harmless enough, doesn't it? Both urlSuffixes and pageNames are perfectly acceptable NSArrays of NSStrings. So what's the problem?
Well, if you're not familiar with Objective-C, you might be a little gobsmacked to learn that the solution was to change the above line to
Yes, really.
As far as I can tell, the difference is that in the second example, instead of the instance variable being directly modified, the @synthesized auto-generated setter is called, with "retain" as dictated in the @property definition, which prevents the NSDictionary from being released when we exit the method which generates it. Perhaps some people think of this stuff as intuitive. Not me. It's all fixed and working now, but this kind of man-behind-the-curtain stuff makes me a little uneasy.
That said, there's a lot to like. The user-interface stuff, once you figure it out, is (mostly) a joy to work with. You can lay out items visually or programmatically; you get a built-in navigation header and button toolbar, yours for just a few lines of code; it's easy to reach out via HTTP, with a one-line synchronous-call version, a delegated version with fine control, or a compromise somewhere between; and as of the 3.0 SDK, instead of hand-writing SQL code to access SQLite, you can use Apple's Core Data engine to manage all your persistent data. (Which comes with its own problems but overall is definitely more elegant and more powerful.)
Also, while I wouldn't say that I'm fluent in Objective-C yet, I'm now conversant, and I like it a lot when it's reminding me of Smalltalk, and not reminding me of C. You do, however, seem to wind up writing a lot more lines of code than you would in Java to do the same thing. It's like speaking German vs. English.
Overall, though, qualified thumbs up. I still prefer Java to Objective-C, and there are still things that Android can do and the iPhone can't - multitasking is the biggest, and the associated inter-app communication possibilities - while I can't think of any examples of the converse. But I am increasingly a fan of the latter's SDK. What at first looked a bit like a junkyard is now becoming more of a playground.
Well, sorta. I write to you on my shiny new MacBook, which I purchased in part because you can't write iPhone applications on non-Macs. I guess I don't resent that. Much. It is a nice machine, although I'm still getting used to all of OS X's quirks.
But never mind the OS: writing an iPhone version of my app means mastering the iPhone SDK and a whole new language, Objective-C. Which was kind of frustrating for a week. Imagine, for a moment, that you're a car mechanic. Now imagine that you move to a new garage, where the cars are totally different, not just the engines but even the doors and the dashboard controls are laid out in some bizarre new arrangement, and instead of your old tools, there's a wall full of all these curvy New Age things that look more like vacuum cleaner parts and art objects than hex wrenches and hammers. That's kind of what diving into the iPhone SDK was like.
But. You are a car mechanic. And ultimately, we're still talking about wheeled vehicles powered by internal combustion engines; so after a bewildering week, during which you often don't even know what questions to ask, you begin to realize that in fact these cars are quite elegantly if counterintuitively designed, and that these tools are well suited to them.
Yeah, so the metaphor got out of hand, sue me. Anyway, I didn't know Objective-C, but I've been writing software professionally for nigh on twenty years now, and it's kind of a weird mixture of C and Smalltalk, both of which I have worked with before. It turns out that it is a powerful and flexible language, and the iPhone SDK's tools and libraries do indeed ultimately make sense.
Don't get me wrong. There are still things I hate. Mostly the hangovers from C. Just like C, object you create requires two files: one where all the code goes, and a "header file" that describes the code. It was a bad idea then, and it's a bad idea now, which is why no modern languages require it. The resulting profusion of files is messy, annoying, completely unnecessary, and provides more places for things to go wrong.
Also, you have to allocate memory by hand, and then be extremely careful to de-allocate it, lest you leak memory and consume all your phone's resources. I shake my head with bewildered contempt. Manual memory management is archaic, clumsy, tedious, and perpetually prone to disaster. Making it part of the iPhone SDK is like having a beautifully renovated marble-and-gold bathroom built atop lead plumbing.
(My understanding is that if you're writing for Macs, you can have automatic garbage collection, like the rest of the civilized world, but such is strongly discouraged on the iPhone. Scarce resources, apparently. But I note that Java development for similarly specced Android phones, with garbage collection - and sans useless header files - works just fine...)
I have also learned that there are subtle pitfalls in the Objective-C environment. I'll concede that this can be true of Java as well, though usually that's when you're dealing with classpaths, less of an issue when doing Android development. But, well, see for yourself. Here's what looks like a garden-variety call to a Settings class:
NSMutableString *urlString = [NSMutableString stringWithCapacity:128];
[urlString appendString:[Settings getSearchPageURL]];
which should call the "getSearchPageURL" class method on my "Settings" class, which right now is pretty much just a bundle of constants. Here's a simplified Settings.h:
#import <UIKit/UIKit.h>
@interface Settings {
}
+(NSString *) getSearchPageURL;
and Settings.m:
+(NSString *)getSearchPageURL
{
return @"http://wetravelwrite.appspot.com/mb/searchForPage";
}
Nothing to it, right? Compiled just fine. Did it run? Did it hell. Instead I got
2009-07-02 22:48:28.519 iTravelWrite[17002:20b] *** NSInvocation: warning: object 0xaa40 of class 'Settings' does not implement methodSignatureForSelector: -- trouble ahead
2009-07-02 22:48:28.520 iTravelWrite[17002:20b] *** NSInvocation: warning: object 0xaa40 of class 'Settings' does not implement doesNotRecognizeSelector: -- abort
OK, points for the amusing "trouble ahead" warning, but what the hell? I'm just calling a very simple function that does nothing but return a hardcoded string - so what could possibly have gone wrong?
Turns out that function automatically gets abstracted up at runtime into a selector - and for that to work, Settings must extend NSObject. Which is trivially done, of course, but far from intuitive.
Wacko problem number 2: I changed a variable from an NSArray to an NSDictionary, and reworked the code appropriately - no big deal - and suddenly the app was dying with an EXC_BAD_ACCESS call, meaning that I was trying to release the memory of an object I had already released. Muttering foul imprecations about the whole notion of manual garbage collection, I went carefully through my code and found ... nothing. So I Googled. (Which is how I ultimately solved the previous problem, too; but in both cases I had to go well past the first page of results, which is one reason I'm writing this with copious details. Maybe future sufferers will find their way to this post.)
Here are the relevant parts of the header file:
@interface SearchController : UITableViewController{
NSDictionary *searchResults;
}
@property (nonatomic, retain) NSDictionary *searchResults;
and it turns out that the problematic line in the implementation was this one:
searchResults = [NSDictionary dictionaryWithObjects: urlSuffixes forKeys: pageNames];
Looks harmless enough, doesn't it? Both urlSuffixes and pageNames are perfectly acceptable NSArrays of NSStrings. So what's the problem?
Well, if you're not familiar with Objective-C, you might be a little gobsmacked to learn that the solution was to change the above line to
self.searchResults = [NSDictionary dictionaryWithObjects: urlSuffixes forKeys: pageNames];
Yes, really.
As far as I can tell, the difference is that in the second example, instead of the instance variable being directly modified, the @synthesized auto-generated setter is called, with "retain" as dictated in the @property definition, which prevents the NSDictionary from being released when we exit the method which generates it. Perhaps some people think of this stuff as intuitive. Not me. It's all fixed and working now, but this kind of man-behind-the-curtain stuff makes me a little uneasy.
That said, there's a lot to like. The user-interface stuff, once you figure it out, is (mostly) a joy to work with. You can lay out items visually or programmatically; you get a built-in navigation header and button toolbar, yours for just a few lines of code; it's easy to reach out via HTTP, with a one-line synchronous-call version, a delegated version with fine control, or a compromise somewhere between; and as of the 3.0 SDK, instead of hand-writing SQL code to access SQLite, you can use Apple's Core Data engine to manage all your persistent data. (Which comes with its own problems but overall is definitely more elegant and more powerful.)
Also, while I wouldn't say that I'm fluent in Objective-C yet, I'm now conversant, and I like it a lot when it's reminding me of Smalltalk, and not reminding me of C. You do, however, seem to wind up writing a lot more lines of code than you would in Java to do the same thing. It's like speaking German vs. English.
Overall, though, qualified thumbs up. I still prefer Java to Objective-C, and there are still things that Android can do and the iPhone can't - multitasking is the biggest, and the associated inter-app communication possibilities - while I can't think of any examples of the converse. But I am increasingly a fan of the latter's SDK. What at first looked a bit like a junkyard is now becoming more of a playground.
Labels: Apple, doesNotRecognizeSelector, EXC_BAD_ACCESS, iPhone, Objective-C, SDK
Subscribe to Posts [Atom]