In my custom framework, I have a method like the one shown below which fetches value from dictionary and converts it into BOOL and returns the boolean value.
- (BOOL)getBoolValueForKey:(NSString *)key;
What if the caller of this method passes a key that does not exist. Should I throw a custom NSException saying key does not exist(but throwing exception is not recommended in objective c) or add NSError parameter to this method as shown below?
- (BOOL)getBoolValueForKey:(NSString *)key error:(NSError **)error;
If I use NSError, I will have to return 'NO' which will be misleading since 'NO' can be a valid value of any valid key.
The API for this is long-established by NSUserDefaults, and should be your starting point for designing your API:
- (BOOL)boolForKey:(NSString *)defaultName;
If a boolean value is associated with defaultName in the user defaults, that value is returned. Otherwise, NO is returned.
You should avoid creating a different API for fetching bools from a keystore unless you have a strong reason. In most ObjC interfaces, fetching a non-exixtant key returns nil and nil is interpreted as NO in a boolean context.
Traditionally, if one wants to distinguish between NO and nil, then call objectForKey to retrieve the NSNumber and check for nil. Again, this is behavior for many Cocoa key stores and shouldn't be changed lightly.
However, it is possible that there is a strong reason to violate this expected pattern (in which case you should definitely note it carefully in the docs, because it is surprising). In that case, there are several well established patterns.
First, you can consider fetching an unknown key to be a programming error and you should throw an exception with the expectation that the program will soon crash because of this. It is very unusual (and unexpected) to create new kinds of exceptions for this. You should raise NSInvalidArgumentException which exists exactly for this problem.
Second, you can distinguish between nil and NO by correctly using a get method. Your method begins with get, but it shouldn't. get means "returns by reference" in Cocoa, and you can use it that way. Something like this:
- (BOOL)getBool:(BOOL *)value forKey:(NSString *)key {
id result = self.values[key];
if (result) {
if (value) {
// NOTE: This throws an exception if result exists, but does not respond to
// boolValue. That's intentional, but you could also check for that and return
// NO in that case instead.
*value = [result boolValue];
}
return YES;
}
return NO;
}
This takes a pointer to a bool and fills it in if the value is available, and returns YES. If the value is not available, then it returns NO.
There is no reason to involve NSError. That adds complexity without providing any value here. Even if you are considering Swift bridging, I wouldn't use NSError here to get throws. Instead, you should write a simple Swift wrapper around this method that returns Bool?. That's a much more powerful approach and simpler to use on the Swift side.
If you wish to communicate passing a non-existent key as a programmer error, i.e. something that should actually never occur during runtime because for instance something upstream should have taken care of that possibility, then an assertion failure or NSException is the way to do it. Quoting Apple's documentation from the Exception Programming Guide:
You should reserve the use of exceptions for programming or unexpected runtime errors such as out-of-bounds collection access, attempts to mutate immutable objects, sending an invalid message, and losing the connection to the window server. You usually take care of these sorts of errors with exceptions when an application is being created rather than at runtime.
If you wish to communicate a runtime error from which the program can recover / can continue executing, then adding an error pointer is the way to do it.
In principle it is fine to use BOOL as the return type there even if there is a non-critical error case. There are however corner cases with this in case you intend to interface with this code from Swift:
If you are accessing this API via Swift, NO always implies that an error is thrown, even if in your Objective-C method implementation you do did not populate the error pointer, i.e. you would need a do / catch and handle specifically of a nil error.
The opposite actually is also valid, i.e. it is possible to throw an error in the success case (NSXMLDocument for instance does this to communicate non-critical validation errors). There is to my knowledge no way to communicate this non-critical error information to Swift.
If you do intend to use this API from Swift, I would perhaps box the BOOL to a nullable NSNumber (at which case the error case would be nil, and the successful NO case would be an NSNumber with NO wrapped in it).
I should note, for the specific case of a potentially failable setter, there are strong conventions that you should follow, as noted in one of the other answers.
You pinpoint the major weakness in Apples error handling approach.
We are dealing with those situations by guaranteeing that the NSError is nil in success cases, so you actually check the error:
if (error) {
// ... problem
// handle error and/ or return
}
As this contradicts Apples error handle, where an Error is never guaranteed to be nil, but is guaranteed to be not nil in failure cases, affected methods have to be well documented to the clients know about this special behaviour.
This is not a nice solution, but the best I know.
(This is one of the nasty things we do not have to deal with any more in swift)
If You want all these
Distinguish between failure and success cases
Work with the bool value only if it is a success
In case of failure, caller mistakenly does not think return value is the value of the key
I suggest to make a block based implementation. You'll have a successBlock and errorBlock to clearly separate.
Caller will call the method like this
[self getBoolValueForKey:#"key" withSuccessBlock:^(BOOL value) {
[self workWithKeyValue:value];
} andFailureBlock:^(NSError *error) {
NSLog(#"error: %#", error.localizedFailureReason);
}];
and the implementation:
- (void)getBoolValueForKey:(NSString *)key withSuccessBlock:(void (^)(BOOL value))success andFailureBlock:(void (^)(NSError *error))failure {
BOOL errorOccurred = ...
if (errorOccurred) {
// userInfo will change
// if there are multiple failure conditions to distinguish between
NSDictionary *userInfo = #{
NSLocalizedDescriptionKey: NSLocalizedString(#"Operation was unsuccessful.", nil),
NSLocalizedFailureReasonErrorKey: NSLocalizedString(#"The operation timed out.", nil),
NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(#"Have you tried turning it off and on again?", nil)
};
NSError *error = [NSError errorWithDomain:#"domain" code:999 userInfo:userInfo];
failure(error);
return;
}
BOOL boolValue = ...
success(boolValue);
}
We use this
- (id) safeObjectForKey:(NSString*)key {
id retVal = nil;
if ([self objectForKey:key] != nil) {
retVal = [self objectForKey:key];
} else {
ALog(#"*** Missing key exception prevented by safeObjectForKey");
}
return retVal;
}
Header file NSDictionary+OurExtensions.h
#import <Foundation/Foundation.h>
#interface NSDictionary (OurExtensions)
- (id) safeObjectForKey:(NSString*)key;
#end
In this case, I would prefer returning NSInteger with returning 0, 1 and NSNotFound if caller passes key that doesn't exist.
From the nature of this method, It should be caller judgement to handle NSNorFound. As I can see, returning error is not very encouraging to user from the method's name.
When my code gets to a certain point in execution, it needs to call a selector. Problem is, which selector to call, along with the object passed to it in performSelector, changes each time. The candidate selectors are all functions written ahead of time, but how can I convert from a variable (e.g.: NSString* or enum telling program which selector to call) to the name of the selector, without using a gigantic switch statement?
SEL selector = NSSelectorFromString(aSelectorName);
if ([self respondsToSelector: selector])
{
[self performSelector: selector];
}
I am using a NSProxy subclass and forwardInvocation: for capturing calls to my Backend API object (a shared instance).
Some Background information:
I want to capture the API calls so I can check everytime if I have to refresh my authentication token. If yes I just perform the refresh before.
The method parameters (of invocation) contain blocks.
Some simplified code:
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation setTarget:self.realAPI];
[invocation retainArguments];
// Perform refresh call and forward invocation after
// successfully refreshed
if (authenticationRefreshNeeded) {
[self.realAPI refreshWithBlock:^(NSObject *someObject) {
[invocation invokeWithTarget:self.realAPI];
}];
}
// Otherwise we just forward the invocation immediately
else {
[invocation invokeWithTarget:self.realAPI];
}
return;
}
I am already calling retainArguments so my blocks and other parameters don't get lost because of the late execution of invokeWithTarget: (refreshWithBlock: makes an async API call).
Everything works fine so far - BUT:
The return value of invocation is always nil when invokeWithTarget: is performed within the refresh block. Is there any way to retain the return value (like the arguments)?
Any hints? Suggestions?
Update
As response to #quellish:
The problem is that the return value is of type NSURLSessionDataTask (that I use to show an activity indicator) which I read directly after making the call. But the proxy does not forward the call immediately so the return value is not there - of course (I was blind).
What would be a possible workaround? Can I return a placeholder value or how can I know as caller when the method gets invoked so I can retrieve the return value later?
To perform an operation when your invocation is complete, passing the result:
if (authenticationRefreshNeeded) {
[self.realAPI refreshWithBlock:^(NSObject *someObject) {
NSURLSessionDataTask *resultTask = nil;
[invocation invokeWithTarget:self.realAPI];
[invocation getReturnValue:&resultTask];
if (completion != nil){
completion(resultTask);
}
}];
}
Where completion() is a block that takes an NSURLSessionDataTask as a parameter. Blocks can be used as callbacks, which make them well suited to what you are trying to do ("when I'm done, do this() ") Ideally, this would have been passed into the method containing the above - but since this is forwardInvocation: , that gets a little more... challenging. You could set it as a property on this proxy object and read it from there.
Another approach would be to extend UIApplication with a category or informal protocol with a method like addDataTask: which you could call instead of your block, which would hand off responsbility for the "i just added a data task" to another receiver, most likely the application's delegate (and you can extend the UIApplicationDelegate protocol with a new method, application:didAddDataTask: to handle this). It sounds like your data task and activity indicator are application-level concerns, which may make this a good fit.
That said, I have some experience with almost exactly the problems you are trying to solve (token based authorization). I would suggest taking at a look at how ACAccountStore approaches this problem , it may offer some ideas for alternative implementations.
I have a test case using OCMock which does the following:
CAAOAuth2AuthenticationManager *oAuth2AuthManager = [[CAAOAuth2AuthenticationManager alloc] init];
id authDelegate = [OCMockObject mockForProtocol:#protocol(CAAAuthenticationDelegate)];
id partialAuthManagerMock = [OCMockObject partialMockForObject:oAuth2AuthManager];
id resultMock = [OCMockObject mockForClass:[CAAOAuth2AuthenticationResult class]];
[[authDelegate reject] didFailWithError:OCMOCK_ANY];
[[[partialAuthManagerMock expect] andForwardToRealObject] authenticateWithResult:OCMOCK_ANY formData:OCMOCK_ANY delegate:authDelegate];
[[partialAuthManagerMock reject] authenticateWithOptions:OCMOCK_ANY delegate:authDelegate];
[[[resultMock expect] andReturnValue:OCMOCK_VALUE(YES) ] isAuthenticated];
[[resultMock reject] refreshToken];
When I run the test cases, a second test case (completely different test class and file) which also uses the CAAAuthenticationDelegate protocol fails with SIGABRT:
2014-02-28 10:11:24.594 otest[37161:303] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'OCMockObject[CAAAuthenticationDelegate]: unexpected method invoked: didReceiveAuthenticationWithResult:OCMockObject[CAAOAuth2AuthenticationResult]
stubbed: didFailWithError:<OCMAnyConstraint: 0xa913fc0>'
But, I don't use any mocks in the second test case. I was trying to clear the mocks with stopMocking with no success.
The following mock setup works without any problems:
[[authDelegate reject] didFailWithError:OCMOCK_ANY];
[[[partialAuthManagerMock expect] andForwardToRealObject] authenticateWithResult:OCMOCK_ANY formData:OCMOCK_ANY delegate:authDelegate];
[[partialAuthManagerMock expect] authenticateWithOptions:OCMOCK_ANY delegate:authDelegate];
[[[resultMock expect] andReturnValue:OCMOCK_VALUE(NO) ] isAuthenticated];
[[[resultMock expect] andReturn:refreshToken] refreshToken];
Can someone tell me, why this happens?
As a workaround, can you create an empty implementation of the protocol and then mock a real object? I've had better luck with that method -- mocking protocols has only led to wonkiness for me.
#interface TestAuthDelegateImpl : NSObject <CAAAuthenticationDelegate>
#end
#implementation
- (void)didFailWithError:(id)whatever;
#end
Something like that. Then just mockForClass it -- might be better behaved.
That seems to mean that your CAAOAuth2AuthenticationManager instance was still around in a later test, and still had the old mock delegate set on it, and some method was called on it which caused that delegate method to be called. Is CAAOAuth2AuthenticationManager a singleton-type object, or is the same instance used in the second test? I would reset the delegate to nil on the auth manager in the first test when it is done.
You can also use niceMockForProtocol, which will silently ignore any method calls which do not have an explicit reject set up on them. By the exception, it sounds like the reject has been removed, and the delegate mock will now just throw exceptions on any method sent to it, since there are also no expects set up.
Also, I would use STAssertNoThrow() around the actual call to your real code (which presumably happens after the setup you show above). Rejects and unexpected methods will raise exceptions, which can cause the mock objects to not get deallocated properly and create issues for subsequent tests. If the test in question passed though, that is probably not an issue here.
The last thing to check is if your delegate property is declared as "assign" instead of "weak". If it's "assign", and you don't nil it out and it gets freed, then anything could happen (segfault, or an entirely new object getting allocated at that same memory address). That also seems unlikely here though.
I have a recursive method that changes a property of self (the object on which the method is defined).
I get the error:
...uncaught exception 'NSGenericException', reason: '*** Collection <__NSArrayM: 0x755f670> was mutated while being enumerated...'
I read about this error, but I'm not sure how to apply a solution to my problem. Recursion is crucial to the solution, as is updating this particular property. I'm very new to Objective-C, so perhaps I'm missing something or designing this solution poorly.
currentPlayer is the property
recursiveMethod is, obviously, the recursive method that's producing the error
This recursive method is called from within a for in loop.
- (void) recursiveMethod:(id <Team>)team atIndex:(int *)i withPlayer:(id <Player>) {
[self.currentPlayer replaceObjectAtIndex:i withObject:nextPlayer];
if // some conditional that's unimportant to this question
{
// grab another team
// grab another index
// grab another player
[self recursiveMethod:nextTeam atIndex:i withPlayer:nextPlayer];
}
}
A lot of the details are unimportant. I stripped it down; really it's just a recursive method that will update a property (in this case an array) of the object on which the method is defined.
Just read the exception message:
NSArrayM [...] was mutated while being enumerated.
You can't do that.