I want to mock the facebook login block, but test failed, the block wasn't invoked. Please help me solve the problem.
//Test
-(void)testFacebookLogin
{
id mockManager = OCMClassMock([FBSDKLoginManager class]);
OCMStub([[mockManager alloc]init]).andReturn(mockManager);
FBSDKLoginManagerLoginResult *res = [[FBSDKLoginManagerLoginResult alloc]initWithToken:nil isCancelled:YES grantedPermissions:nil declinedPermissions:nil];
NSError* err = [NSError errorWithDomain:#"This is an error" code:NSURLErrorNotConnectedToInternet userInfo:nil];
[[mockManager stub]logInWithReadPermissions:OCMOCK_ANY fromViewController:OCMOCK_ANY handler:[OCMArg invokeBlockWithArgs:res,err,nil]];
__block BOOL invoke;
[LoginHelper facebookLoginWithLoginResult:^(BOOL success, NSError *error, id result) {
invoke = YES;
}];
XCTAssertTrue(invoke);
}
//LoginHelper.m
+(void)facebookLoginWithLoginResult:(LoginResult)loginResult
{
UIViewController* currentRootViewController = AppDelegateHelperSingleton.globalDelegate.window.rootViewController;
FBSDKLoginManager* loginManager = [[FBSDKLoginManager alloc]init];
loginManager.loginBehavior = FBSDKLoginBehaviorNative;
NSArray* permissions = #[#"email",#"public_profile",#"user_birthday"];
//facebook login with read permisssions
[loginManager logInWithReadPermissions:permissions fromViewController:currentRootViewController handler:^(FBSDKLoginManagerLoginResult *result, NSError *error)
{
loginResult(result, error);
}];
}
OCMStub([[mockManager alloc]init]).andReturn(mockManager);
That's not going to work. The problem you face is that your production code has a dependency which it locks down:
FBSDKLoginManager* loginManager = [[FBSDKLoginManager alloc]init];
In order for your test code to supply a "test double" (something that stands in for the real thing), you need a way to inject it.
There are various approaches to Dependency Injection. You can make it an initializer argument. You can make it a property. If you want the FBSDKLoginManager to be short-lived, you can make it a method argument.
For more, see How to Use Dependency Injection to Make Your Code Testable
Related
How to get score of local player from Leaderboard Game Center? I tried this code, but it returns nothing. Anybody know how to solve it, or is there better way how to get score?
- (NSString*) getScore: (NSString*) leaderboardID
{
__block NSString *score;
GKLeaderboard *leaderboardRequest = [[GKLeaderboard alloc] init];
if (leaderboardRequest != nil)
{
leaderboardRequest.identifier = leaderboardID;
[leaderboardRequest loadScoresWithCompletionHandler: ^(NSArray *scores, NSError *error) {
if (error != nil)
{
NSLog(#"%#", [error localizedDescription]);
}
if (scores != nil)
{
int64_t scoreInt = leaderboardRequest.localPlayerScore.value;
score = [NSString stringWithFormat:#"%lld", scoreInt];
}
}];
}
return score;
}
I think, that method have to wait for completion of [leaderboardRequest loadScoresWithCompletionHandler: ...
Is it possible?
Your code appears to not have any bugs that I can see. I would recommend displaying the standard leaderboard interface to see if your code that reports the scores is actually working correctly. If so, you should see the scores in the leaderboard. The code below works in my game, and I know the score reporting is working properly because it shows in the default game center UI.
GKLeaderboard *leaderboardRequest = [[GKLeaderboard alloc] init];
leaderboardRequest.identifier = kLeaderboardCoinsEarnedID;
[leaderboardRequest loadScoresWithCompletionHandler:^(NSArray *scores, NSError *error) {
if (error) {
NSLog(#"%#", error);
} else if (scores) {
GKScore *localPlayerScore = leaderboardRequest.localPlayerScore;
CCLOG(#"Local player's score: %lld", localPlayerScore.value);
}
}];
If you aren't sure how, the code below should work to show the default leaderboard (iOS7):
GKGameCenterViewController *gameCenterVC = [[GKGameCenterViewController alloc] init];
gameCenterVC.viewState = GKGameCenterViewControllerStateLeaderboards;
gameCenterVC.gameCenterDelegate = self;
[self presentViewController:gameCenterVC animated:YES completion:^{
// Code
}];
You cannot return score outside the block. In this code first "return score" will be executed before the method "loadScoresWithCompletionHandler". Additionally you haven't set initial value for "score", this method will return completely random value.
I suggest you to put your appropriate code inside the block, instead of:
int64_t scoreInt = leaderboardRequest.localPlayerScore.value;
score = [NSString stringWithFormat:#"%lld", scoreInt];
The leaderboard request completes after the return of your method. This means that you are returning a null string.
The method you put the leaderboard request in should be purely for sending the request. The method will be finished executing before the leaderboard request is complete thus your "score = [NSString stringWithFormat:#"%lld", scoreInt];" line is being executed AFTER the return of the "score" which is null until that line is executed.
The solution is to not return the outcome of the completion handler using the method that sends the request. The score is definitely being retrieved correctly, so just do whatever you need to with the score inside of the completion handler. You have no way to know when the completion handler is going to be executed. This, in fact, is the reason why Apple allows you to store the code to be executed in a block! Although, it can be confusing to understand how to work with blocks that will definitely be executed later, or in your situation, sometime after the method is returning.
The best way to handle your situation is to not return anything in this method and just use the "score" variable as you intend to after the block has set score to a non-null value!
I have a singleton class, APIClient, which needs to have userId and authToken set up before it can make calls to my backend.
We are currently storing userId and authToken in NSUserDefaults. For fresh installs, these values do not exist and we query the server for them.
Currently, we have code in our ViewControllers' viewDidLoad methods that manually query the server if these values do not exist.
I am interested to make this class "just work". By this, I mean have the client check if it has been initialized, if not fire a call to the server and set the appropriate userId and authToken - all without manual interference.
This has proven to be a rather tricky due to:
I can't make asyncObtainCredentials synchronous because I was told by folks at #iphonedev that the OS will kill my app if I have to freeze the main thread for a network operation
For what we have right now, the first call will always fail because of the asynchronous nature of asyncObtainCredential. Nil will be returned and first calls will always fail.
Does anyone know of a good work around for this problem?
`
#interface APIClient ()
#property (atomic) BOOL initialized;
#property (atomic) NSLock *lock;
#end
#implementation APIClient
#pragma mark - Methods
- (void)setUserId:(NSNumber *)userId andAuthToken:(NSString *)authToken;
{
self.initialized = YES;
[self clearAuthorizationHeader];
[self setAuthorizationHeaderWithUsername:[userId stringValue] password:authToken];
}
#pragma mark - Singleton Methods
+ (APIClient *)sharedManager {
static dispatch_once_t pred;
static APIClient *_s = nil;
dispatch_once(&pred, ^{
_s = [[self alloc] initWithBaseURL:[NSURL URLWithString:SERVER_ADDR]];
_s.lock =[NSLock new] ;
});
[_s.lock lock];
if (!(_s.initialized)) {
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSNumber *userId = #([prefs integerForKey:KEY_USER_ID]);
NSString *authToken = [prefs stringForKey:KEY_AUTH_TOKEN];
// If still doesn't exist, we need to fetch
if (userId && authToken) {
[_s setUserId:userId andAuthToken:authToken];
} else {
/*
* We can't have obtainCredentials to be a sync operation the OS will kill the thread
* Hence we will have to return nil right now.
* This means that subsequent calls after asyncObtainCredentials has finished
* will have the right credentials.
*/
[_s asyncObtainCredentials:^(NSNumber *userId, NSString *authToken){
[_s setUserId:userId andAuthToken:authToken];
}];
[_s.lock unlock];
return nil;
}
}
[_s.lock unlock];
return _s;
}
- (void)asyncObtainCredentials:(void (^)(NSNumber *, NSString *))successBlock {
AFHTTPClient *client = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:SERVER_ADDR]];
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:[OpenUDID value], #"open_udid", nil];
NSMutableURLRequest *request = [client requestWithMethod:#"GET" path:#"/get_user" parameters:params];
AFJSONRequestOperation *operation = \
[AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
...
// Do not use sharedManager here cause you can end up in a deadlock
successBlock(userId, authToken);
} failure:^(NSURLRequest *request , NSURLResponse *response , NSError *error , id JSON) {
NSLog(#"obtain Credential failed. error:%# response:%# JSON:%#",
[error localizedDescription], response, JSON);
}];
[operation start];
[operation waitUntilFinished];
}
You should check if those values are there in NSUserDefaults during the application launch. If they are not there, make a call to fetch it from server and show a loading overlay on the screen. Once you have fetched it, you can proceed with the next step.
If you dont want to use loading overlay, you can set some isLoading flag in APIClient class and check that to know if the asyn is still fetching. So whenever you are making a service call and you need these values, you know that how to handle it based this flag. Once you have got the required values and stored in NSUserDefaults, you can proceed with the next step. You can use Notifications/Blocks/KVO to notify your viewcontrollers to let them know that you have fetched these values.
I have this problem: a block inside a block.
self.createStuff = ^ (NSString *text) {
self.post.onCompletion = ^(NSURLResponse *response, NSData *data, NSError *error){
[self doStuff]; // error here
};
[self doMoreStuff]; // error here
};
I will have errors in [self doStuff] and on [self doMoreStuff]. The error is capturing 'self' strongly in this block is likely to lead to a retain cycle
Easy you say, just add
id mySelf = self;
before the first block and use mySelf instead.
Nope. This will not save my problem, simply because mySelf being of kind id will not give me a post property, needed by the second line. So I need to declare it like
MyClass *mySelf = self;
Making it like:
MyClass *mySelf = self;
self.createStuff = ^ (NSString *text) {
mySelf.post.onCompletion = ^(NSURLResponse *response, NSData *data, NSError *error){
[self doStuff]; // error here
};
[mySelf doMoreStuff];
};
OK, you say, now the self.post.onCompletion line and doMoreStuff are not complaining anymore, but we have another self inside onCompletion... because this is a block inside a block. I can repeat the process creating another weak reference like and this will have to be a weak reference to a weak reference
MyClass *internalMyself = mySelf;
and use
[internalMyself doStuff];
this seems to me to be a pretty pathetic way to do this and more, the app hangs when this method runs. Something like the reference is being deallocated before the method executes...
How do I solve this charade?
thanks.
note: this is being compiled to iOS 6+
You're pretty close. Just replace your solution
MyClass *mySelf = self;
self.createStuff = ^ (NSString *text) {
mySelf.post.onCompletion = ^(NSURLResponse *response, NSData *data, NSError *error) {
[self doStuff]; // error here
};
[mySelf doMoreStuff];
};
with
__weak MyClass *mySelf = self;
self.createStuff = ^ (NSString *text) {
mySelf.post.onCompletion = ^(NSURLResponse *response, NSData *data, NSError *error) {
[self doStuff]; // error here
};
[mySelf doMoreStuff];
};
The problem with the first solution is that mySelf isn't designated weak, so it's ownership qualifier is implicitly __strong (see LLVM's documentation). I'm not sure why this quiets the warning in the first block, but designating the reference __weak will fully remove the retain cycle.
I have a singleton that I'm using to parse XML and then cache it. The parsing/caching is done with a block. Is there any way for me to pass an argument to this block from another class so that I can change the URL from outside the singleton?
Here's the code I have now:
// The singleton
+ (FeedStore *)sharedStore
{
static FeedStore *feedStore = nil;
if(!feedStore)
feedStore = [[FeedStore alloc] init];
return feedStore;
}
- (RSSChannel *)fetchRSSFeedWithCompletion:(void (^)(RSSChannel *obj, NSError *err))block
{
NSURL *url = [NSURL URLWithString:#"http://www.test.com/test.xml"];
...
return cachedChannel;
}
And here's the class where I need to modify the NSURL from:
- (void)fetchEntries
{
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
// Initiate the request...
channel = [[BNRFeedStore sharedStore] fetchRSSFeedWithCompletion:
^(RSSChannel *obj, NSError *err) {
...
}
}
How do I pass an argument from fetchEntries to fetchRSSFeedWithCompletion?
You would want to add a parameter in the method, not the block.
Also, when using a completion block, there really is no reason to return anything in the method.
I'd change it to look like this:
-(void)fetchRSSFeed:(NSURL *)rssURL completion:(void (^)(RSSChannel *obj, NSError *error))block{
RSSChannel *cachedChannel = nil;
NSError *error = nil;
// Do the xml work that either gets you a RSSChannel or an error
// run the completion block at the end rather than returning anything
completion(cachedChannel, error);
}
I am working on catching errors in my app, and I am looking into using NSError. I am slightly confused about how to use it, and how to populate it.
Could someone provide an example on how I populate then use NSError?
Well, what I usually do is have my methods that could error-out at runtime take a reference to a NSError pointer. If something does indeed go wrong in that method, I can populate the NSError reference with error data and return nil from the method.
Example:
- (id) endWorldHunger:(id)largeAmountsOfMonies error:(NSError**)error {
// begin feeding the world's children...
// it's all going well until....
if (ohNoImOutOfMonies) {
// sad, we can't solve world hunger, but we can let people know what went wrong!
// init dictionary to be used to populate error object
NSMutableDictionary* details = [NSMutableDictionary dictionary];
[details setValue:#"ran out of money" forKey:NSLocalizedDescriptionKey];
// populate the error object with the details
*error = [NSError errorWithDomain:#"world" code:200 userInfo:details];
// we couldn't feed the world's children...return nil..sniffle...sniffle
return nil;
}
// wohoo! We fed the world's children. The world is now in lots of debt. But who cares?
return YES;
}
We can then use the method like this. Don't even bother to inspect the error object unless the method returns nil:
// initialize NSError object
NSError* error = nil;
// try to feed the world
id yayOrNay = [self endWorldHunger:smallAmountsOfMonies error:&error];
if (!yayOrNay) {
// inspect error
NSLog(#"%#", [error localizedDescription]);
}
// otherwise the world has been fed. Wow, your code must rock.
We were able to access the error's localizedDescription because we set a value for NSLocalizedDescriptionKey.
The best place for more information is Apple's documentation. It really is good.
There is also a nice, simple tutorial on Cocoa Is My Girlfriend.
I would like to add some more suggestions based on my most recent implementation. I've looked at some code from Apple and I think my code behaves in much the same way.
The posts above already explain how to create NSError objects and return them, so I won't bother with that part. I'll just try to suggest a good way to integrate errors (codes, messages) in your own app.
I recommend creating 1 header that will be an overview of all the errors of your domain (i.e. app, library, etc..). My current header looks like this:
FSError.h
FOUNDATION_EXPORT NSString *const FSMyAppErrorDomain;
enum {
FSUserNotLoggedInError = 1000,
FSUserLogoutFailedError,
FSProfileParsingFailedError,
FSProfileBadLoginError,
FSFNIDParsingFailedError,
};
FSError.m
#import "FSError.h"
NSString *const FSMyAppErrorDomain = #"com.felis.myapp";
Now when using the above values for errors, Apple will create some basic standard error message for your app. An error could be created like the following:
+ (FSProfileInfo *)profileInfoWithData:(NSData *)data error:(NSError **)error
{
FSProfileInfo *profileInfo = [[FSProfileInfo alloc] init];
if (profileInfo)
{
/* ... lots of parsing code here ... */
if (profileInfo.username == nil)
{
*error = [NSError errorWithDomain:FSMyAppErrorDomain code:FSProfileParsingFailedError userInfo:nil];
return nil;
}
}
return profileInfo;
}
The standard Apple-generated error message (error.localizedDescription) for the above code will look like the following:
Error Domain=com.felis.myapp Code=1002 "The operation couldn’t be completed. (com.felis.myapp error 1002.)"
The above is already quite helpful for a developer, since the message displays the domain where the error occured and the corresponding error code. End users will have no clue what error code 1002 means though, so now we need to implement some nice messages for each code.
For the error messages we have to keep localisation in mind (even if we don't implement localized messages right away). I've used the following approach in my current project:
1) create a strings file that will contain the errors. Strings files are easily localizable. The file could look like the following:
FSError.strings
"1000" = "User not logged in.";
"1001" = "Logout failed.";
"1002" = "Parser failed.";
"1003" = "Incorrect username or password.";
"1004" = "Failed to parse FNID."
2) Add macros to convert integer codes to localized error messages. I've used 2 macros in my Constants+Macros.h file. I always include this file in the prefix header (MyApp-Prefix.pch) for convenience.
Constants+Macros.h
// error handling ...
#define FS_ERROR_KEY(code) [NSString stringWithFormat:#"%d", code]
#define FS_ERROR_LOCALIZED_DESCRIPTION(code) NSLocalizedStringFromTable(FS_ERROR_KEY(code), #"FSError", nil)
3) Now it's easy to show a user friendly error message based on an error code. An example:
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error"
message:FS_ERROR_LOCALIZED_DESCRIPTION(error.code)
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
Great answer Alex. One potential issue is the NULL dereference. Apple's reference on Creating and Returning NSError objects
...
[details setValue:#"ran out of money" forKey:NSLocalizedDescriptionKey];
if (error != NULL) {
// populate the error object with the details
*error = [NSError errorWithDomain:#"world" code:200 userInfo:details];
}
// we couldn't feed the world's children...return nil..sniffle...sniffle
return nil;
...
Objective-C
NSError *err = [NSError errorWithDomain:#"some_domain"
code:100
userInfo:#{
NSLocalizedDescriptionKey:#"Something went wrong"
}];
Swift 3
let error = NSError(domain: "some_domain",
code: 100,
userInfo: [NSLocalizedDescriptionKey: "Something went wrong"])
Please refer following tutorial
i hope it will helpful for you but prior you have to read documentation of NSError
This is very interesting link i found recently ErrorHandling
I'll try summarize the great answer by Alex and the jlmendezbonini's point, adding a modification that will make everything ARC compatible (so far it's not since ARC will complain since you should return id, which means "any object", but BOOL is not an object type).
- (BOOL) endWorldHunger:(id)largeAmountsOfMonies error:(NSError**)error {
// begin feeding the world's children...
// it's all going well until....
if (ohNoImOutOfMonies) {
// sad, we can't solve world hunger, but we can let people know what went wrong!
// init dictionary to be used to populate error object
NSMutableDictionary* details = [NSMutableDictionary dictionary];
[details setValue:#"ran out of money" forKey:NSLocalizedDescriptionKey];
// populate the error object with the details
if (error != NULL) {
// populate the error object with the details
*error = [NSError errorWithDomain:#"world" code:200 userInfo:details];
}
// we couldn't feed the world's children...return nil..sniffle...sniffle
return NO;
}
// wohoo! We fed the world's children. The world is now in lots of debt. But who cares?
return YES;
}
Now instead of checking for the return value of our method call, we check whether error is still nil. If it's not we have a problem.
// initialize NSError object
NSError* error = nil;
// try to feed the world
BOOL success = [self endWorldHunger:smallAmountsOfMonies error:&error];
if (!success) {
// inspect error
NSLog(#"%#", [error localizedDescription]);
}
// otherwise the world has been fed. Wow, your code must rock.
Another design pattern that I have seen involves using blocks, which is especially useful when a method is being run asynchronously.
Say we have the following error codes defined:
typedef NS_ENUM(NSInteger, MyErrorCodes) {
MyErrorCodesEmptyString = 500,
MyErrorCodesInvalidURL,
MyErrorCodesUnableToReachHost,
};
You would define your method that can raise an error like so:
- (void)getContentsOfURL:(NSString *)path success:(void(^)(NSString *html))success failure:(void(^)(NSError *error))failure {
if (path.length == 0) {
if (failure) {
failure([NSError errorWithDomain:#"com.example" code:MyErrorCodesEmptyString userInfo:nil]);
}
return;
}
NSString *htmlContents = #"";
// Exercise for the reader: get the contents at that URL or raise another error.
if (success) {
success(htmlContents);
}
}
And then when you call it, you don't need to worry about declaring the NSError object (code completion will do it for you), or checking the returning value. You can just supply two blocks: one that will get called when there is an exception, and one that gets called when it succeeds:
[self getContentsOfURL:#"http://google.com" success:^(NSString *html) {
NSLog(#"Contents: %#", html);
} failure:^(NSError *error) {
NSLog(#"Failed to get contents: %#", error);
if (error.code == MyErrorCodesEmptyString) { // make sure to check the domain too
NSLog(#"You must provide a non-empty string");
}
}];
extension NSError {
static func defaultError() -> NSError {
return NSError(domain: "com.app.error.domain", code: 0, userInfo: [NSLocalizedDescriptionKey: "Something went wrong."])
}
}
which I can use NSError.defaultError() whenever I don't have valid error object.
let error = NSError.defaultError()
print(error.localizedDescription) //Something went wrong.
Well it's a little bit out of question scope but in case you don't have an option for NSError you can always display the Low level error:
NSLog(#"Error = %# ",[NSString stringWithUTF8String:strerror(errno)]);