I have a UITableView that gets its data from an array. Populating that array, however, requires downloading and parsing large chucks of data from the Web. That being the case, I'd like perform those operations in a background thread. Here's what I've got so far:
#interface MyClass()
#property (nonatomic, strong) NSArray *model;
#end
#implementation MyClass
- (void) getData {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:SOME_URL]];
if (data) {
NSMutableArray *arr = [NSMutableArray array];
//Populate arr with data just fetched, which can take a while
dispatch_async(dispatch_get_main_queue(), ^{
//THIS IS THE STEP I AM UNSURE ABOUT. SHOULD I DO:
self.model = arr;
//OR
self.model = [NSArray arrayWithArray:arr];
//OR
self.model = [arr copy];
//OR
//something else?
});
}
});
}
#end
Thank you!
// you can use any string instead "mythread"
dispatch_queue_t backgroundQueue = dispatch_queue_create("com.mycompany.myqueue", 0);
dispatch_async(backgroundQueue, ^{
// Send Request to server for Data
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:SOME_URL]];
dispatch_async(dispatch_get_main_queue(), ^{
// Receive Result here for your request and perform UI Updation Task Here
if ([data length] > 0) {
// if you receive any data in Response, Parse it either (XML or JSON) and reload tableview new data
}
});
});
You should do:
self.model = arr;
The reference to self calls the setter, which will release any previous references in that variable and then add a reference count to arr so it doesn't go out of scope. If you were accessing the ivar directly you would do:
[model release];
model = [arr retain];
Take a look at this link Understanding dispatch_async and this https://developer.apple.com/library/ios/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html
You should add DISPATCH_QUEUE_PRIORITY_BACKGROUND instead of DISPATCH_QUEUE_PRIORITY_DEFAULT to run this on the background.
By using DISPATCH_QUEUE_PRIORITY_DEFAULT you just made your task be classified as a normal task. If you have changed it to higher or lower priority, the queue would have run it before or after some other tasks, respectively.
Related
In my iOS application I am using Core Data.
For table View listing I use NSFetchedResultsController and
Connecting to Remote store I use NSIncrementalStore.
My FetchedResultsController Context is having MainQueue Cuncurrency type.(I couldn't do it with a PrivateQueueCurrencyTYpe).
For resolving Fault, for a many relationship, the executeFetchResultsCall:withContext:error method is executed from my IncrementalStore subclass.
Inside the executeFetchResults method, I will invoke the API (connecting to remote server) if it is not available in my local database.
myarray = [object representationsForRelationship:#"manyconnection" withParams:nil];
Now I need the results array in return synchronously to be returned to the ExecuteFetchResultsMethod. Also this operation should be executed on Main thread.
So I am having only one option to fetch the results from server which causes the UI to unresponsive for the specified sleep time.
-(RequestResult*)makeSyncJsonRequest{
__block RequestResult *retResult = [[RequestResult alloc] init];
__block BOOL block = YES;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
void (^resultBlock)(RequestResult*) = ^(RequestResult* result){
if(!retResult.error)
retResult = result;
block = NO;
dispatch_group_leave(group);
};
// Add a task to the group
dispatch_group_async(group, queue, ^{
// Some asynchronous work
dispatch_group_enter(group);
[self makeAsyncJsonRequestWithBlock:resultBlock];
});
// Do some other work while the tasks execute.
// When you cannot make any more forward progress,
// wait on the group to block the current thread.
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
return retResult;
}
As the above operation is being executed on main thread,the UI hangs.
Inorder to make the UI smoother, I need to carry out the executeFetchrequest in some other thread which is not possible.
It also expects the results array in return.
Is there any option to carry out this something like in a completion handler manner?
or
Any alternate methods or design to work this proper.
Any Help is highly appreciated.
This is a skeleton, using a dispatch_group, assuming you are using an NSFetchedResultsController to update your UITableView:
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// do your setup (FetchedResultsController and such)
[self syncData];
}
- (void)syncData
{
NSArray<Entity*> *results = [self fetchData];
BOOL needsUpdateFromServer = YES; // check your results and set this bool accordingly
if (!needsUpdateFromServer) {
return;
}
__block ServerResponse *fromServer = nil;
__block dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[self loadDataFromServer:^(ServerResponse *response) {
fromServer = response;
dispatch_group_leave(group);
}];
dispatch_group_notify(group,dispatch_get_main_queue(),^{
[self persistData:fromServer];
/*
According to our discussion, you are using an NSFetchedResultsController.
So your tableView should update automatically after persisting the data.
*/
});
}
- (void)loadDataFromServer:(void (^)(ServerResponse *response))completion
{
// [someDownloadService downloadDataFromServerInBackgroundWithCompletion:^(ServerResponse* response){
dispatch_async(dispatch_get_main_queue(), ^{
completion(response);
});
// }];
}
- (NSArray<Entity*>*)fetchData
{
NSArray<Entity*> *results = nil;
// fetch from core data and return it
return results;
}
- (void)persistData:(NSArray<ServerResponse*> *)serverResponses
{
// parse whatever you get from server
// ... and persist it using Core Data
}
#end
I have 3 blocks of code that must execute one by one after previous finished. My implementation not works. I need some help to do it. My code bellow.
for (NSString *i in items)
{
[[RequestAPI sharedInstance]downloadImage:i completion:^(AFHTTPRequestOperation *operation, UIImage *image, NSError *error) {
//here main thread I receive images and go to BG
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// here I save image on disk and get path
NSString *path = [ImageManager saveImageToDisk:image toEntity:entity withparams:#{#"save" : #"lala"}];
__block NSMutableDictionary *attachments = [NSMutableDictionary dictionary];
__block NSMutableArray *photoPaths = [NSMutableArray array];
dispatch_async(dispatch_get_main_queue(), ^{
//block1. here I load entity and dictionary from it with NSKeyedUnarchiver from CD and set to it image path
if (entity.attachments)
{
attachments = [NSKeyedUnarchiver unarchiveObjectWithData:entity.attachments];
if (attachments[type])
{
photoPaths = attachments[type];
}
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
//block2. here I check all images equality ti themselves in entity
BOOL haveDublicate = NO;
NSData *i = [ImageManager imageDataFromPath:path];
NSArray *photoImages = [ImageManager imageDatasFromPaths:photoPaths];
for (NSData *saved in photoImages)
{
if ([saved isEqualToData: i])
{
haveDublicate = YES;
}
}
if (!photoPaths)
{
photoPaths = [NSMutableArray array];
}
dispatch_async(dispatch_get_main_queue(), ^{
//block3. and finally if all ok I save image path, change load counter and post notification
if (path.length
&& ![photoPaths containsObject:path]
&& !haveDublicate
)
{
[photoPaths addObject:path];
[savedLinks setObject:photoPaths forKey:type];
entity.attachments = [NSKeyedArchiver archivedDataWithRootObject:savedLinks];
[self saveContext];
}
[RequestAPI sharedInstance].downloadsCount -= 1;
[[NSNotificationCenter defaultCenter]postNotificationName:kReloadFeedData object:nil];
});
});
});
});
}];
As dispatch_async says they will be executed asynchronous and not synchronous as you expected. Use dispatch_sync instead.
If you want to execute your code on a separate thread simple do the following
// create your thread
dispatch_queue_t queue = dispatch_queue_create("My Other Queue", 0);
// execute your synchronous block on the thread you've just created
dispatch_sync(queue,^{
// add your implementation here to be executed on your separate thread
dispatch_sync(dispatch_get_main_queue()^{
// update your UI here. Don't forget you can only update UI on the main thread
});
});
I am kind of new to web service calls and threading in iOS. I have a ViewController in my app that contains a tableview control. I am populating the table with data obtained via a JSON web service. The JSON web service is called on its own thread, during which I am populating an NSArray and NSDictionary.
My array and dictionary seem like they are going out of scope since my NSLog statement is returning zero for the array count even though while in fetchedData the array is fully populated.
Can someone offer an explanation as to why my array and dictionary objects are empty outside of the thread?
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *serviceEndpoint = [NSString stringWithFormat:
#"http://10.0.1.12:8888/platform/services/_login.php?un=%#&pw=%#&ref=%#",
[self incomingUsername], [self incomingPassword], #"cons"];
NSURL *url = [NSURL URLWithString:serviceEndpoint];
dispatch_async(kBgAdsQueue, ^{
NSData *data = [NSData dataWithContentsOfURL:url];
[self performSelectorOnMainThread:#selector(fetchedData:) withObject:data waitUntilDone:YES];
});
NSLog(#"ARRAY COUNT: %d\n", [jsonArray count]);
}
-(void)fetchedData:(NSData*)responseData{
NSError *error;
jsonDict = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&error];
jsonArray = [[jsonDict allKeys]sortedArrayUsingSelector:#selector(compare:)];
for(NSString *s in jsonArray){
NSLog(#"%# = %#\n", s, [jsonDict objectForKey:s]);
}
}
When you use dispatch_async, that bit of code doesn't block. This means your array count log statement triggers before fetchedData is called, so your dictionary and array are still empty. Look at the order of your log statements - you should see array count before your logging of the dictionary.
// Executes on another thread. ViewDidLoad will continue to run.
dispatch_async(kBgAdsQueue, ^{
NSData *data = [NSData dataWithContentsOfURL:url];
[self performSelectorOnMainThread:#selector(fetchedData:) withObject:data waitUntilDone:YES];
});
// Executes before the other thread has finished fetching the data. Objects are empty.
NSLog(#"ARRAY COUNT: %d\n", [jsonArray count]);
You'll need to finish populating your TableView after the data returns (ie in FetchData:).
The log statement within viewDidLoad SHOULD report that the array is empty as it has not been populated at that time. Calling dispatch_async causes that block of code to be run asynchronously and allows the viewDidLoad function to finish before the block does. That's why you have nothing in your array at the end of viewDidLoad.
You are trying to print count of jsonArray elements before it was populated. Here is what happens:
You prepare url
You create new thread to fetch some date. Execution of this thread may take some time (depending on connection speed and amount of data).
You are accessing jsonArray while thread is executing and fetchedData: was not called
Also, suggestion:
Don't use dataWithContentsOfURL: methods. Better take a look at some networking frameworks like AFNetworking.
I have a method that should return an array which is populated in a background thread. I would like to wait with the return statement until the array is completely populated. How would I do that ? I know how to process data with no return type, but I would like to call this function and get the populated array.
Example (Here the array ends up empty because it returns before array is populated - I would like to do something to return the populated array) :
-(NSArray*)fetchSomeArray{
__block NSArray *arrayToReturn;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,(unsigned long)NULL), ^(void) {
NSArray *someArray = ...; // getting some array from database
arrayToReturn = [NSMutableArray new];
for(int i=0;i<someArray.count;i++){
[arrayToReturn addObject:...];
}
});
return arrayToReturn;
}
Use delegation, or blocks and take out the return arrayToReturn;. After your "for" and inside the "dispatch_async":
dispatch_async(dispatch_get_main_queue(),^{
[myDelegate passComputedArray:arrayToReturn];
});
- (void)someMethod:(void (^)(BOOL result))completionHandler {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//all your functionality
// Check that there was not a nil handler passed.
if( completionHandler ){
//your function to so that you can return
}
});
});
Pass the completionHandler and once done, do your function
If you want to wait until it returns, it is unnecessary to background it.
-(NSArray*)fetchSomeArray{
NSArray *someArray = ...; // getting some array from database
return someArray;
}
But I'll bet that you really want to do something with that returned value which takes some time to compute without blocking the main thread, so you can instead pass the array back to a method in the same object or elsewhere.
-(void)fetchSomeArray{
__block id bself = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,(unsigned long)NULL), ^(void) {
NSArray *someArray = ...; // getting some array from database
[bself arrayFetched:someArray];
});
return arrayToReturn;
}
-(void)arrayFetched:(NSArray*)array{
// do something with the returned array
}
The easiest way to deal with this would be to call a function once the array is complete and process the array there.
-(void)fetchSomeArray{
__block NSArray *arrayToReturn;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,(unsigned long)NULL), ^(void) {
NSArray *someArray = ...; // getting some array from database
arrayToReturn = [NSMutableArray new];
for(int i=0;i<someArray.count;i++){
[arrayToReturn addObject:...];
}
[self finishedArrayResult:arrayToReturn];
});
}
But if you wanted that return function to update anything n the UI you would need to run that function in the main thread and not the background. To do that you could either use performSelector: onThread: withObject: waitUntilDone: or you could use another dispatch_async using the main thread instead of global thread.
I get this warning in Xcode
warning: Attempting to create USE_BLOCK_IN_FRAME variable with block
that isn't in the frame.
Xcode redirect me to my NSStream
_naturStream = [[NSInputStream alloc] initWithData:natur];
It is random when it does this error, and my application crashes when it is triggered. Anyone tried similar problem ?
thanks
EDIT
in the appDelegate.h
#property (nonatomic, strong) NSInputStream *naturStream;
In the appDelegate.m:
NSData *natur = [NSData dataWithContentsOfURL:[NSURL URLWithString:_locString]];
_naturStream = [[NSInputStream alloc] initWithData:natur];
[_naturStream open];
if (_naturStream) {
NSError *parseError = nil;
id jsonObject = [NSJSONSerialization JSONObjectWithStream:_naturStream options:NSJSONReadingAllowFragments error:&parseError];
if ([jsonObject respondsToSelector:#selector(objectForKey:)]) {
for (NSDictionary *natur in [jsonObject objectForKey:#"results"]) {
_poi = [[POI alloc]init];
[_poi setTitle:[natur objectForKey:#"title"]];
[_poi setLat:[[natur objectForKey:#"lat"]floatValue]];
[_poi setLon:[[natur objectForKey:#"lng"]floatValue]];
[_poi setDistance:[natur objectForKey:#"distance"]];
[_poi setWebUrl:[natur objectForKey:#"webpage"]];
[_naturArray addObject:_poi];
}
}
}
else {
NSLog(#"Failed to open stream.");
}
[_naturStream close];
}
I realized that i forgot [_naturStream close] i don't know if it has solved the problem or not ?
EDIT
Another thing,.... I use a Thread for fetching the JSON data:
dispatch_queue_t jsonParsingQueue = dispatch_queue_create("jsonParsingQueue", NULL);
// execute a task on that queue asynchronously
dispatch_async(jsonParsingQueue, ^{
[self parseJSON];
dispatch_async(dispatch_get_main_queue(), ^{
[_kortvisning updateAnno];
[visListe updateList];
});
});
// release the dispatch queue
dispatch_release(jsonParsingQueue);
Sounds like you're using ARC - if _naturStream is an instance variable for an objective C class, you might need to pull it out and add a __block reference so that ARC knows the scope correctly - but I'm guessing because I don't see how the block is used with the NSInputStream (if you post that part we might know). A good bit is here: http://nachbaur.com/blog/using-gcd-and-blocks-effectively
-- edit --
Ok, now that you posted the rest, I bet it has to do with the _kortvisning and visListe variables. I think you want to pull those out right after you create your queue something like
__block KortVisning *localKortVisning = _kortvisning;
__block NSMutableArray *localVisListe = visListe;
Then access those directly from your final completion handler you're sending back to the main queue.