MobiDevDay Presentation Slides
I gave a presentation on blocks and Grand Central Dispatch today at MobiDevDay. You can download the slides at SlideShare.
Looking for more reading about blocks? Here are some more resources:
- Programming with C Blocks by Joachim Bengtsson
- Introducting Blocks and Grand Central Dispatch by Apple
- Blocks Programming Topics by Apple
Also, as we saw in the presentation, check out cdecl.org to cheat!
UPDATE: Looking for example code? I’ve put some of the code that went into the slides up on GitHub. It’s light, but it also includes a .PDF version of the presentation.
iPhone Programming: Hard Mode
After a conversation with some co-workers, I discussed how it would be technically possible to write an iPhone app using only a main.m file—no separate class files. This post is the result of that. It’s definitely doable, but not something I’d ever recommend for shipping code.
The code is explained below, but you can get the full source in my public GitHub repository.
Getting Started
By default, an iPhone application template gives you a main.m file, but it’s pretty basic:
#import <UIKit/UIKit.h>
int main(int argc, char *argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, nil);
[pool release];
return retVal;
}
To go from that code to running your application, a few things happen, mostly in UIApplicationMain. Its last argument, typically nil, is a pointer to an NSString that specifies the class name of the application delegate. Normally, your Info.plist file specifies a nib file (the keys NSMainNibFile or NSMainNibFile~ipad), which in turn specifies the application delegate, instantiates it, etc.
So, first, delete the MainWindow.xib file, then all other class files (MyCoolAppDelegate.h and MyCoolAppDelegate.m, for instance). There should be three files left in the iPhone application: your Prefix.pch file (which we could technically do without), main.m, and your Info.plist file.
The Fun Begins
Administrivia: Creating Strings
Since, as a part of the exercise, we don’t want to use Objective-C, I also decided against using any constant NSStrings (@"Hello, World!" and the like). To create a string, then, we’ll use CFStrings. I created a macro to create one from a C string using ASCII encoding:
#define JK_EasyCFString(X) (CFStringCreateWithCString(kCFAllocatorDefault, (X), kCFStringEncodingASCII))
Don’t forget to call CFRelease() when you’re done with it, though.
Creating a Class
We need to modify the call to UIApplicationMain to include our application delegate class if we want this app to be anything more than a blank screen. What class name can we give it, though? We’ll need to create a class, and we’ll call it “HardModeAppDelegate.”
// Create a class to serve as our application delegate. Class appDelegate = objc_allocateClassPair(NSClassFromString((id)NSObjectString), "HardModeAppDelegate", 0);
The first argument is a toll-free bridged CFString that specifies NSObject as the superclass, then we pass in the name of the class as a C string and 0 for the number of extra bytes we want (which is nearly always zero). Now let’s set it up a bit:
// Conform to the UIApplciationDelegate protocol. Protocol *appDelegateProto = NSProtocolFromString((id)UIApplicationDelegateString); class_addProtocol(appDelegate, appDelegateProto); // Add methods. SEL applicationDidFinishLaunchingWithOptions = NSSelectorFromString((id)appDidFinishLaunchingOptionsString); class_addMethod(appDelegate, applicationDidFinishLaunchingWithOptions, (IMP)UIApplicationDelegate_ApplicationDidFinishLaunchingWithOptions, "v@:@@"); SEL dealloc = NSSelectorFromString((id)deallocString); class_addMethod(appDelegate, dealloc, (IMP)appDelegate_dealloc, "v@:"); // Add an instance variable for the window. class_addIvar(appDelegate, "window", sizeof(id), log2(sizeof(id)), "@"); // Now that we’ve added ivars, we can register the class. objc_registerClassPair(appDelegate);
That code is the same as writing an @interface block for a new class, setting up ivars, conforming to protocols, and defining instance methods, but done using the runtime calls instead. Methods are actually C functions, but with two arguments prepended to the argument list: id self and SEL _cmd. Those arguments are actually passed to every Objective-C method, but usually hidden.
Custom Message Sending
The rest of the application is more of this bootstrapped Objective-C in C, but there are a few wrinkles worth discussing, most notably the use of objc_msgSend with methods that return other than id and/or have additional arguments beyond self and _cmd. For instance, to call -bounds on a UIScreen object, I had to cast the return type of objc_msgSend to CGRect:
CGRect (*msgSendBounds)(id self, SEL _cmd); msgSendBounds = (CGRect(*)(id, SEL))objc_msgSend_stret;
Similarly, when calling -initWithFrame: on a UIWindow or UILabel, I casted it to take in a CGRect argument:
id (*msgSendCGRect)(id self, SEL _cmd, CGRect rect); msgSendCGRect = (id(*)(id, SEL, CGRect))objc_msgSend;
Accessing Instance Variables
In my implementation of the HardModeAppDelegate class’s -dealloc method, I need to access the window instance variable to send it a -release message. Using the Ivar type and the object_getIvar function, it becomes easy:
Ivar windowIvar = class_getInstanceVariable(self->isa, "window"); id window = object_getIvar(self, windowIvar);
So what do we have?
To be clear, this is not writing an iPhone app without using Objective-C, per se. The runtime is still being used, messages are being sent, all that. But it is an illustration of some of the heavy lifting that the runtime does for you. I would caution against adopting this practice for real, shipping apps.
Using AppleScript to Automate Data Entry
I was working on an app today and ran into a problem: I had to transfer data from a table in a Microsoft Word document to a dictionary in a dictionary in a dictionary in a property list in Xcode. After copying and pasting several times—I had 243 total entries to copy—I figured there had to be a better way. So, I fired up AppleScript Editor and wrote this quick script to do nine at a time:
repeat 9 times tell application "Xcode" activate end tell tell application "System Events" keystroke tab keystroke tab end tell tell application "Pages" activate end tell tell application "System Events" keystroke "c" using {command down} delay 0.5 keystroke tab end tell tell application "Xcode" activate end tell tell application "System Events" keystroke "v" using {command down} end tell end repeat
Nothing fancy, but it worked, and I was saved from carpal tunnel syndrome. So if you find yourself needing to do something tedious, repetitive, and (most of all) easily reproduced, you too can turn to AppleScript to get it done. I won’t spend too much time on explaining the code, but just know that you have to enable access for assistive devices in System Preferences before doing it.
Take Me Home
Take Me Home was a good “Hello, World!” iOS app, but its time has come. I’ve pulled Take Me Home from the App Store. Goodnight, sweet prince.
Selling “Physical Goods” or “Goods and Services Used Outside of the Application” in an iOS app
Since this question has come up a few times, I thought I’d record my answer to a common question:
Can I use an iPhone app to sell something outside of the App Store?
Reading Apple’s rules for the App Store makes developers nervous. We’re walking on eggshells, hoping that our application will not run afoul of the myriad of regulations put forth by Apple for inclusion into the store. So, questions like this come up. I’ll reproduce my answer here:
Obviously, I am not a lawyer, but I think you’ll be OK. Here’s my interpretation of the three relevant rules from the developer guidelines (emphasis mine):
11.1 Apps that unlock or enable additional features or functionality with mechanisms other than the App Store will be rejected.
11.2 Apps utilizing a system other than the In App Purchase API (IAP) to purchase content, functionality, or services in an app will be rejected.
11.3 Apps using IAP to purchase physical goods or goods and services used outside of the application will be rejected.
The first rule prohibits you from unlocking anything inside of your app with something other than the App Store. This would prevent you from, say, making a game that downloads new levels from your server based on your membership to a website.
The second rule prohibits you from, say, making a game and enabling PayPal in it to unlock more levels. Apple wants you to use in-app purchase for that.
The third rule—and this is where it gets interesting—prohibits you from using in-app purchase in an application to buy “physical goods” or “goods and services used outside of the application.” Nowhere does it say, however, that you can’t use other purchasing systems.
With that third rule, I think what Apple is saying is this: anything that runs on the iPhone must be purchased through the App Store, and everything purchased in the App Store must run on iOS. For something like insurance, which isn’t new functionality in the app, I think you’ll be OK. This is absolutely worth an e-mail to Apple’s technical support staff, but if you look at Amazon’s app, you can purchase physical goods using Amazon’s checkout system.
I’d love to be proven right or wrong on this. If my reading is correct, then using an iOS app as a storefront is perfectly acceptable. If I’m reading this too charitably, then you’re better off making this type of application for a different platform.
Autorelease is Not Your Friend
How many times have you written this line?
NSMutableArray *foo = [[[NSMutableArray alloc] init] autorelease];
At first glance, it looks fine. foo is an autoreleased NSMutableArray that you can use and, at the end of the method, it’s gone into the ether of the autorelease pool. Don’t get me wrong, most of the time, this use of -autorelease is acceptable. But, in this post, I’ll try to convince you to use autorelease differently in subtle ways. Read more
Apple Doesn’t Like “Die, You Gravy-Sucking Pig-Dog!”
There’s a relatively well-known Easter egg in BSD’s shutdown.c: a function named die_you_gravy_sucking_pig_dog (side note: I’ve got three-to-one odds that say whoever wrote that has a huge UNIX beard). It turns out that Apple doesn’t care to have such uncouth function names floating around, so they re-defined it:
#ifdef __APPLE__
void log_and_exec_reboot_or_halt(void);
#else
void die_you_gravy_sucking_pig_dog(void);
#endif
Sure, it does the same thing, but I don’t think log_and_exec_reboot_or_halt has the same panache.
Xcode 3.2: Using GDB as a Non-Admin User
New in Xcode 3.2 is an authorization setting that looks like this:
<dict> <key>allow-root</key> <false/> <key>class</key> <string>rule</string> <key>comment</key> <string>For use by Apple. WARNING: administrators are advised not to modify this right.</string> <key>k-of-n</key> <integer>1</integer> <key>rule</key> <array> <string>is-admin</string> <string>is-developer</string> <string>authenticate-developer</string> </array> <key>shared</key> <true/> </dict>
The upshot of this is that if you aren’t in the _developer group in the local directory, you’ll have to authenticate as an administrator to use gdb or some of the performance tools. For the vast majority of developers on Mac OS X, who run as an administrator, this is fine, but if you’re running as a regular user, either for security reasons or because you’re in something like a lab setting, this can be a problem. To add a user to the _developer group, use the dscl command:
dscl . -append /Groups/_developer GroupMembership UserName
Replace UserName with the short name of your user account (or $(whoami)) and you should be all set.
If you’re administering Mac OS X in a lab setting, you can either create a LaunchAgent that handles this or a login hook. See the Apple tech note “Running At Login” for more information on login hooks. As an added touch, my login and logout scripts to handle this also remove all users from the group, like so:
dscl . -delete /Groups/_developer GroupMembership
If the GroupMembership key doesn’t exist, dscl will create it—and it doesn’t exist by default—so deleting it outright shouldn’t cause any problems.
Dealing with Special Characters in iPhone 4 Graphics Filenames with Subversion
With the iPhone 4’s high-resolution screen, designers need to create two sets of art; the guidelines are to name the files like so: SomeCoolImage.png and SomeCoolImage@2x.png. Unfortunately, if you try to add these files to an SVN repository, the @ symbol throws them off:
$ svn add Icon\@2x~iphone.png svn: warning: 'Icon' not found
The fix, thanks to the subversion_users Google Group, is to add another @ to the end of the filename, like so:
$ svn add ./Icon\@2x~iphone.png@ A (bin) Icon@2x~iphone.png
If you’d like to do this for all of your high-resolution art in a folder, here’s a tiny Bash command for the task:
for x in `ls *\@*`; do svn add $x\@; done
How-To: Run a LaunchDaemon That Requires Networking
I’m a big fan of using launchd to automate things in Mac OS X. That serves me well, as that’s how Apple wants things done moving forward. That said, one of launchd’s biggest shortcomings is a lack of a dependency system. There is currently no way, for instance, to specify in a LaunchDaemon’s property list that the daemon requires the network to be active in order to run. This is problematic for some things, such as a script I wrote to automatically set the computer’s hostname based on the DNS server (more on that later). Luckily, Apple has already defined a function, CheckForNetwork, in /private/etc/rc.common. Here it is in all its glory:
##
# Determine if the network is up by looking for any non-loopback
# internet network interfaces.
##
CheckForNetwork()
{
local test
if [ -z "${NETWORKUP:=}" ]; then
test=$(ifconfig -a inet 2>/dev/null | sed -n -e '/127.0.0.1/d' -e '/0.0.0.0/d' -e '/inet/p' | wc -l)
if [ "${test}" -gt 0 ]; then
NETWORKUP="-YES-"
else
NETWORKUP="-NO-"
fi
fi
}
In your code, simply include rc.common, then call CheckForNetwork as needed. An example:
#!/bin/bash
# Example Daemon Starter
. /etc/rc.common
CheckForNetwork
while [ "${NETWORKUP}" != "-YES-" ]
do
sleep 5
NETWORKUP=
CheckForNetwork
done
# Now do what you need to do.
Note that this will keep the script running indefinitely until CheckForNetwork sets NETWORKUP to “-YES-,” so if there’s a networking problem your code may never execute.

