So, here is my question.
What I want to do, is to queue resources loading; so that every resource request load is done one at a time, and one after the other (controlling the order I receive those resources).
What would be the correct, and cleaner way to complete such a behaviour?
Ok, I think I got it.
Every RKObjectLoader is a subclass of a RKRequest object. So it may be associated to a customized RKRequestQueue.
Let that RKRequestQueue be configured so that only 1 element can be treated at a time we could achieve the ordering. By setting its concurrency to 1.
I have the pseudo-code :
Create a RKRequestQueue and define its concurrency to 1.
Mark it as suspended, so that it will wait load until I'm done with queueing my resource loading requests.
Loop over my resource loading requests and add them in the order I want them to be executed.
We start with a lazy loading of the request queue
- (RKRequestQueue *)mainDownloadRequestQueue {
if (!_mainDownloadRequestQueue) {
// Creating the request queue
RKRequestQueue * queue = [RKRequestQueue requestQueue];
//queue.delegate = self;
queue.concurrentRequestsLimit = 1;
queue.showsNetworkActivityIndicatorWhenBusy = YES;
// Start processing!
[queue start];
_mainDownloadRequestQueue = [queue retain];
}
return _mainDownloadRequestQueue;
}
And the main methods could/should look like this, we set the queue in the blocks, just before it is checked by RestKit that a queue is available for handling download.
- (void)setUpMainQueue {
// We lock the download queue so that, no download will start until, we want it
[[self mainDownloadRequestQueue] setSuspended:YES];
// Fill up the queue
[self fillQueueWithMandatoryDownloads];
// No, let's start and wait for data to be there
[[self mainDownloadRequestQueue] setSuspended:NO];
}
- (void)fillQueueWithMandatoryDownloads {
// Add the first request
[self addLanguagesRequest];
// Add another request
[self addLanguagesRequest];
// … Add any other request
}
- (void)addLanguagesRequest {
// Load the object model via RestKit
RKObjectManager *objectManager = [RKObjectManager sharedManager];
objectManager.client.baseURL = [RKURL URLWithString:kFoundationHost];
__unsafe_unretained OMResourceLoader * wSelf = self;
[objectManager loadObjectsAtResourcePath:#"/sources/api/languages" usingBlock:^(RKObjectLoader * loader) {
// Set the queue there, this is the one defined
loader.queue = [wSelf mainDownloadRequestQueue];
// Do other configuration or behaviour for that
loader.onDidLoadObjects = ^(NSArray *objects) {
[[OMLogManager sharedLogManager] log:[objects description] logLevelParam:OM_LOG_LEVEL_INFO exceptionParam:nil errorParam:nil];
};
}];
}
- (void)addCategoriesRequest {
// Load the object model via RestKit
RKObjectManager *objectManager = [RKObjectManager sharedManager];
objectManager.client.baseURL = [RKURL URLWithString:kFoundationHost];
__unsafe_unretained OMResourceLoader * wSelf = self;
[objectManager loadObjectsAtResourcePath:#"/sources/api/categories" usingBlock:^(RKObjectLoader * loader) {
// Set the queue
loader.queue = [wSelf mainDownloadRequestQueue];
loader.onDidFailLoadWithError = ^(NSError * error) {
[[OMLogManager sharedLogManager] log:#"Categories loading error" logLevelParam:OM_LOG_LEVEL_ERROR exceptionParam:nil errorParam:error];
};
loader.onDidFailWithError = ^(NSError * error) {
[[OMLogManager sharedLogManager] log:#"Categories loading error" logLevelParam:OM_LOG_LEVEL_ERROR exceptionParam:nil errorParam:error];
};
loader.onDidLoadResponse = ^(RKResponse *response) {
[[OMLogManager sharedLogManager] log:[[response bodyAsString] description] logLevelParam:OM_LOG_LEVEL_INFO exceptionParam:nil errorParam:nil];
};
loader.onDidLoadObjects = ^(NSArray *objects) {
[[OMLogManager sharedLogManager] log:[objects description] logLevelParam:OM_LOG_LEVEL_INFO exceptionParam:nil errorParam:nil];
};
}];
}
Related
The following code is freezing my UI. Cant do any actions.
- (void) longPoll {
//create an autorelease pool for the thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSError* error = nil;
NSURLResponse* response = nil;
NSURL* requestUrl = [NSURL URLWithString:#"myurl"];
NSURLRequest* request = [NSURLRequest requestWithURL:requestUrl];
//send the request (will block until a response comes back)
NSData* responseData = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response error:&error];
dispatch_async(dispatch_get_main_queue(), ^{
[self dataReceived:responseData];
});
});
//compose the request
//pass the response on to the handler (can also check for errors here, if you want)
//clear the pool
}
- (void) startPoll {
//not covered in this example: stopping the poll or ensuring that only 1 poll is active at any given time
[self performSelectorInBackground:#selector(longPoll) withObject: nil];
}
- (void) dataReceived: (NSData*) theData {
//process the response here
NSDictionary *dict=[theData JSONValue];
[self ParseJson:dict];
[self performSelectorInBackground:#selector(longPoll) withObject: nil];
}
Can anyone give me the exact reason for it or any alternative to do the similar code for continues polling.
You are creating an infinite loop:
longCall calls dataReceived calls longCall etc....
What exactly you want to do. There is infinite loop between longPool and dataReceived
there should be mechanism where you stop this call and you can use
#autorelease {} block for create autorelease pool in ARC Enabled project and
NSAutoReleasePool class obj for Without ARC.
I have a loadImages method
- (void)loadImages {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//this method loads images from a url, processes them
//and then adds them to a property of its view controller
//#property (nonatomic, strong) NSMutableArray *storedImages;
});
}
When a button is clicked a view enters the screen, and all the images that currently exist in _storedImages are displayed
- (void)displayImages {
for (NSString *link in _storedImages) {
//displayImages
}
}
The problem with this setup is, that if the user clicks the button before all the images are loaded, not all the images are presented on the screen.
Hence, I would like to display an SVProgressHUD if the button is clicked, and the loadImages dispatch_async method is still running.
So, how do I keep track of when this dispatch_async is completed? Because if I know this, then I can display an SVProgressHUD until it is completed.
On a side note, if you know how to load/display the images dynamically that info would be helpful too, i.e. you click the button and then as you see the current images, more images are downloaded and displayed
Thank you from a first time iOS developer!
Ok I found a solution but it is incredibly inefficient, I am sure there's a better way to do this
1. Keep a boolean property doneLoadingImages which is set to NO
2. After the dispatch method finishes, set it to YES
3. In the display images method, have a while (self.doneLoadingImages == NO)
//display progress HUD until all the images a loaded
Keep in mind that NSMutableArray is not thread-safe. You must ensure that you don't try to access it from two threads at once.
Using a boolean to track whether you're still loading images is fine. Make loadImages look like this:
- (void)loadImages {
self.doneLoadingImages = NO;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (1) {
UIImage *image = [self bg_getNextImage];
if (!image)
break;
dispatch_async(dispatch_get_main_queue(), ^{
[self addImage:image];
});
}
dispatch_async(dispatch_get_main_queue(), ^{
[self didFinishLoadingImages];
});
});
}
So we send ourselves addImage: on the main queue for each image. The addImage: method will only be called on the main thread, so it can safely access storedImages:
- (void)addImage:(UIImage *)image {
[self.storedImages addObject:image];
if (storedImagesViewIsVisible) {
[self updateStoredImagesViewWithImage:image];
}
}
We send ourselves didFinishLoadingImages when we've loaded all the images. Here, we can update the doneLoadingImages flag, and hide the progress HUD if necessary:
- (void)didFinishLoadingImages {
self.doneLoadingImages = YES;
if (storedImagesViewIsVisible) {
[self hideProgressHUD];
}
}
Your button action can then check the doneLoadingImages property:
- (IBAction)displayImagesButtonWasTapped:(id)sender {
if (!storedImagesViewIsVisible) {
[self showStoredImagesView];
if (!self.doneLoadingImages) {
[self showProgressHUD];
}
}
}
What I usually do with this kind of problem is basically the following (rough sketch):
- (void)downloadImages:(NSArray*)arrayOfImages{
if([arrayOfImages count] != 0)
{
NSString *urlForImage = [arrayOfImages objectAtIndex:0];
// Start downloading the image
// Image has been downloaded
arrayOfImages = [arrayOfImages removeObjectAtIndex:0];
// Ok let's get the next ones...
[self downloadImages:arrayOfImages];
}
else
{
// Download is complete, use your images..
}
}
You can pass the number of downloads that failed, or even a delegate that will receive the images after.
You put a "note" and this may help, it allows you to display the images as they come down, I store the URLs in an array (here it is strings but you could do array of NSURL).
for(NSString *urlString in imageURLs)
{
// Add a downloading image to the array of images
[storedImages addObject:[UIImage imageNamed:#"downloading.png"]];
// Make a note of the image location in the array
__block int imageLocation = [storedImages count] - 1;
// Setup the request
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
[request setTimeoutInterval: 10.0];
request.cachePolicy = NSURLRequestReturnCacheDataElseLoad;
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue currentQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// Check the result
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (data != nil && error == nil && [httpResponse statusCode] == 200)
{
storedImages[imageLocation] = [UIImage imageWithData:data];
[self reloadImages];
}
else
{
// There was an error
recommendedThumbs[imageLocation] = [UIImageimageNamed:#"noimage.png"];
[self reloadImages]
}
}];
}
You then need another method which reloads the display. If the images are in a table then [[tableview] reloaddata];
-(void)reloadImages
iam using restKit, to send and receive data from server... iam getting back
{
"request":"globalUpdate",
"updateRevision":2,
"updatedObjects":{
"users":[
{
id:"someid1",
name:"somename"
},
{
id:"someid2",
name:"somename2",
}
]
}
}
i want to use
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:nil usingBlock:^(RKObjectLoader * loader){)];
to load only objects inside updatedObjects into CoreData and request, updateRevision into NSDictionary
so in
loader.onDidLoadObjects = ^(NSArray *objects) {
}
the first object is the Dictionary and the later one are CoreData
Well this is the matter of your choice which object you use for the coredata and which for your own purpose. RKObjectLoader also provides onDidLoadResponse block which has a reference to the response which you could use for your own use.
[myobjectManager loadObjectsAtResourcePath:resourcePath usingBlock:^(RKObjectLoader *loader) {
loader.mappingProvider = [RKObjectMappingProvider mappingProviderUsingBlock:^(RKObjectMappingProvider *provider) {
[provider setObjectMapping:[MyClass mapping] forKeyPath:#"updatedObjects"];
loader.onDidLoadObjects = ^(NSArray *objects){
};
loader.onDidLoadResponse = ^(RKResponse *response){
// NSData * data = [response data];
id object = [response parsedBody:nil];
// now parse the data yourself which will give you
// the entire json in NSData for and parse it,
// extract the component you need
};
loader.onDidFailWithError = ^(NSError *error){
};
}];
}];
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'm writing test cases for a wrapper class written around ASIHTTPRequest. For reasons I can't determine, my test cases complete with failure before the ASIHTTPRequest finishes.
Here's how the program flow works.
Start in my test case.
Init my http engine object, instruct it to create a new list
Create the new ASIHTTPRequest object and set it up.
Add the request to an operation queue.
Wait until that queue is empty
Check to see if my delegate methods were called and fail the test if they weren't.
Now, most of the time everything works fine and the test passes, but some of the time it fails because my delegate methods were called AFTER the operation queue returned control to my wait method.
Test Case
// Set my flags to 'NO'
- (void)setUp {
requestDidFinish = NO;
requestDidFail = NO;
}
- (void)testCreateList {
NSString *testList = #"{\"title\": \"This is a list\"}";
JKEngine *engine = [[JKEngine alloc] initWithDelegate:self];
NSString *requestIdentifier = [engine createList:jsonString];
[self waitUntilEngineDone:engine];
NSString *responseString = responseString_;
[engine release];
GHAssertNotNil(requestIdentifier, nil);
GHAssertTrue(requestDidFinish, nil);
GHAssertTrue([responseString hasPrefix:#"{\"CreateOrEditListResult\""], nil);
}
// Puts the test into a holding pattern until the http request is done
- (void)waitUntilEngineDone:(JKEngine *)engine {
[engine waitUntilFinishedRunning];
}
// The delegate method called on successful completion
- (void)requestFinished:(NSString *)requestIdentifier withResponse:(NSString *)response {
NSLog(#"request did finish");
requestDidFinish = YES;
responseIdentifier_ = [requestIdentifier retain];
responseString_ = [response retain];
}
Engine Code
- (NSString *)createList:(NSString *)list {
ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:[NSURL URLWithString:url]];
[request addRequestHeader:#"Content-Type" value:kContentType];
[request setRequestMethod:kPOST];
request.delegate = self;
[request appendPostData:[list dataUsingEncoding:NSUTF8StringEncoding]];
NSString *requestIdentifier = [NSString stringWithNewUUID];
[operationQueue_ addOperation:request];
[operationDictionary_ setObject:request forKey:requestIdentifier];
return requestIdentifier;
}
// This is the ASIHTTPRequest delegate method that's called on success
// but it sometimes isn't called until AFTER the operationQueue finishes running
- (void)requestFinished:(ASIHTTPRequest *)request {
DLog([request responseString]);
BOOL canNotifiyDelegate = [self.delegate respondsToSelector:#selector(requestFinished:withResponse:)];
if (canNotifiyDelegate) {
NSArray *keyArray = [operationDictionary_ allKeysForObject:request];
NSString *requestIdentifier = [keyArray objectAtIndex:0];
[operationDictionary_ removeObjectForKey:requestIdentifier];
if ([keyArray count] != 1) {
ALog(#"It looks like a request was added to the operation dictionary multiple times. There's a bug somewhere.", nil);
}
[self.delegate requestFinished:requestIdentifier withResponse:[request responseString]];
}
}
- (void)waitUntilFinishedRunning {
[operationQueue_ waitUntilAllOperationsAreFinished];
}
This is the way ASIHTTPRequest works. Delegate methods are called on the main thread, and calls to delegates do not block the request thread, so it's perfectly possible your delegates will be called after the queue finishes.
ASIHTTPRequest calls delegate methods on the main thread, by default GH-Unit runs its tests on a background thread. I'm still a little hazy on exactly what was going on, but forcing my network tests to run on the main thread fixed the problem.
I implemented the following method in my network test class.
- (BOOL)shouldRunOnMainThread {
return YES;
}