It is mostly a separation of logic, but, there is one big difference in the order in which these methods are called.
init is sent to an object when it is being created. In the case of the application delegate, this is sent at the beginning of the NIB unarchiving process. All of the archived objects in the NIB are instantiated and sent init (or initWithCoder:, in the case of objects that conform to NSCoding and are fully supported IB objects).
However, during init, all of the IBOutlets for the AppDelegate will be nil. The NIB unarchiving process will not have connected outlets at the time it is instantiating the archived objects. (This makes sense, if you send init to an object as it is being created, you could not have set its outlets beforehand; the object didn’t exist!)
application:didFinishLaunchingWithOptions: is sent after the NIB file has been fully loaded; right before the application enters the run loop. All connected outlets will be valid at this point.
Why did we do it this way? Aaron likes separating the instantiation of model objects and the final application setup. (You can tell which chapters he wrote the source code for by this design decision. While he and I have incredibly similar styles, there are a few places we differ, and we thought it would be best to present both options to the readers.)