Tuesday, September 6, 2011

JavaScript, HTML5, and Binary Data

Lately I've been playing around with HTML5 and JavaScript, seeing what new things are possible, and in general honing up on my JS skills (HTML4 and the modern DOM were a long time ago!).

In the process of all this, I started thinking about binary data and JavaScript, and, likewise, what a waste it is transmitting and manipulating strings EVERYWHERE. Yeah, the compilers and profilers help, as does compression, caches, code minimizers, etc., but still.

Think about the worst-case, dumbest scenario: - You're transmitting XML.
- You're transmitting data verbosely ('attribute="true"' instead of 'a=1').
- You're only effectively using ASCII (0-127) but you're transmitting in UTF-8 (0-255).
- You're not using Gzip compression.
- You're using XMLHttpRequest to poll for data regularly, and, since you don't want older browsers caching it, putting in a random number so you're always getting the data. If your protocol sucks, you may be returning several thousand bytes for "no change" instead of just "0".
- Your JS sucks, so you're parsing through that load of data, and all those strings, rather slowly, making too many DOM queries, and bogging things down.

The usual solution(s) is: - Use Gzip compression.
- Use JSON instead of XML.
- Don't write crap code.

That does the trick for most sites, because, as we all should know, premature optimization is the root of all evil! Just STOP while you're ahead!

If you want to compress your data further though, and are free to use recent browser versions (this is an important caveat!), what if you want to go further? Use binary!

Basic idea: - Make sure this is appropriate for your application. You'll likely have to transmit some sort of key for the binary data, so, you'll want to use HTML5 local storage (for your key/js algorithm) to get the most benefit. Or a well-written algorithm. You can also depend on the cache if you have to, but that'll only get you so far. HTML5 local storage will serve as your traditional library, so to speak.
- On the server, fit the data into a PNG file format. Grayscale, 8 bits per channel, no alpha.
- On the client, create a <canvas> element. Make sure it's hidden.
- Use XMLHttpRequest to get the PNG file with the data and write it to the <canvas> element.
- In this case, each pixel on the <canvas> is a byte. Read the pixel, translate it to binary, and do whatever conversions you need to do.
- Optionally, if this is a very large data set, considering using the HTML5 <canvas> compositing methods to perform your XOR and bit-wise AND operations. Hopefully this will take advantage of the GPU acceleration!

Details: 1) Generate the PNG file on the server. In this case, we are going to encode 4 bits (acting as flags). Naturally, you'll want to encode more than this because the PNG headers and file structure takes a minimum of 67 bytes!

Note: I originally learned this idea from garethrees.org. It's a great source. You will also want to look at libpng.org and w3.org for more detailed specs.

In general, we can create a grayscale PNG file, with no alpha, 8 bits per channel, and everything else on default settings, and this will effectively result in each pixel being 8 bits. I've been working on this with a Python server, so:

Python example:

import struct, zlib def chunk(type, data): # pack in big-endian order. format for a PNG chunk is: # [length of data in chunk] [chunk type] [data] [crc checksum on data] return (struct.pack('>I', len(data)) + type + data + struct.pack('>i', zlib.crc32(type + data))) png = ('\x89PNG\r\n\x1A\n' + # PNG magic number # 1 = width, 1 = height, 8 = 8bit channel, 0 = color type (grayscale), 0 = compression method (standard, aka, zlib), 0 = filter method (standard), 0 = interlace method (none) chunk('IHDR', struct.pack('>IIBBBBB', 1, 1, 8, 0, 0, 0, 0)) + # data. first byte is required (0=default filter). 14 = 1110 in binary, what we are trying to send over chunk('IDAT', zlib.compress(struct.pack('>BBB', 0, 14))) + chunk('IEND', ''))


2) On the client, figure out a way to get to this file. Typically you'd use a XMLHTTPRequest to ask for the file, and the server send it over. Stuff it into an Image object. For the example here, I am going to assume you have a local PNG file you can read in just for testing purposes.

<html><head> <script type="text/javascript"> window.addEventListener("load", eventWindowLoaded, false); function eventWindowLoaded () { canvas = document.getElementById("canvas_test") context = canvas.getContext("2d") i = new Image() i.src="test.png" i.onload = function() { // TODO } } </script> </head><body><canvas id="canvas_test_"><canvas>

3) Write the Image object to the canvas.

// STARTING FROM THE "TODO" SECTION ABOVE context.drawImage(i, 0, 0) // write the PNG to the CANVAS

4) Read back the pixel data in the canvas to figure out the bytes. Convert this to binary.

d = context.getImageData(0, 0, 1, 1) // just one pixel (1, 1) d.data[0] // pixel values from <canvas> come in 4 bytes (r, g, b, a). in our case [0] (and [0+i*4]) give us the data d.data[0].toString(2) // this is your binary string // you can also go from here and use bit operations to decode your data // or, instead of doing that, create a <canvas> image object that // has your bitwise operations on it and go to town!

Saturday, November 13, 2010

Creating an iPhone App "From Scratch"

Current as of: ~Jan. 2011 (iOS 4.2 and XCode 3.2.5).
Download source code.

This tutorial explains how to compile, build, and deploy to the Simulator an iPhone application without using Xcode or Interface Builder. I don't claim that this is the best way to go about developing without Xcode/IB, but, it works well for me.

I will attempt to explain things as I go along, however, you'd best be advised to read through the code and, in particular, read the comments in the code.

Pros (subjective!): - Better grasp of the basics and what's "under the hood".
- More control over the build process and the user interface.
- Possibly better integration with standard Unix development tools.
- Less reliance on the mouse. Useful as I sometimes develop on a Netbook where the UI/mouse is a real pain. Allows for more efficient development with, say, just a fullscreen emacs and fullscreen terminal, which also fits my development tools on other platforms.
- Lack of dependence on Xcode toolsuite.
- Probably others.

Cons (subjective!): - No Xcode benefits such as "intellisense" (though some editors support this). - Syntax highlighting and formatting dependent on your editor (though almost all editors support this). - Debugging probably both more complicated and more labor-intensive. - Probably others.

TO DO: - Figure out how to script the simulator to start an app automatically on launch. - Improve the monitoring of the NSLog log file (probably with a tail script).- Add some scripts and pre-processor macros for GDB integration.- Figure out how to use a Prefix file for common includes- Determine minimum entries required in Info.plist.- Determine the absolute minimum compilation and linking flags and variables.- Put together a Makefile and script for deployment to an iPhone.- Change $DIR var in Makefile to not use pwd.- Determine why "User" and "OS" deployment locations differ.

CHANGES: 8.9.2010 - N/A. Initial release.1.29.2011 - Updated for iOS 4.2. (minor bug fix in Makefile)

Contents

1. The App Proper Files: main.m BasicApp.m BasicApp.h

Though technically it is not necessary to seperate these files, it is rather wise to do so as a foundation for a larger application where such partitioning is necessary. These files constitute what is just shy of the absolute minimum for an iPhone app using the standard Apple framework.

These files are pretty straight-forward. First, we create a basic entry point (main.m), then tell it to go grab our application delegate, a basic class that inherits a protocol from UIApplicationDelegate (BasicApp.m/.h). Our class, in turn, programatically creates a UIWindow with a basic UIView attached, sets a background color for the view, and displays the window. Done.

main.m

#import <UIKit/UIKit.h> int main (int argc, char *argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Start up the application and point it's delegate to our BasicAppDelegate class // (BasicApp.m/.h). UIApplicationMain(int argc, char *argv[], // NSString *principalClassName, NSString *delegateClassName) int r = UIApplicationMain(argc, argv, @"UIApplication", @"BasicAppDelegate"); [pool release]; return r; }

BasicApp.h

#import <UIKit/UIKit.h> @interface BasicAppDelegate: NSObject { // These are the class variables we will use for our UIWindow and UIView objects. // We could have just as easily declared these in BasicApp.m but decided not to. // Your call. UIWindow *mainWindow; UIView *mainView; } // The UIApplicationDelegate protocol requires you implement this function. // It will return our main UIWindow so the application (UIApplicationMain from main.m) // knows where to start. - (UIWindow *) getMainWindow; @end

BasicApp.m

#import "BasicApp.h" @implementation BasicAppDelegate - (void) applicationDidFinishLaunching: (UIApplication *) application { // Note that mainScreen is a class method that returns the device's screen // and is provided by the platform by default. // Create the window and set it's size to the maximum. mainWindow = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]]; // Determine the max size allowed for the view. This is based off of the application // size. This should be something like the max size minus the status bar up top. mainView = [[UIView alloc] initWithFrame: [[UIScreen mainScreen] applicationFrame]]; // Add the view to our window and set the color of the view // (the color change is really only necessary to make it more obvious // that we've succeeded). [mainWindow addSubview: mainView]; [mainView setBackgroundColor: [UIColor lightGrayColor]]; // Display the window and make it the "key" window. The "key" window is the one // that will receive user input. [mainWindow makeKeyAndVisible]; } - (void) applicationWillTerminate { [mainWindow release]; } - (UIWindow *) getMainWindow { return mainWindow; } @end
2. The Info.plist Files: Info.plist

An application under iOS/OSX usually consists of more than the application binary. In reality a ".app" is a directory with a specific structure. For example, when we finish, our application bundle will look like this:

.app is a directory

~/BasicApp.app > ls -l total 20 -rwx------+ 1 user domain group 15384 2010-08-08 18:51 BasicApp -rwx------+ 1 user domain group 806 2010-08-08 18:51 Info.plist ~/BasicApp.app >

If you examine other applications, you will notice plenty of other items, such as sub-directories for cache, images, data, screenshots, etc., however, the minimum requirements for an iPhone app are a) the application binary and b) the Info.plist.

When you copy an app onto the Simulator, the Simulator interrogates the Info.plist to determine things such as what the application name is, what the binary is to launch, what kind of region the app was designed for, any special styles you want to apply, the version number, etc. There are many options for the Info.plist, however, the important ones are CFBundleExecutable and CFBundleIdentifier.

The Info.plist is just XML, however, one thing to keep in mind is that the iPhone can accept either a binary "compiled" Info.plist or just a plain text plist. Xcode by default uses a compiled plist while we will use a regular text one (mostly because I can't really be bothered to figure out the compilation step ;)).

Info.plist

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleDevelopmentRegion</key> <string>English</string> <!-- CFBundleExecutable needs to be the name of the actual application binary, which, in this example, is BasicApp. --> <key>CFBundleExecutable</key> <string>BasicApp</string> <!-- CFBundleIdentifier needs to be a UNIQUE name for your app. This is not the same thing as the display name. --> <key>CFBundleIdentifier</key> <string>com.whatever.SecondTest</string> <!-- This stuff is rather meaningless. Not sure yet if required. --> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleSignature</key> <string>????</string> <key>CFBundleVersion</key> <string>1.0</string> <!-- These are optional settings that define look-and-feel. Things like this can usually be set in the application code, however, those statements will not be executed until the application is run. These settings take effect before your application is ever displayed. --> <key>UIStatusBarHidden</key> <false/> <key>UIStatusBarStyle</key> <string>UIStatusBarStyleBlackTranslucent</string> <key>UIViewEdgeAntialiasing</key> <string>YES</string> </dict> </plist>

3. CompilingXcode doesn't do anything very mysterious here. After all, this is just Unix, and it is using mostly standard Unix applications. However, rather than delve too deep into the specifics of gcc and build up the compilation step by hand, we will take the easy way out: cheat!

It turns out that all Xcode is really doing is using the xcodebuild application, which is sort of Apple's replacement for make. Not only that, but xcodebuild can actually be quite verbose and tell you exactly how it is going about it's business (you can see the same info in an Xcode window but I find the command line to be clearer).

Try this: go create a new default app in Xcode, save it, build it, and run it. Pretty straight-forward, right? Now, quit Xcode, navigate to the directory of the new app, and run xcodebuild to see what is going on (you may have to run xcodebuild clean first):

Part of the compilation output from xcodebuild

CompileC build/Untitled.build/Release-iphonesimulator/Untitled.build/Objects-normal/i386/UntitledAppDelegate.o Classes/UntitledAppDelegate.m normal i386 objective-c com.apple.compilers.gcc.4_2 cd /Users/x/Documents/Untitled setenv LANG en_US.US-ASCII setenv PATH "/Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin:/Developer/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/local/git/bin:/usr/X11/bin" /Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin/gcc-4.2 -x objective-c -arch i386 -fmessage-length=0 -pipe -std=c99 -Wno-trigraphs -fpascal-strings -fasm-blocks -Os -mdynamic-no-pic -Wreturn-type -Wunused-variable -D__IPHONE_OS_VERSION_MIN_REQUIRED=30200 -isysroot /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator3.2.sdk -fvisibility=hidden -mmacosx-version-min=10.5 -gdwarf-2 -fobjc-abi-version=2 -fobjc-legacy-dispatch -iquote /Users/x/Documents/Untitled/build/Untitled.build/Release-iphonesimulator/Untitled.build/Untitled-generated-files.hmap -I/Users/x/Documents/Untitled/build/Untitled.build/Release-iphonesimulator/Untitled.build/Untitled-own-target-headers.hmap -I/Users/x/Documents/Untitled/build/Untitled.build/Release-iphonesimulator/Untitled.build/Untitled-all-target-headers.hmap -iquote /Users/x/Documents/Untitled/build/Untitled.build/Release-iphonesimulator/Untitled.build/Untitled-project-headers.hmap -F/Users/x/Documents/Untitled/build/Release-iphonesimulator -I/Users/x/Documents/Untitled/build/Release-iphonesimulator/include -I/Users/x/Documents/Untitled/build/Untitled.build/Release-iphonesimulator/Untitled.build/DerivedSources/i386 -I/Users/x/Documents/Untitled/build/Untitled.build/Release-iphonesimulator/Untitled.build/DerivedSources -DNS_BLOCK_ASSERTIONS=1 -include /var/folders/Yv/YvDzynCUEIaYVYJCCeIbHU+++TI/-Caches-/com.apple.Xcode.501/SharedPrecompiledHeaders/Untitled_Prefix-bzdssktcospdfwenxzjipogpifzt/Untitled_Prefix.pch -c /Users/x/Documents/Untitled/Classes/UntitledAppDelegate.m -o /Users/x/Documents/Untitled/build/Untitled.build/Release-iphonesimulator/Untitled.build/Objects-normal/i386/UntitledAppDelegate.o

So, the idea goes, if we examine this output, we can figure out what all the required compilation steps and flags are and assemble them into some start of the venerable Makefile.

Beginnings of a Makefile

# First-off, it looks like we need to set the PATH, so we'll just copy that over from # xcodebuild. Note that it is including the standard Unix bin directories # as well as the iPhone Platform directories. PATH=/Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin:/Developer/usr/bin: \ /usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/local/git/bin:/usr/X11/bin: \ /Developer/usr/bin:/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin # Second, we need to set the platform (not sure if this is required). MACOSX_DEPLOYMENT_TARGET=10.6 # Third, by examining the beginning of the xcodebuild output, it looks like the compiler # that's being used is gcc 4.2. We will use absolute pathing just to be sure. Note this is a # different location than some previous Xcode versions. CC=/Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin/gcc-4.2 # Now we need to dig into the gcc-4.2 flags. It doesn't really matter what these flags stand # for, provided we make sure there's nothing glaringly wrong, since we are just copying # xcodebuild to be safe. Note, however, that we are compiling for the Simulator # (i.e., local computer, i.e., 386) and that we have to point to the Simulator # SDK. If you are going to compile for deployment these flags WILL change. CFLAGS=-x objective-c -arch i386 -fmessage-length=0 -pipe -std=c99 -Wno-trigraphs \ -fpascal-strings -fasm-blocks -Os -mdynamic-no-pic -Wreturn-type -Wunused-variable \ -D__IPHONE_OS_VERSION_MIN_REQUIRED=30200 \ -DNS_BLOCK_ASSERTIONS=1 \ -isysroot /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator4.0.sdk \ -fvisibility=hidden -mmacosx-version-min=10.6 -gdwarf-2 \ -fobjc-abi-version=2 -fobjc-legacy-dispatch # Of course, we also need to define which source files are going to be included in our binary! SRCS = \ main.m \ BasicApp.m

Now, there are some other things that xcodebuild is doing here on the compilation step, but they are not required, so we will skip them for now.

4. Linking Similar to the compiling step above, we will examine the output of xcodebuild to put together the linking portion of the Makefile. The linking step will assemble the individually compiled source files from above and combine them into an actual application binary.

Example linking output from xcodebuild

Ld build/Release-iphonesimulator/Untitled.app/Untitled normal i386 cd /Users/p/Documents/Untitled setenv MACOSX_DEPLOYMENT_TARGET 10.5 setenv PATH "/Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin:/Developer/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/local/git/bin:/usr/X11/bin" /Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin/gcc-4.2 -arch i386 -isysroot /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator3.2.sdk -L/Users/p/Documents/Untitled/build/Release-iphonesimulator -F/Users/p/Documents/Untitled/build/Release-iphonesimulator -filelist /Users/p/Documents/Untitled/build/Untitled.build/Release-iphonesimulator/Untitled.build/Objects-normal/i386/Untitled.LinkFileList -mmacosx-version-min=10.5 -Xlinker -objc_abi_version -Xlinker 2 -framework Foundation -framework UIKit -framework CoreGraphics -o /Users/p/Documents/Untitled/build/Release-iphonesimulator/Untitled.app/Untitled

If we example this output we can determine what sort of flags, commands, and variables are necessary to properly link an iPhone app together.

Additions to our Makefile

# First, we'll just define a convenience variable to hold our directory. This probably # should be changed from pwd in case the Makefile is # executed outside of the directory. DIR:=$(shell pwd) # We are not going to call the linker directly. Instead we will ask gcc to invoke the linker. # This is why some of the flags here look similar to the compilation step. The most importing # thing here, other than calling the linker, is to set isysroot to the Simulator SDK and to # make sure that we are including all the necessary frameworks. If you build off of this app # you will likely need to add additional frameworks here as you add additional features. # # NOTE: these flags (in particular, the framework flags) require the PATH set as per above! LDFLAGS=-arch i386 -mmacosx-version-min=10.6 -Xlinker -objc_abi_version -Xlinker 2 \ -isysroot /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator4.0.sdk \ -L$(DIR) \ -F$(DIR) \ -framework Foundation \ -framework UIKit \ -framework CoreGraphics # This is a standard make substitution command using the SRCS variable that was defined # above. In other words, our compiled objects are the same as the source files, but with # .o extension instead of .m. OBJS := $(SRCS:.m=.o)
5. DeployingThe iPhone Simulator has a local file system where it stores, among other things, the applications. Any valid application copied to this location will show up on the Simulator home screen when you launch the Simulator. There are two locations for applications, one at the user level and one at the "OS" level:

- ~/Library/Application\ Support/iPhone\ Simulator/4.0/Applications/ - ~/Library/Application\ Support/iPhone\ Simulator/User/Applications/

I have come across problems before, for reasons yet unknown, using the "User" directory so I use the "OS" directory. It doesn't really matter so go with whatever works.

Some Makefile additions

# The location of the Simulator. This will be different if you are using a # previous SDK version. SIMULATOR=/Developer/Platforms/iPhoneSimulator.platform/Developer/Applications/iPhone\ Simulator.app # Location where to deploy the application to for the Simulator. APPDIR=~/Library/Application\ Support/iPhone\ Simulator/4.0/Applications/

There is really only one last required step to deploy to the Simulator: setting up the file structure of the app.

Above we examined the file structure of a standard ".app". Our file structure for deployment will be the same with one addition: a parent directory who's name is a UUID. This unique identifier allows one to deploy multiple versions of the same app on a phone while still being able to keep them distinct. There don't seem to be any real rules regarding the generation of this UUID (the formula can accept a value as the seed/hash but it is not necessary). It can remain constant or it can change on every compilation, whichever you prefer (Xcode changes it on every fresh build/deploy).

Fortunately there is a standard function on OSX to generate a UUID:

~/BasicApp > /usr/bin/uuidgen 4663FCE9-0675-432B-8390-1E17D122859C ~/BasicApp >

Since this will be the parent directory, our final file structure for deployment will look like:

(dir) 4663FCE9-0675-432B-8390-1E17D122859C (dir) ... BasicApp.app ... BasicApp ... Info.plist

Copy this to one of the Simulator deployment locations listed above and start the simulator!

6. Putting It All Together (Draft 1) File: Makefile

Below is the first draft of our Makefile. Essentially it includes all of the steps listed above plus a few more to actually make it do something. As long as you have main.m, BasicApp.m, BasicApp.h, and Info.plist all in the same directory as this Makefile, you should be good to go. Just run either make (to build and deploy), make clean (clean out local build files and the app files on the Simulator), or make sim (start the simulator).

Makefile, Draft 1

# Edit this config info below. APPNAME=BasicApp # use /usr/bin/uuidgen to generate a unique UUID for this app UUID=4663FCE9-0675-432B-8390-1E17D122859C SRCS = \ main.m \ BasicApp.m # you shouldn't need to change anything below this line # (unless you need to add frameworks to the linker) ########################################################## PATH=/Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin: \ /Developer/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/local/git/bin: \ /usr/X11/bin:/Developer/usr/bin:/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin MACOSX_DEPLOYMENT_TARGET=10.6 SIMULATOR=/Developer/Platforms/iPhoneSimulator.platform/Developer/Applications/iPhone\ Simulator.app APP_LOC= ~/Library/Application\ Support/iPhone\ Simulator/4.0/Applications/$(UUID) DIR:=$(shell pwd) CC=/Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin/gcc-4.2 CFLAGS=-x objective-c -arch i386 -fmessage-length=0 -pipe -std=c99 -Wno-trigraphs \ -fpascal-strings -fasm-blocks -Os -mdynamic-no-pic -Wreturn-type -Wunused-variable \ -D__IPHONE_OS_VERSION_MIN_REQUIRED=30200 -DNS_BLOCK_ASSERTIONS=1 \ -isysroot /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator4.0.sdk \ -fvisibility=hidden -mmacosx-version-min=10.6 -gdwarf-2 -fobjc-abi-version=2 \ -fobjc-legacy-dispatch LDFLAGS=-arch i386 -mmacosx-version-min=10.6 -Xlinker -objc_abi_version -Xlinker 2 \ -isysroot /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator4.0.sdk \ -L$(DIR) \ -F$(DIR) \ -framework Foundation \ -framework UIKit \ -framework CoreGraphics OBJS:=$(SRCS:.m=.o) ########################################################## # Before we build the application, run some prep commands. all: prep Application # We need to make sure that we create the sub directories to hold our to-be-deployed app. prep: ; @cd $(DIR); \ mkdir -p $(UUID); \ # Create UUID dir and ".app" sub-dir mkdir -p $(UUID)/$(APPNAME).app # Link our application, generate the Info.plist file, and copy the dir to the Simulator. Application: $(OBJS) $(CC) $(LDFLAGS) -o $(UUID)/$(APPNAME).app/$(APPNAME) $^ sed 's/BasicApp/$(APPNAME)/g' Info.plist > $(UUID)/$(APPNAME).app/Info.plist cp -R $(UUID) ~/Library/Application\ Support/iPhone\ Simulator/4.0/Applications/ # Compile our source files. %.o: %.m $(CC) $(CFLAGS) -I. -c $< -o $@ # Launch the simulator. sim: ; open $(SIMULATOR) # Remove all object files, remove the application directory, and remove the app # from the Simulator. clean: rm -rf $(UUID) rm -rf *.o rm -rf $(APP_LOC)
7. Improving NSLog TBD. Note: Check the source code for the beginnings. It's mostly complete (Debug.m/.h) 8. Improving The Makefile TBD. Note: Check the source code for the extra portions of the Makefile. Explanation to follow. 9. Putting It All Together (Draft 2) TBD