Init Question
What’s the best practice for this situation?
Let’s say you have an object where initWithSomething could fail due to bad inputs or other error.
Let’s also say that, if it fails, an error should probably be presented to the user.
If it helps to think about a concrete case, think of a Core Data stack object. (This post is not about Core Data. This is just an example.)
Init might look something like this:
- (instancetype)initWithFolder:(NSString \*)folder modelName:(NSString \*)modelName databaseName:(NSString \*)databaseName;
There are two reasons it could fail:
-
Inputs are bad. (For instance: folder is nil, or modelName has a typo.)
-
A method called during init fails. For example,
-[NSPersistentStoreCoordinator addPersistenStoreWithType:configuration:URL:options:error:]
could fail.
I can think of a few options, and I’m not sure I like any of them. So I’d love to know what you think.
Option 1: in-out error parameter
I don’t think I’ve ever seen a failable initializer with an in-out NSError ** parameter. (Do they exist? Maybe they do and I just haven’t noticed.)
The init method would look like this:
- (instancetype)initWithFolder:(NSString \*)folder modelName:(NSString \*)modelName databaseName:(NSString \*)databaseName error:(NSError **)error;
That seems ugly and unconventional to me. But it would reflect reality pretty well.
Update 1:15 pm: This is the winner! Look at NSString.h to see some examples.
Option 2: error property
Don’t fail in the initializer. Instead, create a property for the error:
@property (nonatomic) NSError *initError;
The caller of initWith… would have to check initError
right after to see if there was an error. Ugly and unconventional again.
Option 3: initialize lazily
The main reason for this object is to provide properties for other objects and hide how they’re created.
The example could have a property like this:
@property (nonatomic) NSManagedObjectContext *context;
All the initialization of the stack could be put off until context
is first referenced.
But, then, there’s still the issue of what to do with errors. Something like this instead of a property?
- (NSManagedObjectContext \*)context:(NSError **)error;
Ugh. Weird.
But maybe it’s less weird if the API is something like this:
@property (nonatomic) NSManagedObjectContext \*context;
- (BOOL)setupManagedObjectContext:(NSError **)error;
The caller of initWith… will have to call setupManagedObjectContext
before referencing the context
property.
This is also weird because setting up context
should be completely in the hands of the Core Data stack object. It shouldn’t be something a caller has to remember to do.
Option 4: assertions and faith
Use NSParameterAssert
and NSAssert
to catch programmer errors in debug builds — then assume that, once programmer errors are fixed, nothing else that technically can fail will actually fail.
This would mean believing that, in this case, -[NSPersistentStoreCoordinator addPersistenStoreWithType:configuration:URL:options:error:]
would never fail as long as the inputs were good.
Arguably you should just call abort()
if such a method fails with good inputs, on the grounds that the device has probably caught fire.
But still, that requires a certain amount of optimism — but optimism means doubt, and our job as programmers is to remove doubt.
Option 5: don’t use a separate object for this
Give up on the idea of encapsulating all this in reusable code. Instead, have the app delegate (or similar high-level object) do the setup. Presumably that high-level object can show errors to the user and decide what to do if things go wrong.
This answer is a bummer, though. I like reusable objects and I don’t like copy-and-paste.
But maybe I’ve proven that this option is the only responsible choice.
What am I missing?