Skip to content

Recent Articles

30
Jun

Google Earth Now Available Without Automatic Updates

A while back I blogged about Google Earth’s stealthy, silent automatic update feature. That post continues to be pretty popular, so I wanted to share an update: Google has released a version of Google Earth, including the Web plug-in, that does not include the self-updater. On the download page, the EULA features this paragraph:

(b) Automatic Updates. The Google Earth software may communicate with Google servers from time to time to check for available updates to the software, such as bug fixes, patches, enhanced functions, missing plug-ins and new versions (collectively, “Updates”). By installing the Google Earth software, you agree to automatically request and receive Updates.

However, if you navigate to the advanced setup page, there’s a box you can un-check labeled “Allow Google Earth to automatically install recommended updates.” This box directs you to a separate download that does not include the updater. The EULA, however, does not change when you deselect it. I don’t think that really matters as much in the grand scheme of things, so I’ve really got to give Google some kudos here for listening to systems administrators and concerned users on this one.

18
Jun

WordPress 3.0

I’ve updated the site to WordPress 3.0. So far all is well; I’ll likely create a new post type (excellent new feature) for software once I have more software to be creating posts about.

15
Jun

Updating dyld Shared Caches with Radmind: Best Practices

Similar to my last post about updating kernel extensions, you can run into problems with Radmind due to the dyld shared cache. You may see messages like this in your system log:

current cache invalid because /System/Library/Frameworks/WebKit.framework/Versions/A/WebKit has changed

Running the update_dyld_shared_cache command will fix this, but there’s a better way. Sure, there’s almost no overhead to that command, but where’s the fun in that? Here’s a pre-apply script that will delete any shared caches that have changed, which will then be re-built at reboot.

#!/bin/sh

# update_dyld_caches: Inspects the applicable transcript for something that
#                     might cause a dyld cache to become outdated. If it exists,
#                     delete the cache so it's re-created at startup.

DYLD_CACHE_FOLDER="/private/var/db/dyld"
DYLD_PREFIX="dyld_shared_cache_"
ARCHITECTURES="i386 x86_64 rosetta ppc ppc64"

for arch in ${ARCHITECTURES}; do
	cache="${DYLD_CACHE_FOLDER}/${DYLD_PREFIX}${arch}"
	map="${DYLD_CACHE_FOLDER}/${DYLD_PREFIX}${arch}.map"

	if /bin/test -f "${cache}"; then
		if /bin/test -f "${map}"; then
			/bin/cat "${map}" | grep ^/ | sort --unique --ignore-case | while read line; do
				if /bin/test -n "$(grep ${line} ${1})"; then # found a match
					/bin/rm -f "${cache}"
					/bin/rm -f "${map}"
					break;
				fi
			done
		else
			# Cache exists, but there's no map.
			/bin/rm -f "${cache}"
		fi
	fi
done

27
May

Updating Kernel Extensions with Radmind: Best Practices

One of the problems that I’ve run into so far using Radmind to manage Mac OS X—specifically, the Leopard to Snow Leopard transition—is that kextd helpfully starts recreating your kernel extension cache as soon as you modify anything in /System/Library/Extensions. This can be problematic when you’re updating core system files; as you update the 10.5 kernel extensions to their 10.6 counterparts, you don’t want the 10.5 version of kextd creating a cache of 10.6 kernel extensions, especially as the kernel extension cache has moved (from /System/Library/Extensions.mkext to /System/Library/Caches/com.apple.kext.caches/). So, should you handle this? My solution is to stop kextd if I know that I’m updating kernel extensions; that way, they won’t be re-created until reboot. Here’s the script:

/private/var/radmind/preapply/update_kernel_extensions:

#!/bin/sh

# update_kernel_extensions: Manage the replacement of old kernel extensions.
#                           If there are updates, kill kextd and destroy the
#                           caches.

KEXT_CACHE="/System/Library/Caches/com.apple.kext.caches"
KEXT_FOLDER="/System/Library/Extensions"
KEXTD_LAUNCHD="com.apple.kextd"
SYSTEM_LAUNCHD_FOLDER="/System/Library/LaunchDaemons"

transcript="${1}"
result="${transcript}.$$"

/usr/bin/grep "${KEXT_FOLDER}" "${transcript}" > "${result}"

if test -n "${result}"; then #result is non-empty
    # Disable kextd to prevent it from recreating kernel extension caches, which
    # will be re-created at startup.
    if test -n "$(/bin/launchctl list | /usr/bin/grep ${KEXTD_LAUNCHD})"; then
        #kextd is running
        /bin/launchctl unload "${SYSTEM_LAUNCHD_FOLDER}/${KEXTD_LAUNCHD}.plist"
    fi

    # Remove kernel extension cache.
    /bin/rm -rf "${KEXT_CACHE}"
fi

rm -f "${result}"

In its present form, it only works on Snow Leopard, but I’ll be updating it to work on Leopard as well.

10
May

Take Me Home Now Free

Take Me Home is an ancient, buggy iPhone app that was my “Hello, World!” in the App Store. Its sale rate has plummeted to only a couple of buyers a week and, with the prevalence of real, honest-to-God GPS apps in the store these days, its usefulness is questionable. So from here on out, it’s free. I can’t promise to continue supporting it—especially for new devices and APIs—so the best I can do may be to remove it from the store in the event that some update breaks it.

Enjoy!

10
May

Quick Tip: Don’t Do This

I could not find out where a bug was coming from for the life of me today. Naturally it one of those “assignment instead of equality” bugs that seem to crop up when trying to code too quickly. The difference here was that I had implemented a subclass of NSObject that reimplemented the method +resolveInstanceMethod:. So, the code went like this:

+ ( BOOL )resolveInstanceMethod:( SEL )sel
{
    if ( sel = @selector( setFoobar: )) {
        return class_addMethod([ self class ], sel, ( IMP )setFB, "v@:@" );
    } else if ( sel = @selector( foobar )) {
        return class_addMethod([ self class ], sel, ( IMP )getFB, "@@:" );
    } else {
        return [ super resolveInstanceMethod:sel ];
    }
}


For those of you keeping score at home, when the Objective-C runtime tried to resolve any selector, this method happily added a selector for -setFoobar: to self’s class and returned YES.

Don’t do this.

9
May

Big Nerd Ranch

Big Nerd Ranch Cabin

My cabin at the Big Nerd Ranch.

Hopefully, I will continue to improve the content of this site in the months to come. To that end—among others—I’m at the Big Nerd Ranch in Atlanta, Georgia for their Advanced Mac OS X Programming course this week. I’m very excited and hope to learn as much as I can possibly fit into my brain.

30
Mar

Using AppleScript to Automate an iChat Video Chat

I had to write a script to automatically start iChat at login and start a video chat with a specified screenname. I wanted to only start the chat if the user was online and available, and quit iChat on an error or if the chat ended. So here’s the script I have:

using terms from application "iChat"
	tell application "iChat"
		activate
		log in
		delay 5
		set theBuddy to buddy "ScreennameGoesHere" of service "AIM"
		try
			set theStatus to status of theBuddy
		on error errmesg number errno
			set message to display dialog "The user is currently unavailable." buttons {"OK"}
			quit
			return
		end try
		if theStatus is available then
			set theCapabilities to get capabilities of theBuddy
			if (theCapabilities contains multiperson video) then
				send "A user is attempting to contact you." to theBuddy
				delay 2
				tell service "AIM" to make video chat with properties {participants:theBuddy}
				set theChat to result
				delay 30
				try
					set theStatus to the av connection status of theChat
				on error errmesg number errno
					quit
					return
				end try
				repeat while theStatus is not ended
					delay 5
					try
						set theStatus to the av connection status of theChat
					on error errmesg number errno
						quit
						return
					end try
				end repeat
				quit
				return
			else
				set message to display dialog "The user cannot video chat at this time. Please try again later." buttons {"OK"}
				quit
				return
			end if
		else
			set message to display dialog "The user is currently unavailable." buttons {"OK"}
			quit
			return
		end if
	end tell
end using terms from

A couple of gotchas:

  • I tried using video chat instead of multiperson video, but that always returned false. I don’t know why.
  • Once the video chat has ended, you can’t poll its status (hence the try block).
9
Mar

New iPhone Project: uWarranty

So, I created a new iPhone application called uWarranty. It used an unpublished Apple API for warranty status (from selfsolve.apple.com), and so I got this after submitting it:

Thank you for submitting your application to the App Store. Unfortunately, your application, uWarranty, cannot be added to the App Store because it violates section 3.3.7 of the iPhone Developer Program License Agreement:

“Applications may not perform any functions or link to any content or use any robot, spider, site search or other retrieval application or device to scrape, retrieve or index services provided by Apple or its licensors, or to collect, disseminate or use information about users for any unauthorized purpose.”

OK, I get it. That’s fine and is Apple’s prerogative. But now I have this program and all the development time that went into it. So what do I do? I guess the answer is to open-source it, just like AppSales Mobile. Watch this space for more details as I clean up the code and throw it up on a public repository somewhere.

26
Feb

Cocoa Touch Tutorial: Stripping Non-Alphanumeric Characters on Entry in a UITextField

In a previous post, I showed you how to trim non-alphanumeric characters from a string. Here I’ll go more in-depth and show a method that I wrote to restrict text entry in a UITextField to alphanumeric characters. Since I also wanted the characters to be uppercase, I’ll also ensure that only uppercase characters are allowed.

This should all happen in the - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string method of your UITextField’s delegate (which, of course, must implement the UITextFieldDelegate protocol). I’ve implemented it as follows:

- ( BOOL )textField:( UITextField * )textField
shouldChangeCharactersInRange:( NSRange )range
  replacementString:( NSString * )string
{
    /*
     * We only want uppercase letters and numbers in this text field, so if
     * this method is adding something else, we don't want it. But we also
     * want to support copy-and-paste, so it's not always going to be one
     * character added.
     */
    BOOL shouldAllowChange = YES;

The shouldAllowChange variable is set to YES initially because we want to allow this change when possible. The method will test the string to see if it meets criteria for rejection as we move forward.

    NSMutableString *newReplacement =
    [[ NSMutableString alloc ] initWithString:[ string uppercaseString ]];

    if ( ! [ string isEqualToString:newReplacement ]) {
        shouldAllowChange = NO;
    }

First, we define newReplacement. It’s an NSMutableString so that if we discover non-alphanumeric characters in it, we can remove them on-the-fly. It also serves as a convenient string against which we can test to see if string is already uppercase.

    NSCharacterSet *desiredCharacters =
    [ NSCharacterSet alphanumericCharacterSet ];

    for ( NSUInteger i = 0; i < [ newReplacement length ]; i++ ) {
        unichar currentCharacter = [ newReplacement characterAtIndex:i ];

        if ( ! [ desiredCharacters characterIsMember:currentCharacter ]) {
            shouldAllowChange = NO;
            [ newReplacement deleteCharactersInRange:NSMakeRange( i, 1 )];
            i--;
        }
    }

In this section, we define the NSCharacterSet that we want to work with - in this case, the alphanumeric character set. We go through one character by a time and if the current character isn’t alphanumeric, we remove it from the NSMutableString (decrementing i so that we don’t inadvertently skip a character) and set our shouldAllowChange flag accordingly.

    if ( shouldAllowChange ) {
        [ newReplacement release ];
        return YES;
    } else {
        [ textField setText:[[ textField text ]
                             stringByReplacingCharactersInRange:range
                             withString:newReplacement ]];
        [ newReplacement release ];
        return NO;
    }
}

To finish, if shouldAllowChange is still true, we return YES and allow the replacement characters to be added. Otherwise, we return NO, but not before using our replacement replacement string (say that ten times fast) to manually edit the text field’s text. The end result is a text field that will consist only of uppercase letters and numbers.