Pass argument to a block? - iphone

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);
}

Related

OCMock facebook block mock

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

iOS instance variables not initialised from within a block

i am using parse.com as backend for my app.
i need to get information from my backend and init an instance with this information.
i use this code in order to do so:
- (id) initWithTeamId:(NSString *)teamId
{
__block NSString *str;
__block FrFTeam *blockSelf = self;
PFQuery *query = [PFQuery queryWithClassName:#"teams"];
[query getObjectInBackgroundWithId:teamId block:^(PFObject *object, NSError *error) {
str = [object objectForKey:#"teamName"];
(void)[blockSelf initWithName:str players:nil thumb:nil];
}];
return self;
}
when this code is done self.name is set to null,
what am i doing wrong?
thank you!
Try this code:
// call init on the object, then setup the team id
- (id)initWithTeamId:(NSString *)teamId
{
self = [super init];
if (self) {
[self setupWithTeamId:teamId];
}
return self;
}
- (void) setupWithTeamId:(NSString *)teamId
{
__weak FrFTeam *blockSelf = self;
PFQuery *query = [PFQuery queryWithClassName:#"teams"];
[query getObjectInBackgroundWithId:teamId block:^(PFObject *object, NSError *error) {
NSString *name = [object objectForKey:#"teamName"];
NSLog(#"Received name: %# from object: %#", name, object);
[blockSelf setName:name];
}];
}
Then, change the name of the method from initWithName:... because this isn't really an init method because you have already done the init before calling setupWithTeamId:.
If you need the parse bit to be done before the init method returns, you should:
Call parse to get the details before calling init on the object
Use getObjectWithId: --- not recommended as this blocks the thread in init, bad idea
Pretty sure the reason is in the method name you are calling -getObjectInBackgroundWithId:block: (it specifies InBackground, which suggests the block is called at some later stage and not immediately)
This would suggest that you end up with self == nil (as you are not calling any other initialiser in the method.
Initialisation of an object has to be synchronous.

ARC capturing self... block inside a block and reference being released before execution

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.

Objective-C GCD wait for Block to finish (AFNetworking)

I've been trying to experiment with some code from a tutorial, however not having much success due to not getting my head around GCD.
I have an class named API.m and here is the code regarding GCD:
+ (API *) sharedInstance
{
static API *sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
sharedInstance = [[self alloc] initWithBaseURL:[NSURL URLWithString:APIHost]];
});
return sharedInstance;
}
-(void)commandWithParams:(NSMutableDictionary*)params
onCompletion:(JSONResponseBlock)completionBlock
{
NSMutableURLRequest *apiRequest = [self multipartFormRequestWithMethod:#"POST"
path:APIPath
parameters:params
constructingBodyWithBlock: ^(id <AFMultipartFormData>formData) {
//TODO: attach file if needed
}];
AFJSONRequestOperation* operation = [[AFJSONRequestOperation alloc] initWithRequest: apiRequest];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
//success!
completionBlock(responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//failure :(
completionBlock([NSDictionary dictionaryWithObject:[error localizedDescription] forKey:#"error"]);
}];
[operation start];
}
I make a simple test by implementing a button and getting an NSArray to print it's content to the output window:
- (IBAction)test:(id)sender {
NSMutableDictionary* params =[NSMutableDictionary dictionaryWithObjectsAndKeys:
#"pending", #"command",
[[[API sharedInstance] user] objectForKey:#"UserID"] , #"userID",
nil];
[[API sharedInstance] commandWithParams:params
onCompletion:^(NSDictionary *json) {
//result returned
if ([json objectForKey:#"error"]==nil) {
// Simple example
[self.users addObject:#"1"];
} else {
//error
[UIAlertView title:#"Error" withMessage:[json objectForKey:#"error"]];
}
}];
NSLog(#"%#", self.users);
}
Now when I first click the button an empty NSArray is printed to the output window, but when I press it again it print's "1". It's clear that the program is reaching NSLog before the completion block has time to fully execute. Could someone please help me modify the code so that I have the option to have the NSLog execute after the completion block has finished?
Not sure as to what you are trying to accomplish, but if the goal is to just have NSLog execute after the completion block, you can move the NSLog statement after
[self.users addObject:#"1"];
If you have some code which you want to execute after adding it to the array, you can have
[self methodName]; in the completion block and it will get called there.
Completion block, is the code which is run after execution of the code which you wanted run. The code which you wanted run, will happen asynchronously and on another thread. After that code is run, the completion block code will get executed.

Logic Issue - NSObject class - Beginner

I have 2 questions.
1.) I am creating a NSObject class, and i am having the following code in it. (ASIHTTPRequest POST).
The name of the NSObject class is called, SendToServer. I call the class as follows;
SendToServer *sv = [[SendToServer alloc]];
sv.grabURLInTheBackground ;
NSLog(#"This line is executed ");
The following is the code that is in the SendToServer NSObject class.
- (void)grabURLInTheBackground
{
if (![self queue]) {
[self setQueue:[[[NSOperationQueue alloc] init] autorelease]];
}
NSURL *url = [NSURL URLWithString:#"http://allseeing-i.com"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request setDidFinishSelector:#selector(requestDone:)];
[request setDidFailSelector:#selector(requestWentWrong:)];
[[self queue] addOperation:request]; //queue is an NSOperationQueue
}
- (void)requestDone:(ASIHTTPRequest *)request
{
NSString *response = [request responseString];
}
- (void)requestWentWrong:(ASIHTTPRequest *)request
{
NSError *error = [request error];
}
The problem is that, the code executes the line sv.grabURLInTheBackground ; and before it executes the requestDone or requestWentWrong methods, it executes the NSLog (NSLog(#"This line is executed "); )
What i want my program to do is to complete all the operations in the SendToServer NSObject class and then Execute the NSLog (In a sequence).
First execute sv.grabURLInTheBackground ; once all the activities in that method/class is over, then return to the code and execute the other line which is NSLog(#"This line is executed "); .
2.) I need to return a String when the requestDone method is executed. How do i modify the code to do so;
- (NSString * )requestDone:(ASIHTTPRequest *)request {
}
but how do i edit [request setDidFinishSelector:#selector(requestDone:)];, for the above code ?
------------------------------------------------------------------------------------------------------------------------------------------
EDIT
I am doing this for user login. Upon button click i will be calling the grabURLInTheBackground method from the NSObject class. And the viewcontroller needs to know if the user login was successful or failed.
SendToServer *sv = [[SendToServer alloc]];
[sv grabURLInTheBackground] ;
NSLog(#"User login SUcess or failed %#", [sv userloginSucessOrFail]);
For example say [sv userloginSucessOrFail] returns if the user login was success or failed.
What hapence here, is that after [sv grabURLInTheBackground] is called, it directly goes and executes the NSLog(#"User login SUcess or failed %#", [sv userloginSucessOrFail]); line of code.
What i want is, i need to find a way to let my ViewCOntroller know if the user login was a Success or failure.
First: call init on your object.
Second: grabURLInTheBackground is a method not a property. It should by called with square brackets
So you code becomes:
SendToServer *sv = [[SendToServer alloc] init];
[sv grabURLInTheBackground];
NSLog(#"This line is executed ");
To accomplish point 1) you need to make a synchronous request
NSURL *url = [NSURL URLWithString:#"http://allseeing-i.com"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request startSynchronous];
NSError *error = [request error];
if (!error) {
NSString *response = [request responseString];
}
The problem is that if this code is executed on the main thread is blocking (not good)...
For the second point. You can't.
EDIT:
What you have to do is something like the following steps:
Before calling grabURLInTheBackground you have to notify the user that a request is pending.. like putting an UIActivityIndicator, or disabling the UI,...
when you receive the callback then update the UI: hide the activity indicator, re-enable the UI... or if the request failed, notify the user.