I'm working on a simple instagram project now.
for several occasions I've encountered the same problem.
To work with instagram I use InstagramKit Engine. It has some preset (void)s to make life easier. However I'm always stuck at the same problem.
Let's say we've got this:
- (void)getSelfUserDetails
{
[[InstagramEngine sharedEngine] getSelfUserDetailsWithSuccess:^(InstagramUser *userDetail) {
NSLog(#"%#",userDetail);
} failure:^(NSError *error) {
}];
}
Here userDetail is used inside of the "Success". And it works nice. What I need is to somehow save it after the block is done.
I've tried several things from creating a property to store the userDetails up to making my own method to return the userDetails. The same trouble with saving ints, NSStrings etc..
I think I'm missing some easy way out.
Show it to me please.
You need to capture an object in the block that you can send the response object.
This can be self.
-(void)processUserDetails:(InstagramUser *) userDetail
{
//....
}
- (void)getSelfUserDetails
{
[[InstagramEngine sharedEngine] getSelfUserDetailsWithSuccess:^(InstagramUser *userDetail) {
[self processUserDetails: userDetail];
} failure:^(NSError *error) {
}];
}
You could create a property to hold the Instagram user that you find and assign it to it in the success block:
#property (nonatomic) InstagramUser *myUser;
- (void)getSelfUserDetails
{
[[InstagramEngine sharedEngine] getSelfUserDetailsWithSuccess:^(InstagramUser *userDetail) {
NSLog(#"%#",userDetail);
self.myUser = userDetail;
} failure:^(NSError *error) {
}];
}
This is a good use for an NSNotification (since the call above happens asynchronously).
You could call it like:
[[NSNotificationCenter defaultCenter]
postNotificationName:#"TestNotification"
object:self
userInfo:#{ #"userDetail" : userDetail }
];
And then get that userInfo data back with whatever method is listening for that event.
Related
In my app, I have a download manager. After any of tasks is finished I need to get all data for tableView again and reload it. But I can't get data inside RACObserve signal. Here's my code.
NSArray *activeTasks = [[DownloadManager instance] tasksToProcess];
for (DownloadTask *task in activeTasks) {
[[[self
checkTask:task]
map:^(id value
return [self fetchDownloadedData];
}]
subscribeNext:^(NSArray *models) {
// models returns RACDynamicSignal not NSArray
NSLog(#"%#", models); // <RACDynamicSignal: 0x11611cb50> name:
NSLog(#"checktask next");
} completed:^{
// This is never being executed
NSLog(#"checktask completed");
}];
}
- (RACSignal *)checkTask: (DownloadTask *)task {
return [RACObserve(task, isFinished) map:^id(id _) {
return nil;
}];
}
- (RACSignal *)fetchDownloadedData {
return [[MyCoreDataModel fetchAll] flattenMap:^id(NSArray *models) {
// This is never being executed
return [models filter:^BOOL(MyCoreDataModel *model) {
return model.isDownloaded;
}];
}];
}
- (RACSignal *)fetchAll
{
return [[[MyCoreDataModel findAll] sortBy:#"title"] fetch];
}
Would be great if someone helps me getting where is my mistake is. Thanks in advance.
There are few mistakes:
In map function you use method which returns RACSignal - it's not correct. You should use flattenMap instead.
Complete block will never be called, because RACObserve - hot signal, it only sends next event.
I wrote small example, I hope it helps you.
NSMutableArray<RACSignal *> *signals = [NSMutableArray array];
for (DownloadTask *task in activeTasks) {
RACSignal *signal = [[RACObserve(task, isFinished) ignore:#NO] take:1];
[signals addObject:signal];
}
#weakify(self);
[[[RACSignal merge:signals] flattenMap:^RACStream *(id _) {
#strongify(self);
return [self fetchDownloadedData];
}] subscribeNext:^(NSArray *models) {
}];
Here I created array of signals. Each signal - observing the isFinished property. Also I added ignore:#NO] take:1]; - I think it's more right, because you only need YES value and after that no observe anymore (take:1). Then I merge these signals and each time when someone of them sends finished state, we fetch data.
Please, let me know if something is not understand, I try to explain more clearly.
I've working on/learning this all afternoon.
Following an example here: http://themainthread.com/blog/2012/09/communicating-with-blocks-in-objective-c.html, I have managed to setup a callback to get the result of an asynchronous call to a web service i have.
The web service takes a key code and the app transforms it and passes it back for authentication.
With my code below, how can I change the method from a void to an NSString that I can call to return my pass code?
-(void) showPassCode{
getAuthCodeAndMakePassCodeCompleteBlock callback = ^(BOOL wasSuccessful, NSString *passCode) {
if (wasSuccessful) {
NSLog(#"Code is: %#", passCode);
} else {
NSLog(#"Unable to fetch code. Try again.");
}
};
[self getAuthCodeAndMakePassCode:#"myAuthCode" withCallback:callback];
}
Ideally, I want it to work or look like this:
-(NSString *) strPassCode{
getAuthCodeAndMakePassCodeCompleteBlock callback = ^(BOOL wasSuccessful, NSString *passCode) {
if (wasSuccessful) {
return passCode;
} else {
return nil;
}
};
[self getAuthCodeAndMakePassCode:#"myAuthCode" withCallback:callback];
}
Without knowing the specifics of your code and how you query the server, I have to imagine it would look something like:
-(void)getAuthCodeWithCallback:(void (^)(NSString* authCode))callback
{
//make server call, synchronously in this example
NSString* codeReturnedFromServer = [self getServerCodeSynchronous];
callback(codeReturnedFromServer);
}
//some calling code
[self getAuthCodeWithCallback:^(NSString* authCode) {
NSLog(#"Code is: %#", authCode);
}];
If the method that gets your auth code from the server is asynchronous, it would look something like this:
-(void)getAuthCodeWithCallback:(void (^)(NSString* authCode))callback
{
//make server call, asynchronously in this example
[self someMethodCallToQueryCodeFromServerWithCallback:^(NSError* error, NSString* code) {
if (error) {
//handle error
}
else
callback(code);
}
}
According to https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/GameKit_Guide/LeaderBoards/LeaderBoards.html
Reporting score to gamecenter in ios7 should be done using
[GKLeaderboard reportScores:scores withCompletionHandler:^(NSError *error) {
//Do something interesting here.
}];
However, I could not find any reference to this method in GKLeaderboard.
The method does not exist here:
https://developer.apple.com/library/ios/documentation/GameKit/Reference/GKLeaderboard_Ref/Reference/Reference.html
GKLeaderboard.h does not contain a reportScores method also.
The former way of reporting score using GKScore's reportScoreWithCompletionHandler method had been deprecated so I am reluctant to use that.
Does anyone know whats the correct way to report score to gamecenter in ios7?
I can confirm that the reportScores:withCompletionHandler: method does work; I'm using it in one of my apps. It's located in the header file GKScore.h. This is how I'm using it:
- (void) reportHighScore:(NSInteger) highScore {
if ([GKLocalPlayer localPlayer].isAuthenticated) {
GKScore* score = [[GKScore alloc] initWithLeaderboardIdentifier:MY_LEADERBOARD_ID];
score.value = highScore;
[GKScore reportScores:#[score] withCompletionHandler:^(NSError *error) {
if (error) {
// handle error
}
}];
}
}
I am new to iOS development coming from a JS background with EmberJS. I want to port my EmberJS App to an iOS App. Therefore i would like to use similiar structures in my iOS App. As EmberJS makes heavy use of promises i searched for something similar for iOS and stumbled upon ReactiveCocoa. It is said in the introduction of ReactiveCocoa that this framework can be used to implement Promises. I tried it but it does not work properly. I wanted to start with a quite simple example:
Make an asynchronous network request (to fill a UITableViewController). Return a promise from this method.
Subscribe to this promise and reload the TableView when it is finished.
I want to do it this way, because i will have to perform several things after the data has been loaded successfully. My approach works basically but i am experiencing the following issues:
My TableView does not reload immediately after the request has been finished.
I am seeing the Log Statements in my subscribeCompleted immediately after the request finished. But the TableView stays blank.
The TableView loads the data after a few seconds of waiting.
If i start scrolling the TableView after i have seen the Log output, the TableView is suddenly loaded.
I suspect this may happen because i am fetching the data in a background thread. I think the resolve of the promise (subscribeCompleted) may happen in the background thread too and Cocoa Touch may not like this. Am i right? But if this is the case, how am i supposed implement a promise?
I hope you can help me getting started with ReactiveCocoa. Thx! :-)
UPDATE:
I managed to fix it by wrapping the to reloadData in a dispatch_async(dispatch_get_main_queue(), ^{... But still i am not sure wether this is the best way to go or what is recommended by ReactiveCocoa. So i am still keen on hearing some answers :-)
// this method wants to use the promise
- (void) loadDataAndPerformActionsAfterwards{
RACSignal *signal = [self fetchObjects];
[signal subscribeCompleted:^{
NSLog(#"Entered subscribeCompleted block signal!");
NSLog(#"Number of objects: %i", self.objects.count);
[self.tableView reloadData];
}];
}
// this method returns a promise. I omitted some parts but it shows basically how i go about resolving the promise.
- (RACSignal*) fetchMoviesForCurrentFormState{
return [RACSignal createSignal:^RACDisposable*(id<RACSubscriber> subscriber) {
NSLog(#"RAC createSignal Block called");
NSString *requestURL = #"...";
NSURL *urlObj = [NSURL URLWithString: requestURL];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData* data = [NSData dataWithContentsOfURL: urlObj];
if(data){
[self performSelectorOnMainThread:#selector(fetchedData:)
withObject:data waitUntilDone:YES];
[subscriber sendCompleted];
}else{
// Not implemented yet: handle the error case
[subscriber sendCompleted];
}
});
// actually i do not know yet what i should return here. Copied from a basic example.
return nil;
}];
}
You're right that this is an issue with threading. However, you don't need to drop down to the level of GCD.
Signals can be "delivered" onto another thread, which just invokes any subscription callbacks there:
- (void) loadDataAndPerformActionsAfterwards {
[[[self
fetchObjects]
deliverOn:RACScheduler.mainThreadScheduler]
subscribeCompleted:^{
NSLog(#"Entered subscribeCompleted block signal!");
NSLog(#"Number of objects: %i", self.objects.count);
[self.tableView reloadData];
}];
}
You may take a look into RXPromise. It's an Objective-C implementation of the Promises/A+ specification with a couple more features. (I'm the author).
A solution utilizing the RXPromise library would look as follows:
- (void) loadDataAndPerformActionsAfterwards {
[self fetchMovie]
.thenOn(dispatch_queue_get_main(), ^id(id fetchedMovie) {
self.model = fetchedObjects;
[self.tableView reloadData];
}, nil);
}
This assumes, method fetchMovie returns a Promise.
How do you get this? Well, you can easily wrap any asynchronous method or operation into one that returns a Promise. This works for any signal approach: completion blocks, callback functions, delegates, KVO, Notification, etc.
For example, a simplified implementation for NSURLConnection's async convenience class method (in practice you should check the response and do better error handling):
- (RXPromise*) fetchMovie {
RXPromise* promise = [[RXPromise alloc] init];
NSMutableRequest* request = ...;
[NSURLConnection sendAsynchronousRequest:request
queue:networkQueue
completionHandler:^(NSURLResponse* response, NSData* data, NSError* error){
if (error) {
[promise rejectWithReason:error];
}
else {
[promise fulfillWithValue:data];
}
}];
return promise;
}
You might want to use an approach using the NSURLConnection delegates, or an approach utilizing a NSOperation subclass. This enables you to implement cancellation:
- (RXPromise*) fetchObjects {
RXPromise* promise = [[RXPromise alloc] init];
NSMutableRequest* request = ...;
HTTPOperation* op =
[[HTTPOperation alloc] initWithRequest:request
queue:networkQueue
completionHandler:^(NSURLResponse* response, NSData* data, NSError* error){
if (error) {
[promise rejectWithReason:error];
}
else {
[promise fulfillWithValue:data];
}
}];
promise.then(nil, ^id(NSError* error){
[op cancel];
return nil;
});
[op start];
return promise;
}
Here, the HTTPOperation object will listen to its own promise for an error signal. If it receives one, for example a cancel message send from another object to the promise, the handler then "forwards" the cancel message to the operation.
A View Controller for example can now cancel a running HTTPOperation as follows:
- (void) viewWillDisappear:(BOOL)animate {
[super viewWillDisappear:animate];
[self.fetchObjectsPromise cancel];
self.fetchObjectPromise = nil;
}
I'm new to Obj-c. I've got a class which sets a var boolean to YES if it's successful (Game Center login = successful), what it would be great to do, is somehow have a listener to that var that listens to when it is YES and then executes some code. Do I use a block for that? I'm also using the Sparrow framework.
Here's my code in my GameCenter.m file
-(void) setup
{
gameCenterAuthenticationComplete = NO;
if (!isGameCenterAPIAvailable()) {
// Game Center is not available.
NSLog(#"Game Center is not available.");
} else {
NSLog(#"Game Center is available.");
__weak typeof(self) weakSelf = self; // removes retain cycle error
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer]; // localPlayer is the public GKLocalPlayer
__weak GKLocalPlayer *weakPlayer = localPlayer; // removes retain cycle error
weakPlayer.authenticateHandler = ^(UIViewController *viewController, NSError *error)
{
if (viewController != nil)
{
[weakSelf showAuthenticationDialogWhenReasonable:viewController];
}
else if (weakPlayer.isAuthenticated)
{
[weakSelf authenticatedPlayer:weakPlayer];
}
else
{
[weakSelf disableGameCenter];
}
};
}
}
-(void)showAuthenticationDialogWhenReasonable:(UIViewController *)controller
{
[[[[[UIApplication sharedApplication] delegate] window] rootViewController] presentViewController:controller animated:YES completion:nil];
}
-(void)authenticatedPlayer:(GKLocalPlayer *)player
{
NSLog(#"%#,%#,%#",player.playerID,player.displayName, player.alias);
gameCenterAuthenticationComplete = YES;
}
-(void)disableGameCenter
{
}
But I need to know from a different object if that gameCenterAuthenticationComplete equals YES.
You can use a delegate pattern. It's far easier to use than KVO or local notifications and it's used a lot in Obj-C.
Notifications should be used only in specific situations (e.g. when you don't know who wants to listen or when there are more than 1 listeners).
A block would work here but the delegate does exactly the same.
You could use KVO (Key-Value Observing) to watch a property of your object, but I'd rather post a NSNotification in your case.
You'll need to have the objects interested in knowing when Game Center login happened register themselves to NSNotificationCenter, then post the NSNotification in your Game Center handler. Read the Notification Programming Topics for more details !
If there is a single method to execute on a single delegate object, you can simply call it in the setter. Let me give a name to this property:
#property(nonatomic,assign, getter=isLogged) BOOL logged;
It's enough that you implement the setter:
- (void) setLogged: (BOOL) logged
{
_logged=logged;
if(logged)
[_delegate someMethod];
}
Another (suggested) way is to use NSNotificationCenter. With NSNotificationCenter you can notify multiple objects. All objects that want to execute a method when the property is changes to YES have to register:
NSNotificationCenter* center=[NSNotificationCenter defaultCenter];
[center addObserver: self selector: #selector(handleEvent:) name: #"Logged" object: nil];
The handleEvent: selector will be executed every time that logged changes to YES. So post a notification whenever the property changes:
- (void) setLogged: (BOOL) logged
{
_logged=logged;
if(logged)
{
NSNotificationCenter* center=[NSNotificationCenter defaultCenter];
[center postNotificationName: #"Logged" object: self];
}
}