solving little problems

3,968 views
3,571 views

Published on

Little things that I've had to solve as I was developing my first iPhone application.

Published in: Technology
2 Comments
6 Likes
Statistics
Notes
No Downloads
Views
Total views
3,968
On SlideShare
0
From Embeds
0
Number of Embeds
49
Actions
Shares
0
Downloads
107
Comments
2
Likes
6
Embeds 0
No embeds

No notes for slide
  • This past October and November, I developed my first production iPhone app. I developed it on contract for a non-developer designer who wanted to use his own developer account.

    This obviously presents some challenges. It’s also only my second Objective-C program. So, what can I learn while I’m doing this?
  • http://cocoawithlove.com/2008/03/core-data-one-line-fetch.html
  • http://cocoawithlove.com/2008/03/core-data-one-line-fetch.html
  • solving little problems

    1. solving little problems
    2. My First iPhone App On contract… for non-developer… who wants to use his own developer account. Presents some challenges. Only my second Objective-C program… what can I learn?
    3. Huh? On app store now (yay!). Simple idea—audio translation of silly phrases. Moderate dataset (4 languages, 202 phrases, 808 audio files). May expand later with StoreKit.
    4. Four Things Core Data Slow Startup and Progress Debug Archiving Build Numbering with git
    5. 1. Core Data Used for language/phrase reference information for easy update. Slightly confusing since I know SQL well. Lots of work to set up queries. Matt Gallagher (Cocoa with Love) showed code that helps.
    6. Core Data: one line fetch I adopted and extended this code as a category now on GitHub: github.com/halostatue/ coredata-easyfetch
    7. Apple’s Example NSManagedObjectContext *moc = [self managedObjectContext]; NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"Employee" inManagedObjectContext:moc]; NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease]; [request setEntity:entityDescription]; // Set example predicate and sort orderings... NSNumber *minimumSalary = ...; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(lastName LIKE[c] 'Worsley') AND (salary > %@)", minimumSalary]; [request setPredicate:predicate]; NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"firstName" ascending:YES]; [request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]]; [sortDescriptor release]; NSError *error = nil; NSArray *array = [moc executeFetchRequest:request error:&error]; if (array == nil) { // Deal with error... }
    8. Apple’s Example NSManagedObjectContext *moc = [self managedObjectContext]; NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"Employee" inManagedObjectContext:moc]; 12+ NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease]; [request setEntity:entityDescription]; // Set example predicate and sort orderings... NSNumber *minimumSalary = ...; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(lastName LIKE[c] 'Worsley') AND (salary > %@)", minimumSalary]; [request setPredicate:predicate]; NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"firstName" ascending:YES]; Statements! [request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]]; [sortDescriptor release]; NSError *error = nil; NSArray *array = [moc executeFetchRequest:request error:&error]; if (array == nil) { // Deal with error... }
    9. Matt’s Example [[self managedObjectContext] fetchObjectsForEntityName:@"Employee" withPredicate:@"(lastName LIKE[c] 'Worsley') AND (salary > %@)", minimumSalary]; 1 Statement!
    10. coredata-easyfetch #import “NSManagedObjectContext-EasyFetch.h” … [[self managedObjectContext] fetchObjectsForEntityName:@"Employee" predicateWithFormat:@"(lastName LIKE[c] 'Worsley') AND (salary > %@)", minimumSalary]; Made a category so it’s easy All objects, unsorted (1) to reuse. All objects, sorted (2) Refactored easy filtering. Some objects, unsorted (2) Added easy sorting. Some objects, sorted (4)
    11. How I Use It + (NSMutableArray*)fetchAllFromManagedObjectContext:(NSManagedObjectContext*)moc { return [[[moc fetchObjectsForEntityName:@"DataVersion"] mutableCopy] autorelease]; } + (NSMutableArray*)fetchAllFromManagedObjectContext:(NSManagedObjectContext*)moc { NSArray* fetched = [moc fetchObjectsForEntityName:@"Language"]; NSMutableArray* result = [fetched mutableCopy]; [result release]; return result; } + (NSMutableArray*)fetchAllFromManagedObjectContext:(NSManagedObjectContext*)moc { NSArray* fetched = [moc fetchObjectsForEntityName:@"Phrase" sortByKey:@"phraseId" ascending:YES]; NSMutableArray* result = [fetched mutableCopy]; [result release]; return result; }
    12. 2. Progress Original plan: Copy all media files from bundle to document area. Problem: Slow (~20–30 seconds) Solution: Show a progress monitor.
    13. MBProgressHUD
    14. MBProgressHUD Created in April 2009 Matej Bukovinski http://github.com/matej/ MBProgressHUD/ Other options exist, such as DSActivityView http://www.dejal.com/ developer/dsactivityview
    15. Beautiful, but… …the progress HUD blocked the application logo …immediately on launch. Request: Move the HUD to a different spot, and don’t show it unless the work takes 2 seconds or more.
    16. Easy enough… Added x- and y-offsets Added a delay-before-showing Later changed by Matej to be a “grace time” Pushed back to master and MBProgressHUD is reasonably active for a little project. My version is at github.com/ halostatue/MBProgressHUD
    17. 3. Save the dSYM Had crashes on the client’s iPhone that I couldn’t reproduce. Hadn’t been saving my dSYM bundles…so I couldn’t symbolicate the logs.
    18. Symboliwhat?
    19. Again, with feeling… Craig Hockenberry: http://furbo.org/2008/08/08/ symbolicatifination/ Crash logs have address offsets; if you have the dSYM bundles, you can “symbolicate” to restore method names.
    20. Simple script… # http://furbo.org/stuff/dSYM_Archive.txt ARCHIVE_ROOT="$PROJECT_DIR/dSYM_Archive" if [ $SDK_NAME = "iphoneos2.0" ]; then echo "Archiving $CONFIGURATION in $ARCHIVE_ROOT at `date`" >> /tmp/dSYM_Archive.out if [ $CONFIGURATION = "Distribution-Free" -o $CONFIGURATION = "Distribution-Paid" -o $CONFIGURATION = "Distribution-Beta-Free" ]; then ARCHIVE_CONFIG_FOLDER="$ARCHIVE_ROOT/$CONFIGURATION" mkdir -p -v "$ARCHIVE_CONFIG_FOLDER" >> /tmp/dSYM_Archive.out ARCHIVE_FOLDER="$ARCHIVE_CONFIG_FOLDER/`date "+%Y-%m-%d-%H%M%S"`" echo "Copying $DWARF_DSYM_FOLDER_PATH to $ARCHIVE_FOLDER" >> /tmp/dSYM_Archive.out cp -R $DWARF_DSYM_FOLDER_PATH $ARCHIVE_FOLDER >> /tmp/dSYM_Archive.out fi fi
    21. My script… #!/bin/bash function prepare_adhoc_package() { if [ ${#} -eq 1 ]; then BASE_NAME="${1}" else BASE_NAME="${PROJECT}" fi PAYLOAD_FOLDER="${ARCHIVE_FOLDER}/Payload" mkdir -p "${PAYLOAD_FOLDER}" if [ -d "${ARCHIVE_FOLDER}/${BASE_NAME}.app" ]; then cp -rp "${ARCHIVE_FOLDER}/${BASE_NAME}.app" "${PAYLOAD_FOLDER}" cp "${ARCHIVE_FOLDER}/${BASE_NAME}.app/iTunesArtwork" "${ARCHIVE_FOLDER}" (cd "${ARCHIVE_FOLDER}"; zip -qr "${BASE_NAME}.ipa" iTunesArtwork Payload) else for app in "${ARCHIVE_FOLDER}/*.app"; do cp -rp "${app}" "${PAYLOAD_FOLDER}" cp -rp "${app}/iTunesArtwork" "${ARCHIVE_FOLDER}" done (cd "${ARCHIVE_FOLDER}"; zip -qr "${BASE_NAME}.ipa" iTunesArtwork Payload) fi rm -rf "${ARCHIVE_FOLDER}/iTunesArtwork" "${PAYLOAD_FOLDER}" open "${ARCHIVE_FOLDER}" } function archive_dsym_iphoneos() { case ${CONFIGURATION} in *Distribution*) : ;; *) return ;; esac ARCHIVE_ROOT="${HOME}/projects/dSYMArchive/${PROJECT_NAME}" ARCHIVE_DATE=`date +"%Y%m%d-%H%M%S"` echo "Archiving ${CONFIGURATION} in ${ARCHIVE_ROOT} at ${ARCHIVE_DATE}" >> /tmp/archive_dsym.out ARCHIVE_CONFIG_FOLDER="${ARCHIVE_ROOT}/${CONFIGURATION}" mkdir -p -v "${ARCHIVE_CONFIG_FOLDER}" >> /tmp/archive_dsym.out ARCHIVE_FOLDER="${ARCHIVE_CONFIG_FOLDER}/${ARCHIVE_DATE}" echo "Copying ${DWARF_DSYM_FOLDER_PATH} to ${ARCHIVE_FOLDER}" >> /tmp/archive_dsym.out cp -Rp ${DWARF_DSYM_FOLDER_PATH} ${ARCHIVE_FOLDER} >> /tmp/archive_dsym.out case ${CONFIGURATION} in *[Bb]eta*) prepare_adhoc_package "${1}" ;; *) return ;; esac } case @${SDK_NAME} in @iphoneos*) archive_dsym_iphoneos "${1}" ;; @*) : ;; @) echo "Not running under Xcode." ;; esac
    22. My script… #!/bin/bash function prepare_adhoc_package() { if [ ${#} -eq 1 ]; then BASE_NAME="${1}" else BASE_NAME="${PROJECT}" fi PAYLOAD_FOLDER="${ARCHIVE_FOLDER}/Payload" mkdir -p "${PAYLOAD_FOLDER}" if [ -d "${ARCHIVE_FOLDER}/${BASE_NAME}.app" ]; then cp -rp "${ARCHIVE_FOLDER}/${BASE_NAME}.app" "${PAYLOAD_FOLDER}" cp "${ARCHIVE_FOLDER}/${BASE_NAME}.app/iTunesArtwork" "${ARCHIVE_FOLDER}" (cd "${ARCHIVE_FOLDER}"; zip -qr "${BASE_NAME}.ipa" iTunesArtwork Payload) else for app in "${ARCHIVE_FOLDER}/*.app"; do cp -rp "${app}" "${PAYLOAD_FOLDER}" You can read that, can’t you? cp -rp "${app}/iTunesArtwork" "${ARCHIVE_FOLDER}" done (cd "${ARCHIVE_FOLDER}"; zip -qr "${BASE_NAME}.ipa" iTunesArtwork Payload) fi rm -rf "${ARCHIVE_FOLDER}/iTunesArtwork" "${PAYLOAD_FOLDER}" open "${ARCHIVE_FOLDER}" } function archive_dsym_iphoneos() { case ${CONFIGURATION} in *Distribution*) : ;; *) return ;; esac ARCHIVE_ROOT="${HOME}/projects/dSYMArchive/${PROJECT_NAME}" ARCHIVE_DATE=`date +"%Y%m%d-%H%M%S"` echo "Archiving ${CONFIGURATION} in ${ARCHIVE_ROOT} at ${ARCHIVE_DATE}" >> /tmp/archive_dsym.out ARCHIVE_CONFIG_FOLDER="${ARCHIVE_ROOT}/${CONFIGURATION}" mkdir -p -v "${ARCHIVE_CONFIG_FOLDER}" >> /tmp/archive_dsym.out ARCHIVE_FOLDER="${ARCHIVE_CONFIG_FOLDER}/${ARCHIVE_DATE}" echo "Copying ${DWARF_DSYM_FOLDER_PATH} to ${ARCHIVE_FOLDER}" >> /tmp/archive_dsym.out cp -Rp ${DWARF_DSYM_FOLDER_PATH} ${ARCHIVE_FOLDER} >> /tmp/archive_dsym.out case ${CONFIGURATION} in *[Bb]eta*) prepare_adhoc_package "${1}" ;; *) return ;; esac } case @${SDK_NAME} in @iphoneos*) archive_dsym_iphoneos "${1}" ;; @*) : ;; @) echo "Not running under Xcode." ;; esac
    23. dsym-archiver http://github.com/halostatue/ dsym-archiver Main features: Build adhoc packages. Archives dSYM bundles.
    24. One gotcha… Should be done on every releasable build. Needs to run after code signing for an ad-hoc package to be usable. Create a wrapper project; make the original iPhone target a dependent project. Add a custom build step that runs dsym-archiver at the end.
    25. 4. git Build Stamps Good idea to have build stamps in CFBundleVersion for error reporting. svn uses repository revision number Updated on every check- in. git doesn’t have a repository revision number.
    26. CIMGF to the Rescue? Marcus Zarra wrote a Perl script to use the short git commit sharef in April 2008. Uses regex replacement. Released before the iPhone SDK. I prefer Ruby.
    27. My Rewrite Rewrote the basic script to use Ruby with RubyCocoa. I’m too lazy to compile MacRuby. Info.plist loaded with dictionaryWithContentsOfFile. Formalized a version class.
    28. iTunes Connect‽ The git SHA reference has problems on iPhone projects: Non-contiguous, confuses iTunes old/new for ad hoc builds. iTunes Connect wants only incrementing dotted integers.
    29. git Tags git doesn’t have incrementing revisions We can fake it with tags on the commits. Downside is that repeated builds on the same commit means a lot of tags.
    30. build-number.rb (1/2) #!/usr/bin/ruby require 'osx/cocoa' … # Full implementation at http://github.com/halostatue/xcode-git-version/ class ProgramVersion attr_accessor :major, :minor, :patch, :build, :ref, :ref_type def <=>(other); …; end def self.parse_version(version_string); …; end def short_version; …; end def long_version; …; end def increment_build!; …; end end
    31. build-number.rb (2/2) # Load the plist info_plist = OSX::NSDictionary.dictionaryWithContentsOfFile(path).mutableCopy # Get the current HEAD current_head = %x(git describe --always).chomp # Get the list of build tags and turn them into version numbers. build_tags = `git tag -l build-* --contains #{current_head}`.chomp.split($/) build_tags.map! { |tag| ProgramVersion.parse_version(tag.sub(/^build-/, '')) } # Get the CFBundleVersion from the plist. old_version = ProgramVersion.parse_version(info_plist['CFBundleVersion']) # Add the old version to the list of build tags. build_tags << old_version.dup # Get the largest version we know about for this head. new_version = build_tags.max # Increment the build number new_version.increment_build! puts "Version modified: #{old_version} -> #{new_version}" # Set the long version (M.m.p.b) info_plist['CFBundleVersion'] = new_version.long_version # Set the short version (M.m or M.m.p). info_plist['CFBundleShortVersionString'] = new_version.short_version # Write the file. info_plist.writeToFile_atomically(path, 1) # Tag the version `git tag build-#{new_version.long_version}`

    ×