Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions concerning problems with code you've written must describe the specific problem — and include valid code to reproduce it — in the question itself. See SSCCE.org for guidance.
Closed 9 years ago.
Improve this question
Right now, I got a GET request, after it's finished, I got the json back, and then I want to use the id from the json to execute another fetch request. It's like a nested fetch request right after another. For example:
+ (void)searchPhotoWithTags:(NSArray *)tags page:(NSInteger)page perPage:(NSInteger)perPage completionBlock:(void (^)(NSArray *, NSError *))block
{
NSDictionary *dict = #{#"method": #"search", #"api_key": kAppKey, #"tags": [tags componentsJoinedByString:#","], #"per_page": [NSString stringWithFormat:#"%d", perPage], #"page": [NSString stringWithFormat:#"%d", page], #"format": #"json"};
[[LDHttpClient sharedClient] getPath:#"" parameters:dict success:^(AFHTTPRequestOperation *operation, id responseObject) {
[[responseObject valueForKeyPath:#"photos.photo"] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
Photo *photo = [[Photo alloc] initWithPhotoId:[NSNumber numberWithInt:[obj[#"id"] integerValue]] andSecret:obj[#"secret"]];
//down below, I want to use photo.photoId to execute another request but the data is not completed. what's the better way to do this?
[PhotoSize getPhotoSizesWithPhotoId:photo.photoId completionBlock:^(NSArray *photoSizes, NSError *error) {
[photos addObject:#{#"photo": photo, #"sizes": photoSizes}];
}];
}];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];
}
If I understood your question correctly, I think what you're witnessing is a problem of asynchronous.
You are trying to loop through your photos dictionary, getting the photo size of each photo by means of sending another GET request which is an asynchronous operation. However, because of that, the next iteration of your loop already executes before your previous asynchronous operation has finished.
In this case, what you can do is use recursion to help you "iterate" or "loop" through your photos dictionary.
Requirements
For the below code to work, you'll need to create 2 properties
A property for storing your "photos" NSDictionary (e.g. NSDictionary *photosDict) in yourClass.h file
Another property for storing the enumerator of "photos" NSDictionary, which will be of type NSEnumerator, maybe call it "photosEnum"
Cleaning up your code a bit
In your original method, store the photos dictionary and from that, store the photosEnum enumerator too:
+ (void)searchPhotoWithTags:(NSArray *)tags page:(NSInteger)page perPage:(NSInteger)perPage completionBlock:(void (^)(NSArray *, NSError *))block
{
NSDictionary *dict = #{#"method": #"search", #"api_key": kAppKey, #"tags": [tags componentsJoinedByString:#","], #"per_page": [NSString stringWithFormat:#"%d", perPage], #"page": [NSString stringWithFormat:#"%d", page], #"format": #"json"};
[[LDHttpClient sharedClient] getPath:#"" parameters:dict success:^(AFHTTPRequestOperation *operation, id responseObject) {
// I assume you have a property of type NSDictionary created called "photos"
self.photosDict = [responseObject valueForKeyPath:#"photos"];
// Also create a property for the enumerator of type NSEnumerator
self.photosEnum = [self.photosDict objectEnumerator];
// ----------------------------------------------------------
// First call of our recursion method
//
// This will start our "looping" of our photos enumerator
// -----------------------------------------------------------
[self processPhotoDictionary];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Failed to get photos, error: %#", [error localizedDescription]);
}];
}
And finally, our recursion method does the processing of the photoSizes:
-(void)processPhotoDictionary
{
// ------------------------------------------------------
// Because self.photosEnum is a property of our class
// it remembers where it is "up to" in the "looping"
// ------------------------------------------------------
NSDictionary *photo = [self.photosEnum nextObject];
if(photo != nil)
{
Photo *photoObj = [[Photo alloc] initWithPhotoId:[NSNumber numberWithInt:[[photo valueForKey:#"id"] integerValue]]
andSecret:[photo valueForKey:#"secret"]];
[PhotoSize getPhotoSizesWithPhotoId:photoObj.photoId completionBlock:^(NSArray *photoSizes, NSError *error) {
[photos addObject:#{#"photo": photoObj, #"sizes": photoSizes}];
// ------------------------------------------------------
// Here we're using recursion to iterate through our
// enumerator due to asynchronous nature instead of the
// while loop.
// ------------------------------------------------------
[self processPhotoDictionary];
}];
}
}
Hope that helps.
In addition to the excellent answer of #Zhang, I would like to describe the common problem the OP is facing and how a "general solution" to this common problem may look like.
The common objective is:
Fetch a list of items from a server. Each item contains a URL which points to some other resource (an image for example).
When the list has been received, for each item in the list, fetch the resource (the image) given by the URL.
When implementing this in synchronous style the solution is obvious, and actually quite easy. However, when employing asynchronous style - which is the preferred way when doing networking - a workable solution becomes surprisingly complex, unless you know how to solve such problems ;)
The interesting part here is #2. Part #1 can be simply accomplished via an asynchronous call and a completion function where the completion function invokes part #2.
In order to make the things more easy to understand I will make a few simplifications and a few prerequisites:
In part #1 we obtain a list of elements, say an NSArray object containing our elements. Each element has a property, which is a URL to another resource.
Now, we can easily make the assumption that we already have an array of elements representing the N input values which shall be asynchronously processed in a loop - one after the other. Let us name that array "Source Array".
We will deal with asynchronous methods/functions. One way to have the method/function signal that it is finished with processing something asynchronously is a completion handler (a block).
The common signature for all completion handlers will be defined as follows:
typedef void (^completion_t)(id result);
Note: result shall represent the eventual result of the asynchronous function or method. It may be the kind of thing we expect (an image for example), or it may indicate an error, for example through passing and NSError object.
In oder to implement our part #2, we need an asynchronous method/function which takes an input (one element from the Input Array) and produces an output. This corresponds to your "fetch image resource" task. Later we need to apply this method/function for each element of the "Input Array" we got in part #1.
The generic function, a "transform function", will have this signature:
void transform(id input, completion_t completion);
The corresponding method will have this signature:
-(void) transformWithInput:(id)input
completion:(completion_t)completionHandler;
We can define a typedef for the function as below:
typedef void (^transform_t)(id input, completion_t completion);
Notice, that the result of the transform function or method will be passed through the completion handler's parameter. An synchronous function would just have a return value and return the result.
Note: the name "transform" is just a generic name. You can wrap your network request in a method and get such kind of "transform" function. In the OP's example, the URL would be the input parameter and the completion handler's result parameter would be the image fetched from the server (or an error).
Note: this and the following simplifications are just there to make the explanation of the asynchronous pattern easier to understand. In practice an asynchronous function or method may take other input parameters, and the completion handler may also have other parameters.
Now, the more "tricky" part:
Implementing A Loop In Asynchronous Style
Well, this is a bit "different" than in synchronous programming style.
Purposefully, we define some kind of forEach function or method doing this iteration. That function or method is itself asynchronous! And we know now that any asynchronous function or method will have a completion handler.
So, in case of a function we can declare our "forEach" function as follows:
`void transform_each(NSArray* inArray, transform_t task, completion_t completion);`
transform_each sequentially applies an asynchronous transform function task to each object in the input array inArray. When finished processing all inputs, it invokes the completion handler completion.
The completion handler's result parameter is an array containing the result of each transform function in the same order as the corresponding input.
Note: "sequentially" here means, that the inputs are processed one after the other. A variant of that pattern may process the inputs in parallel.
The parameter inArray is our "Input Array" gathered from step #1.
Parameter task is our asynchronous transform function, which can be virtually anything which takes an input and produces an output. It will be our asynchronous "fetch image" task from the OPs example.
And parameter completion is the handler which gets invoked when all inputs have been processed. It's parameter contains the output of each transform function in an array.
The transform_each can be implemented as follows. First we need a "helper" function do_each.
do_each is actually the heart of the whole pattern for implementing loops in an asynchronous style, so you may take a closer look here:
void do_each(NSEnumerator* iter, transform_t task, NSMutableArray* outArray, completion_t completion)
{
id obj = [iter nextObject];
if (obj == nil) {
if (completion)
completion([outArray copy]);
return;
}
task(obj, ^(id result){
[outArray addObject:result];
do_each(iter, task, outArray, completion);
});
}
The interesting part here, and the "common asynchronous pattern" or "idiom" for implementing loops (as a for_each function) is that do_each will be invoked from the completion handler of the transform function. That may look like a recursion, but actually it is not.
Parameter iter points to the current object within the array which shall be processed.
It will also be used to determine the stop condition: when the enumerator points past the end, we get a nil result from method nextObject. This eventually stops the loop.
Otherwise, the transform function task will be called with the current object as input parameter. The object will be asynchronously processed as defined by the task. When finished, the task's completion handler will be invoked. It's parameter result will be the output of the transform function. The handler needs to add the result to the resulting array outArray. Then it invokes the helper do_each again. This seems to be a recursive call, but it is actually not: the former do_each has already been returned. This is just another invocation of do_each.
Once we have that, we can simply complete our transform_each function as shown below:
void transform_each(NSArray* inArray, transform_t task, completion_t completion) {
NSMutableArray* outArray = [[NSMutableArray alloc] initWithCapacity:[inArray count]];
NSEnumerator* iter = [inArray objectEnumerator];
do_each(iter, task, outArray, completion);
}
NSArray Category
For our convenience we can easily create a Category for NSArray with a "forEach" method which asynchronously processes the inputs in sequence:
#interface NSArray (AsyncExtension)
- (void) async_forEachApplyTask:(transform_t) task completion:(completion_t) completion;
#end
#implementation NSArray (AsyncExtension)
- (void) async_forEachApplyTask:(transform_t) task completion:(completion_t) completion {
transform_each(self, task, completion);
}
#end
A code example can be found here on Gist: transform_each
A much more sophisticated concept to solve common asynchronous patterns is to utilize "Futures" or "Promises". I've implemented the concept of a "Promise" for Objective-C in a small library: RXPromise.
The above "loop" can be implemented including a the capability to cancel the asynchronous tasks via RXPromise, and of course a lot more. Have fun ;)
I think I might just solve this problem. I am not sure about it. It just works. I am using AFNetwroking's enqueueBatchOfHTTPRequestOperations function.
Related
I'm running a data model in one of my apps, where an event has an "eventType" relationship defined. This allows me to modify the look and feel of multiple events by changing their "eventType" relationship object.
The problem that I'm running into is that before I insert an event, I check if a typeRelationship for this object is present with the code below. This takes some time if I need to insert a large number of objects.
Can I cache the results of this fetch request (for example in NSMutableDictionary) and check that dictionary (local memory) to see if there is an NSManagedObject with the given EventIDEnum? Can I keep the cache alive forever, or will the underlying objects get "out of date" after a while?
-(Event*)insertAndReturnNewObjectWithTypeID:(EventIDEnum)eventTypeID date:(NSDate*)date
{
NSFetchRequest *eventTypesArray = [NSFetchRequest fetchRequestWithEntityName:#"EventType"];
eventTypesArray.predicate = [NSPredicate predicateWithFormat:#"SELF.id == %d", eventTypeID];
NSArray *eventTypes = [[DataManager sharedInstance].managedObjectContext executeFetchRequest:eventTypesArray error:nil];
if(eventTypes.count==0)
{
DLog(#"ERROR inserting event with type: %i NOT FOUND",(int)eventTypeID);
return nil;
}
else {
if(eventTypes.count !=1)
{
DLog(#"ERROR found %i events with type %i",eventTypes.count,(int)eventTypeID);
}
EventType* eventType = [eventTypes lastObject];
if(date)
{
// DLog(#"Returning object");
return [self insertAndReturnNewObjectWithEventType:eventType date:date];
}else {
// DLog(#"Returning object");
return [self insertAndReturnNewObjectWithEventType:eventType];
}
}
}
Thank you for taking a look at my question!
The array of objects returned by a fetch request cannot be cached. They are only valid as long as the NSManagedObjectContext that was used to query them has not been released. The NsManagedObject.objectID and the data you retrieve from the query can be cached and kept for as long as you like. You are probably better off copying the pertinent data and objectIDs into another object you cache and maintain separately from CoreData objects; and releasing the core data array that was returned by the fetch request.
The pattern you're using is often referred to as "find or create": look for an object whose uniquing characteristic matches, return it if it exists, create/populate/return it if it didn't exist.
One thing you can do to speed this up is to do the uniquing outside of Core Data. If it's possible based on your data, perhaps you can iterate over your EventIDEnum values, find the unique values you need to have available, and thus reduce the number of fetches you perform. You'll only search once for each EventIDEnum. As long as you're working within one thread/context, you can cache those.
When I'm writing this kind of code, I find it helpful to pass in the NSManagedObjectContext as a parameter. That allows me to use the find-or-create or bulk insert methods anywhere, either on the main thread or within a private queue/context. That would take the place of your [[DataManager sharedInstance] managedObjecContext] call.
working on xcode I realized that if I create a non-void method, I call it from a class / method, the result is processed optimally only if the action is immediate. I tried to do a test by inserting a delay and I realized that it no longer works.
I will write down here the example that I created:
Class A
//--------------------CLASS A
- (void)viewDidLoad {
[super viewDidLoad];
i = 0;
Class *classB = [[Class alloc] init];
i = [classB method1];
[self performSelector:#selector(method3) withObject:NULL afterDelay:1.8];
}
-(void)method3 {
NSLog(#"i = %i",i); // i = 0
}
Class B
//--------------------CLASS B
-(int)method1 {
[self performSelector:#selector(method2) withObject:NULL afterDelay:1];
return a;
}
-(void)method2 {
a = 800;
}
Obviously my problem is not something so trivial but I tried to make it easy to get an answer as thoroughly as possible, I was advised to use modal methods but I don't think that's the solution I was looking for.
What could I do to solve this?!
What you really need is a better understanding of asynchronous methods. In your method1, the variable a is never altered -- all you are doing is scheduling method2 to be called in the future and then returning the current state of variable a.
In Objective-C, there are a few different ways you can solve this problem. People most commonly use protocols and delegates to solve this issue. Here is a basic intro to protocols and delegates. Basically, you would want your class A object to be a delegate of your class B object. You could also use NSNotifications or blocks, although you should probably understand the usage of protocols and delegates (they are very important in Objective-C) before moving on to notifications and blocks.
What could I do to solve this?!
Where do you want to return the value to? In your example, method1 will complete long before method2 is ever invoked. If you want to preserve the value calculated by method2, you'll typically have that method store the value in one of ClassB's instance variables and possibly call some other method to continue processing.
If you really need method1 to return the result from method2, you'll need to call it synchronously (i.e. without -performSelector:withObject:afterDelay:). In this case, consider a) why you need the delay at all; and b) if you should perhaps be calling method1 after a delay instead of method2.
We'll be able to provide much better help if you can explain what the real-world problem that you're trying to solve is.
I am writing here because I tried almost everything to get this thing to solved. But failed I need a solution for this.
-(NSArray *)newPosts
{
NSArray *newDataArray = [[NSArray alloc]init];
//[self performSelectorOnMainThread:#selector(feedUrl:) withObject:nextObjectArray waitUntilDone:YES]
newDataArray = [feed getUserFeed:indexValue+1];
NSLog(#"~~~~~~~~~%#",newDataArray);
return newDataArray;
}
This method I call before reloading the table view and this function goes to class feed that is a NSobject class and call this function to update the array.
[self performSelectorOnMainThread:#selector(feedUrl:) withObject:nextObjectArray waitUntilDone:YES];
return nextObjectArray;
But when feedurl fetch the data from xml through nsurlconnection delegates functions the array returned and it is blank. So why I am not getting any result from array. I read many blog some body says use dispatch queue I tried but I don't find a place in dispatch queue where I can put my return statement. I also try wait/sleep to stop the execution but they are for other use. Can any body help to get rid of the problem. I need the array to be filled before it is returned .
My problem is that I'm using dispatch_async(dispatch_get_main_queue(), ^(void) { ... }); to call a method asynchronously, in this method depending on some conditions i set a boolean to YES. When this boolean is read in this method, it's always read by it's old value which is NO.
The weird thing is that when i made a breakpoint on the line where the bool is checked, everything went fine and as intended !
EDIT:
Here is the code where the threads are spawned
dispatch_async(dispatch_get_main_queue(), ^(void) {
[self drawFaceBoxesForFeatures:features forVideoBox:claporientation:curDeviceOrientation image:img];
});
The method itself
- (void)drawFaceBoxesForFeatures:(NSArray *)features forVideoBox:(CGRect)clap orientation: (UIDeviceOrientation)orientation image:(UIImage *)image;
{
if (![self getSendingRequestStatus]) {
NSLog(#"Sending req");
// send async request
dispatch_async(dispatch_get_main_queue(),^ {
sendingRequest = YES;
} );
}
}
It looks like you are modifying an ivar that was created outside of a block inside of the block. In order to do this and have the ivar hold the correct value, you are going to need to use the __block keyword like so:
#interface MyCoolClass : NSObject {
#private
__block int sendingRequest_;
}
As Jack Lawrence said in the commend above, "[the runtime] takes a snapshot of all of the relevant objects/variables at that point in time". The __block identifier will tell the runtime that it should not copy that ivar to the heap and will allow you to assign values to sendingRequest_ inside of a block, even if that block is simply being run on the main thread.
A lot of good information to start with (including the above) can be found in the Blocks Programming Guide.
When primitives are passed into a block they are copied. So if you put a primitive local or instance variable in a block and then later change it either in the same method that created the block (after the block creation) or another method it won't have any effect on the variable in the block. In the case of a local variable, just make sure you make any necessary changes before block creation. In the case of instance variables you could try accessing the instance variable by using some C: self->iVar or declare it as a property and access it through the property accessor: self.iVar.
The docs say:
you should implement methods of the
form validate:error:, as defined by the NSKeyValueCoding protocol
so lets say I have an attribute which is an int: friendAge
I want to make sure that any friend may not be younger than 30. So how would I make that validation method?
-validateFriendAge:error:
What am I gonna do in there, exactly? And what shall I do with that NSError I get passed? I think it has an dictionary where I can return a humanly readable string in an arbitrary language (i.e. the one that's used currently), so I can output a reasonable error like: "Friend is not old enough"... how to do that?
You can do anything you want in there. You can validate that the age is between ranges or any other logic you want.
Assuming there is an issue, you populate the error and have at least a value for the NSLocaliedDescriptionKey. That error will then be handed back to the UI or whatever this value is getting set from and allow you to handle the validation error. This means if there is other useful information you may need in the calling/setting method, you can add it into the NSError here and receive it.
For example:
-(BOOL)validateFreindAge:(NSNumber*)ageValue error:(NSError **)outError
{
if ([ageValue integerValue] <= 0) {
NSString *errorDesc = #"Age cannot be below zero.";
NSDictionary *dictionary = [NSDictionary dictionaryWithObject:errorDesc forKey:NSLocalizedDescriptionKey];
*error = [NSError errorWithDomain:#"MyDomain" code:1123 userInfo:dictionary];
return NO;
}
return YES;
}