200 Million iPads Update
Back in September, I estimated that Apple would sell 200 million iPads by the end of 2012. It looks like I was a little too optimistic.
Apple just reported stellar quarterly results. However, iPad sales growth was not quite as strong as Apple’s initial sales seemed to indicate. Two years after introduction, the iPad is selling better than the iPhone did two years after its debut. But it looks like it may take one extra quarter for Apple to hit 200 million iPads.
Actual Results
- Apr to Jun 2010: 3.27 million
- Jul to Sep 2010: 4.19 million
- Oct to Dec 2010: 7.33 million
- Jan to Mar 2011: 4.69 million
- Apr to Jun 2011: 9.25 million
- Jul to Sep 2011: 11.1 million
- Oct to Dec 2011: 15.4 million
Estimated Results
- Jan to Mar 2012: 11.8 million
- Apr to Jun 2012: 23.4 million
- Jul to Sep 2012: 28.0 million
- Oct to Dec 2012: 38.9 million
- Jan to Mar 2013: 29.9 million
Disclosure: I own AAPL stock.
The First 100 Days of a Startup
Josh Coates, founder of Mozy and current CEO of Instructure, once taught a series of classes at BYU on high-tech startups. I jumped at the chance to audit his class.
One of the things Josh covered was what should happen during the first 100 days (14 weeks) of a high-tech startup.
Week 1 — Research and choose business
Week 2 — Build financial model and development plan
Week 3 — Build pitch with screenshots and practice
Week 4 — Interview law firm, staff and advisers
Week 5 — Incorporate and setup shop with office space and equipment
Week 6 — Initial documents, books, hires and cap table
Week 7 — Create website and logo (do a trademark search)
Week 8 — Identify 10 to 20 potential investors and study who else they invest in
Week 9 — Practice the pitch and setup meeting with the least important investor
Week 10 — Interview, build product, pitch again
Week 11 — Interview, build product, pitch again
Week 12 — Interview, build product, pitch again
Week 13 — Interview, build product, pitch again
Week 14 — Interview, build product, pitch again
He recommended interviewing one potential employee every day. Pitching to the least important investor first lets you have a chance to practice in a situation where making a mistake isn’t as damaging.
I first met Josh just after publication of an article he wrote on how many angel investors in Utah were doing it wrong. The article, entitled “Poison in the Well”, in addition to having a great title, was direct and clear in its criticism. It was one of the reasons I later applied to work at Mozy.
For anyone who knows him, I think “direct and clear criticism” is a good phrase to describe what it’s like to work for Josh. His class was no exception. It was a great chance to learn from someone who’s been there and done it successfully.
200 Million iPads
About a month ago, I mentioned in a meeting at work (and later tweeted) that I believed Apple would sell about 200 million iPads by the end of 2012. Brief awkward silence. Then I was asked to send out the details of how I’d arrived at that number.
Honestly, at that point, I’d just eyeballed the sales trends. As I opened up a spreadsheet, I really hoped my estimate would hold up. :)
The first iPad went on sale on April 3, 2010. Here are Apple’s official iPad sales numbers:
- Apr to Jun 2010: 3.27 million
- Jul to Sep 2010: 4.19 million
- Oct to Dec 2010: 7.33 million
- Jan to Mar 2011: 4.69 million
- Apr to Jun 2011: 9.25 million
At this point, we only have one year-over-year growth number for comparison. That’s fairly weak evidence on which to predict the future. But this is just for fun and games, right?
Assuming that the growth rate remains consistent, here is what iPad sales will look like:
- Jul to Sep 2011: 11.8 million (actual result was 11.1 million)
- Oct to Dec 2011: 20.7 million (actual result was 15.4 million)
- Jan to Mar 2012: 13.2 million
- Apr to Jun 2012: 26.1 million
- Jul to Sep 2012: 33.3 million
- Oct to Dec 2012: 58.4 million
192 million iPads by the end of 2012. Apple is selling every iPad they can make so these numbers may be limited by Apple’s ability to meet demand.
Put another way, here are the yearly totals:
- 2010: 14.8 million
- 2011: 46.4 million (actual result was 40.1 million)
- 2012: 131.0 million
That’s the beginning of a very fast growth curve. There are a few others who agree.
Apple will report their next financial results in mid-October. Then we’ll have two data points.
Disclosure: I now own some AAPL stock.
Free DNS Hosting Performance Tests
Way back in April 2009, I read two articles about using Pingdom to test hosted DNS services. Pingdom has a limited free account, includes several types of web-related performance tests and has test servers in over 25 locations throughout North America and Europe. I started using it immediately and have been very pleased with their product and service.
I’ve been wanting to test the performance of hosted DNS services that include at least one domain for free. Two years later, I finally got around to it. All tests were done with my own domain name.
Each test covered eight days (except Afraid, which I ended early after six). I would have preferred to run the tests concurrently over a longer period of time. If I missed anyone, please let me know and I’ll add them to the list.
| Name | Uptime | Response | Std Dev | Notes |
|---|---|---|---|---|
| Namecheap | 100.00% | 82 ms | 175 ms | Includes free email forwarding. |
| ClouDNS | 100.00% | 93 ms | 319 ms | Excellent user interface. |
| Moniker | 100.00% | 114 ms | 248 ms | *** Included with domain registration. *** |
| ZoneEdit | 99.65% | 91 ms | 280 ms | |
| GeoScaling | 99.78% | 103 ms | 332 ms | |
| XName | 100.00% | 117 ms | 325 ms | |
| DNS Exit | 99.98% | 256 ms | 1025 ms | |
| FreeDNS | 100.00% | 187 ms | 530 ms | Website down for days recently. No comment on why. |
| Afraid | 80.45% | 223 ms | 1022 ms | Sub-domain sharing. Down 4 mins every 19. |
I recommend Namecheap and ClouDNS. If you are willing to pay for DNS services, there may be faster and more reliable options. I’d love to try out easyDNS and DynDNS but neither have a free option.
UPDATE May 2011: Namecheap’s DNS service has recently experienced two outages: one due to a denial of service attack and the other caused by a misconfigured DNS entry at another registrar. Their service is relatively new, so I hope they handle these types of issues better in the future. I appreciate the honesty and openness with which they have reported these issues. ClouDNS is also experiencing intermittent downtime, but has not mentioned anything. END UPDATE
Here are the charts to give you a visual idea of the test results. Pingdom needs to allow customers to control the vertical scale. I’m just too lazy to make my own charts using their cool API. :)
Namecheap
ClouDNS
Moniker
ZoneEdit
GeoScaling
XName
DNS Exit
FreeDNS
Afraid
How to Modify the Dock or Login Items on OS X
If you need to programmatically add or remove applications from the user’s Dock or Login Items, you’ll find that there isn’t a standard way to do both that works on OS X 10.4 and later systems.
Apple provided some early sample code for handling login items called LoginItemsAE. The code is complicated and does not work consistently for me.
CocoaDev has a page that talks about starting an application on startup. I agree that launchd user agents do not work well on 10.4.
If you no longer need to support 10.4, you can use the LSSharedFileList API that Apple introduced in 10.5 to manage Login Items. Information on that API does not yet appear in the official documentation, but you can find example code here and here.
As far as I know, Apple still does not provide a way to easily manage Dock items. I believe this is by design. The Dock is supposed to controlled by the user. However, I’ve found that users sometimes want an application to add or remove itself from the Dock for convenience.
Login Items
This code uses NSUserDefaults to modify the settings files for the user’s Login Items. If the System Preferences application is open, it must be closed and reopened to see the changes.
@implementation NSUserDefaults (Additions)
- (BOOL)addApplicationToLoginItems:(NSString *)path {
NSDictionary *domain = [self persistentDomainForName:@"loginwindow"];
NSArray *apps = [domain objectForKey:@"AutoLaunchedApplicationDictionary"];
NSArray *matchingApps = [apps filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"Path CONTAINS %@", path]];
if ([matchingApps count] == 0) {
NSMutableDictionary *newDomain = [domain mutableCopy];
NSMutableArray *newApps = [[apps mutableCopy] autorelease];
NSDictionary *app = [NSDictionary dictionaryWithObjectsAndKeys:path, @"Path", [NSNumber numberWithBool:NO], @"Hide", nil];
[newApps addObject:app];
[newDomain setObject:newApps forKey:@"AutoLaunchedApplicationDictionary"];
[self setPersistentDomain:newDomain forName:@"loginwindow"];
return [self synchronize];
}
return NO;
}
- (BOOL)removeApplicationFromLoginItems:(NSString *)name {
NSDictionary *domain = [self persistentDomainForName:@"loginwindow"];
NSArray *apps = [domain objectForKey:@"AutoLaunchedApplicationDictionary"];
NSArray *newApps = [apps filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"not Path CONTAINS %@", name]];
if (![apps isEqualToArray:newApps]) {
NSMutableDictionary *newDomain = [domain mutableCopy];
[newDomain setObject:newApps forKey:@"AutoLaunchedApplicationDictionary"];
[self setPersistentDomain:newDomain forName:@"loginwindow"];
return [self synchronize];
}
return NO;
}
@end
Dock
The code for adding applications to and removing them from the user’s Dock is very similar. The differences include the domain, key, predicate and dictionary entry.
This code uses NSUserDefaults to modify the settings files for the user’s Dock. The Dock must also be restarted after making a change. The command “killall Dock” works, either programmatically or from the Terminal.
@implementation NSUserDefaults (Additions)
- (BOOL)addApplicationToDock:(NSString *)path {
NSDictionary *domain = [self persistentDomainForName:@"com.apple.dock"];
NSArray *apps = [domain objectForKey:@"persistent-apps"];
NSArray *matchingApps = [apps filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"%K CONTAINS %@", @"tile-data.file-data._CFURLString", path]];
if ([matchingApps count] == 0) {
NSMutableDictionary *newDomain = [domain mutableCopy];
NSMutableArray *newApps = [[apps mutableCopy] autorelease];
NSDictionary *app = [NSDictionary dictionaryWithObject:[NSDictionary dictionaryWithObject:[NSDictionary dictionaryWithObjectsAndKeys:path, @"_CFURLString", [NSNumber numberWithInt:0], @"_CFURLStringType", nil] forKey:@"file-data"] forKey:@"tile-data"];
[newApps addObject:app];
[newDomain setObject:newApps forKey:@"persistent-apps"];
[self setPersistentDomain:newDomain forName:@"com.apple.dock"];
return [self synchronize];
}
return NO;
}
- (BOOL)removeApplicationFromDock:(NSString *)name {
NSDictionary *domain = [self persistentDomainForName:@"com.apple.dock"];
NSArray *apps = [domain objectForKey:@"persistent-apps"];
NSArray *newApps = [apps filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"not %K CONTAINS %@", @"tile-data.file-data._CFURLString", name]];
if (![apps isEqualToArray:newApps]) {
NSMutableDictionary *newDomain = [domain mutableCopy];
[newDomain setObject:newApps forKey:@"persistent-apps"];
[self setPersistentDomain:newDomain forName:@"com.apple.dock"];
return [self synchronize];
}
return NO;
}
@end
If you need to manage both the Dock and the user’s Login Items. You can put all four methods into one category.
I consider this code to be in the public domain. Please feel free to copy and paste. And let me know if you find any problems or have suggestions.
Original Inspiration for the iPhone Maps Application
I am 100% sure that this Dilbert comic from 1990 was Steve Jobs’ original inspiration for creating the iPhone with its famous Maps application:
You knew that Dilbert was over 21 years old, right? (I didn’t. :)
How to Launch a Privileged Process on OS X
For security reasons, Apple recommends that GUI applications should never run with the privileges of the root user. GUI applications normally load several types of plugins and input managers automatically. If a malicious plugin was installed then it could cause security problems when the privileged application was launched.
In addition, system services that run with the privileges of the root user (such as launch daemons) need to avoid using certain technology frameworks provided by Apple. These frameworks are not safe to use in a service that runs with the privileges of the root user.
If you need your GUI application to do something that requires root privileges, Apple recommends you split your application into two parts. First, create a GUI that runs as a normal user. Then when you need to do something with root privileges, you launch a separate helper process or tool. Splitting your application avoids security holes while keeping things very easy for your users.
Two Steps
Launching a privileged process is done in two steps:
1) Request authorization. The operating system will ask the user for permission to run a privileged process. The user will need to enter an administrator’s username and password.
#import <Security/Security.h>
OSStatus PreauthorizePrivilegedProcess(AuthorizationRef *authRef) {
AuthorizationItem item = { kAuthorizationRightExecute, 0, NULL, 0 };
AuthorizationRights rights = { 1, &item };
AuthorizationFlags flags = kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights | kAuthorizationFlagPreAuthorize;
return AuthorizationCreate(&rights, kAuthorizationEmptyEnvironment, flags, authRef);
}
2) Launch the process. If the user authenticates correctly, you can use the authorization reference created above to launch the helper process.
OSStatus LaunchPreauthorizedProcess(AuthorizationRef *authRef, NSString *path) {
OSStatus status = AuthorizationExecuteWithPrivileges(*authRef, [path UTF8String], kAuthorizationFlagDefaults, NULL, NULL);
AuthorizationFree(*authRef, kAuthorizationFlagDestroyRights);
return status;
}
Please note that the authorization reference created in the first function is released in the second function. If you want to launch several privileged processes in a short amount of time, you can comment out this line and and release the reference on your own afterwards.
Some of Apple’s sample code (and other examples) has an extra step where they copy the authorization reference. This is only necessary if you have a previously created authorization reference that you want to add elevated privileges to.
Just One Step
If you don’t need to update your GUI between these two steps, then both actions can be combined into one step.
#import <Security/Security.h>
OSStatus LaunchPrivilegedProcess(NSString *path) {
AuthorizationRef authRef;
OSStatus status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authRef);
if (status == errAuthorizationSuccess) {
status = AuthorizationExecuteWithPrivileges(authRef, [path UTF8String], kAuthorizationFlagDefaults, NULL, NULL);
AuthorizationFree(authRef, kAuthorizationFlagDestroyRights);
}
return status;
}
I consider this code to be in the public domain. Please feel free to copy and paste. And let me know if you find any problems or have suggestions.
How To Type Curly Quotes In Mac OS X
Mac OS X has an easy way to type “curly” quotes and apostrophes instead of "straight" versions. I used both versions in that sentence to show the difference. Here is a bigger version to make the distinction more visible:
Many people think “curly” quotes look better than "straight" ones.
You can use the following keyboard shortcuts to type a single or double curly quote:
- Single quote open (‘) — option ]
- Single quote close (’) — shift option ]
- Double quote open (“) — option [
- Double quote close (”) — shift option [
However, I think it makes more sense to use [ and ] for open and close versions instead of the shift key. I found myself constant typing “mismatched‘ quotes. I also wanted to use the shift key for double quotes since that’s how the normal keyboard button works.
- Single quote open (‘) — option [
- Single quote close (’) — option ]
- Double quote open (“) — option shift [
- Double quote close (”) — option shift ]
Since OS X supports custom key bindings, I looked for a way to fix this. The trick is to create a file called DefaultKeyBinding.dict in the KeyBindings folder inside your Library folder. You can use this file to override the default key bindings for most applications.
Here are my changes. Please feel free to copy the settings below and save them to your own computer. You may need to create the KeyBindings folder if it isn’t already there.
/*
Updates Apple's default keybindings for curly quotes.
See http://www.danandcheryl.com/1072
Save this file here:
/Users/<name>/Library/KeyBindings/DefaultKeyBinding.dict
*/
{
"~[" = ("insertText:", "‘");
"~]" = ("insertText:", "’");
"~{" = ("insertText:", "“");
"~}" = ("insertText:", "”");
}
How to Check the System Idle Time Using Cocoa
There is sample code on the Internet for programmatically checking the system idle time using IOKit and Cocoa (see here, for example). However, most of the examples seem overly long (see Paul Graham’s Succinctness is Power). The code below works in Tiger/10.4 and later and is about as concise as I can make it while still handling errors properly.
#include <IOKit/IOKitLib.h>
/**
Returns the number of seconds the machine has been idle or -1 if an error occurs.
The code is compatible with Tiger/10.4 and later (but not iOS).
*/
int64_t SystemIdleTime(void) {
int64_t idlesecs = -1;
io_iterator_t iter = 0;
if (IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching("IOHIDSystem"), &iter) == KERN_SUCCESS) {
io_registry_entry_t entry = IOIteratorNext(iter);
if (entry) {
CFMutableDictionaryRef dict = NULL;
if (IORegistryEntryCreateCFProperties(entry, &dict, kCFAllocatorDefault, 0) == KERN_SUCCESS) {
CFNumberRef obj = CFDictionaryGetValue(dict, CFSTR("HIDIdleTime"));
if (obj) {
int64_t nanoseconds = 0;
if (CFNumberGetValue(obj, kCFNumberSInt64Type, &nanoseconds)) {
idlesecs = (nanoseconds >> 30); // Divide by 10^9 to convert from nanoseconds to seconds.
}
}
CFRelease(dict);
}
IOObjectRelease(entry);
}
IOObjectRelease(iter);
}
return idlesecs;
}
I consider this code to be in the public domain. Please feel free to copy and paste. And let me know if you find any problems or have suggestions.
How to Print a PDF File Using Cocoa
Mac OS X is well known for its great support for PDF files. You can create a PDF file from anything you can print. I thought that using Apple’s PDFKit framework would make it easy to program a way to print an existing PDF file. That turned out not to be the case.
Sending a file to a printer using the lp command is easy. However, this approach does not work for PDF files formatted for landscape printing. You can specify landscape orientation, but I wanted a way to detect the orientation automatically.
PDFKit has a PDFView object that has a printWithInfo:autoRotate: method. However, adding a PDFDocument to a PDFView and telling it to print doesn’t work. I eventually stumbled onto the fact that PDFDocumenthas a secret method that makes printing easy. So here is the code:
#import <Quartz/Quartz.h>
- (void)printPDF:(NSURL *)fileURL {
// Create the print settings.
NSPrintInfo *printInfo = [NSPrintInfo sharedPrintInfo];
[printInfo setTopMargin:0.0];
[printInfo setBottomMargin:0.0];
[printInfo setLeftMargin:0.0];
[printInfo setRightMargin:0.0];
[printInfo setHorizontalPagination:NSFitPagination];
[printInfo setVerticalPagination:NSFitPagination];
// Create the document reference.
PDFDocument *pdfDocument = [[[PDFDocument alloc] initWithURL:fileURL] autorelease];
// Invoke private method.
// NOTE: Use NSInvocation because one argument is a BOOL type. Alternately, you could declare the method in a category and just call it.
BOOL autoRotate = YES;
NSMethodSignature *signature = [PDFDocument instanceMethodSignatureForSelector:@selector(getPrintOperationForPrintInfo:autoRotate:)];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setSelector:@selector(getPrintOperationForPrintInfo:autoRotate:)];
[invocation setArgument:&printInfo atIndex:2];
[invocation setArgument:&autoRotate atIndex:3];
[invocation invokeWithTarget:pdfDocument];
// Grab the returned print operation.
NSPrintOperation *op = nil;
[invocation getReturnValue:&op];
// Run the print operation without showing any dialogs.
[op setShowsPrintPanel:NO];
[op setShowsProgressPanel:NO];
[op runOperation];
}
I consider this code to be in the public domain. Please feel free to copy and paste. And let me know if you find any problems or have suggestions.














