How to know if data is ready - iphone

I am using RestKit Framework to parse JSON data coming from a web service. Once I send the request URL the data will be downloaded automatically and will be available in one of the delegate methods of RestKit . I am writing this as a re-usable wrapper class, so that I create an instance of this class wherever required and pass only the URL to download and rest of the process will be done by the class. Now, I have a problem, how the instance will know that the data is available to use after download ? How can i achieve that ? NSNotification or delegation ? Any coding examples or suggestions might help. Thanks in advance.

I found that the most convenient way to handle the callback in RestKit was to use the completion blocks. You can pass around this completion block to achieve what you are trying to do.
Here is an example of how you can create a completion block and pass it around.
-(void)startRequest {
RKRequestDidLoadResponseBlock block = ^(RKResponse *response) {
//your completion code
};
[self sendRequestWithCompletionBlock:block];
}
-(void)sendRequestWithCompletionBlock:(RKRequestDidLoadResponseBlock)completionBlock {
RKRequest *request = [RKRequest requestWithURL:[NSURL URLWithString:#"www.google.com"]];
//configure request;
request.onDidLoadResponse = completionBlock;
}

Related

How to call custom method right before sessionTask resume in Alamofire

I need to record some data/info when Alamofire called resume to start the request.
(I used Swift in my project)
Is there anyway without do method_swizzling?
the timeline will be like this:
Call a request (put in request queue or execute right away) -> [custom method] -> SessionTask.resume()
I know Moya did something similar called WillSend. But I would like to know how to do it without using Moya.
Thank you.
If all you need to do is inspect various request elements you can use Alamofire 5's EventMonitor protocol, which is called during various lifetime events as Alamofire makes requests. There's a built in ClosureEventMonitor which allows you to set closures for those events. For example:
let monitor = ClosureEventMonitor()
monitor.requestDidCompleteTaskWithError = { (request, task, error) in
debugPrint(request)
}
let session = Session(eventMonitors: [monitor])
Please see our documentation for ClosureEventMonitor and the EventMonitor protocol itself.

Restangular - how to cancel/implement my own request

I found a few examples of using fullRequestInterceptor and httpConfig.timeout to allow canceling requests in restangular.
example 1 | example 2
this is how I'm adding the interceptor:
app.run(function (Restangular, $q) {
Restangular.addFullRequestInterceptor(function (element, operation, what, url, headers, params, httpConfig) {
I managed to abort the request by putting a resolved promise in timeout (results in an error being logged and the request goes out but is canceled), which is not what I want.
What I'm trying to do - I want to make the AJAX request myself with my own requests and pass the result back to whatever component that used Restangular. Is this possible?
I've been looking a restangular way to solve it, but I should have been looking for an angular way :)
Overriding dependency at runtime in AngularJS
Looks like you can extend $http before it ever gets to Restangular. I haven't tried it yet, but it looks like it would fit my needs 100%.
I'm using requestInterceptor a lot, but only to change parameters and headers of my request.
Basically addFullRequestInterceptor is helping you making change on your request before sending it. So why not changing the url you want to call ?
There is the httpConfig object that you can modify and return, and if it's close to the config of $http (and I bet it is) you can change the url and even method, and so change the original request to another one, entirely knew.
After that you don't need timeout only returning an httpConfig customise to your need.
RestangularConfigurer.addFullRequestInterceptor(function (element, operation, route, url, headers, params, httpConfig) {
httpConfig.url = "http://google.com";
httpConfig.method = "GET";
httpConfig.params = "";
return {
httpConfig: httpConfig
};
});
It will be pass on and your service or controller won't know that something change, that's the principle of interceptor, it allow you to change stuff and returning to be use by the next process a bit like a middleware. And so it will be transparent to the one making the call but the call will be made to what you want.

Ordering multiple Asynchronous JSON parses from website

Quick brief of what I'm doing: I have two arrays, they both contain 50% of the information for a table view. Why? Because one of the arrays pulls current information from the internet, while the other array has saved user data. I had no idea as to how to get current information from the internet in a non messy way, as I'm an amateur to objective-C let alone networking in Objective-C. Anyway, so the internet Array is pulling information that corresponds to the objects in the saved array (saved in Core Data) using AFNetworking. It's therefore Asynchronous, which I want. Now here comes the problem, or at least what I can garner from the situation.
I'm doing a for loop that effectively counts through the objects in the saved array and passes the unique ID for each object so that the corresponding information from the internet can be downloaded, parsed and added to the internet array. However, since the networking is Asynchronous, the loop is effectively downloading all the information from the internet at once. Therefore the objects are being written to the internet array in order of which downloaded first. Therefore savedArray[0] does not correspond to the object in internetArray[0]. This is a pretty big flaw as you can imagine, as when I'm inserting the values into my tableView, nothing really matches up/makes sense.
I'm really looking for a way to postpone the downloading of the information, until the previous download has been completed and added to the internetArray, how on earth do I do this? Now for the code. Here is where I get the appropriate key:
for ( int i = 0; i < [mySavedObjects count]; i++) {
MySavedObject* mySavedObject = [mySavedObjects objectAtIndex:i];
[self retrieveMissingInformation: myObject.Id];
[self.tableView reloadData];
}
And here is where I actually get the information (simplified for the sake of space):
- (void)retrieveMissingInformation:(NSString *) Id
{
// create URL here.
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSLog(#"Success");
// Do some safety checks & parse the JSON, returning a searchResult.
[searchResults addObject:searchResult];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON)
// Do some error messages etc.
}];
[queue addOperation:operation]; //where queue is an ivar NSOperationQueue.
}
Finally, in the cellForRowAtIndexpath, I these use both:
MySavedObject *mySavedObject = [mySavedObjects objectAtIndex:indexPath.row];
&
SearchResult *searchResult = [searchResults objectAtIndex:indexPath.row];
To get the values for the cell.
Sorry for the massively large wall of text. I'm not really good enough to explain things efficiently, often getting tongue-tied on the terminology and having to resort to making up my own exmaples. Any help with ordering this mess and I'd be really grateful.
Regards,
Mike.
Good work mate, you've stumble on a common design pattern.
As you've seen, with asynchronous request, execution happens almost concurrently so you're not guaranteed order when you think of "queues" in terms of a for loop.
The way you need to think of your download queue is in terms of recursion.
In other words, instead of:
// your current method of queue-ing the submission of data
for(int i = 0; i < arrData.count; i++)
{
[self doAsyncDataSendForIndex:i];
}
You need to start doing something like this:
// perform the first submit for the first element in your array
[self doAsyncDataSendForIndex:dataIndex];
// a completion callback method
-(void)asyncFinishDownloading
{
[self parseResult:serverReturnedData];
}
-(void)parseResult:(id)serverData
{
// do all the processing of the data that you need here
...
// update index for next round
dataIndex++;
// -----------------------------------------------------------------
// now at the end of this method, perform a recursive call
// of your async download method again
// -----------------------------------------------------------------
[self doAsyncDataSendForIndex:dataIndex];
}
Using the callback delegate way of queueing your download you are telling the code to only download the next data after it has finished processing the last asynchronous download.
Now you can use a number of libraries to help you with your asynchronous stuff. I myself use ASIHttpRequest and ASIFormData, you can use AFNetworking too which is newer.
-(void)asyncSubmitData:(id)someData
{
NSURL *url = [NSURL urlWithString:#"http://www.yourserver.com/...."];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
// setup your POST request parameters
[request setPostValue:someData forKey:#"someKey"];
...
// setup the completion callback delegate action
[request setCompletionBlock:^{
// process your server returned data here
NSString *serverResponse = [request responseString]; // raw server response
...
// increment your index and call your download method again
dataIndex++;
[self asyncSubmitData:[myArray objectAtIndex:dataIndex]];
}];
// setup fail block in case the server fails
[request setFailedBlock:^{
NSLog(#"Server error: %#", [[request error] localizedDescription];
}];
// start the asynchronous request now
[request startAsynchronous];
}
Hope that helps.
I think I understand your problem and it sounds like you're using the wrong data structures to store the data.
You have one array that is generating requests and the results to those requests are stored in a separate, unrelated array.
As you generate the request for information with an objectId, can you store the results in a NSDictionary instead of an array and use the objectId as the key to this array?
You have the NSURLRequest in the success method so you should be able to retrieve your objectId from the request through the NSURL.query if it is a parameter on the query string
Postponing the downloading of info and effectively making all of your network requests serial (one at at time) would work, but isn't a very nice solution, because it's often handy to have concurrent network requests going on - for example, allowing four requests at once would probably take less time than making all requests serially.
A much better solution is to correctly handle a piece of data when it comes back. To do this you need to handle the returned data in a more structured way. Rather than just appending it to an array, do one of the following:
A. Put returned data in a dictionary. The dictionary can map from an index or appropriate key to the returned data. (Hint: if you want to put an int as a key in a dictionary, construct an NSNumber containing that int.) This option is recommended over B.
B. Insert the returned data into an array. In order for ths to make sense, before you do any fetching, you should fill up your array with placeholder objects that denote "this bit of data hasn't been fetched yet". One possible placeholder object choice would be NSNull - it's just an object to denote null/nil.
So then you can return the correct item in cellForIndexPath: by dipping into your dictionary or array.
These two data structure ideas are just the most obvious ones that come to mind, there are doubtless other ways to crack this nut.

RestKit - Appending a parameter to all requests

I have an iOS app using RestKit in order to communicate with a RESTful Rails server. The server uses a basic session token for authentication of users. I want to append this token to every request sent using the RKObjectManager's methods.
I've tried creating a Category overload of NSManagedObject in order to use the following method:
- (void)willSendWithObjectLoader:(RKObjectLoader *)objectLoader
That works fine, however I see no way of appending to the object loader's params. I've even gone as far as to reserialize the object but there is no way to do that without it being sent using escape characters. For example, the following code will give me the object in JSON serialized form
RKObjectMapping *serializationMapping = [[[RKObjectManager sharedManager] mappingProvider] serializationMappingForClass:[self class]];
RKObjectSerializer *ser = [RKObjectSerializer serializerWithObject:self mapping:serializationMapping];
NSObject<RKRequestSerializable> *obj = [ser serializationForMIMEType:RKMIMETypeJSON error:nil];
But the object returned there is intend to be used as a param right away so the following code does not work
[params setValue:[LCSingletonLoggedInUser sharedLoggedInUser].sessionToken forParam:#"token"];
[params setData:obj.HTTPBody forParam:#"data"];
I've also tried various combinations of setObject and setData and obj.HTTPBody as well as just obj right away.
Actually appending obj to params in any way will always result in it adding escape characters which the server can't handle. Setting params = obj will give the correct values to the server but won't include the token.
How about adding it to queryParams?
NSString *resourcePath = [#"/products" stringByAppendingQueryParameters:_queryParams];
Where queryParams is a NSMutableDictionary where you add your token.

iPhone UIWebView: loadData does not work with certain types (Excel, MSWord, PPT, RTF)

My task is to display the supported document types on an iPhone with OS 3.x, such as .pdf, .rtf, .doc, .ppt, .png, .tiff etc.
Now, I have stored these files only encrypted on disk. For security reasons, I want to avoid storing them unencrypted on disk.
Hence, I prefer to use loadData:MIMEType:textEncodingName:baseURL: instead of loadRequest: to display the document because loadData allows me to pass the content in a NSData object, i.e. I can decrypt the file in memory and have no need to store it on disk, as it would be required when using loadRequest.
The problem is that loadData does not appear to work with all file types:
Testing shows that all picture types seem to work fine, as well as PDFs, while the more complex types don't. I get a errors such as:
NSURLErrorDomain Code=100
NSURLErrorDomain Code=102
WebView appears to need a truly working URL for accessing the documents as a file, despite me offering all content via the NSData object already.
Here's the code I use to display the content:
[webView loadData:data MIMEType:type textEncodingName:#"utf-8" baseURL:nil];
The mime-type is properly set, e.g. to "application/msword" for .doc files.
Does anyone know how I could get loadData to work with all types that loadRequest supports? Or, alternatively, is there some way I can tell which types do work for sure (i.e. officially sanctioned by Apple) with loadData? Then I can work twofold, creating a temp unencrypted file only for those cases that loadData won't like.
Update
Looks like I'm not the first one running into this. See here:
http://osdir.com/ml/iPhoneSDKDevelopment/2010-03/msg00216.html
So, I guess, that's the status quo, and nothing I can do about it.
Someone suggested a work-around which might work, though:
http://osdir.com/ml/iPhoneSDKDevelopment/2010-03/msg00219.html
Basically, the idea is to provide a tiny http server that serves the file (from memory in my case), and then use loadRequest. This is probably a bit more memory-intensive, though, as both the server and the webview will probably both hold the entire contents in memory as two copies then, as opposed to using loadData, where both would rather share the same data object. (Mind you, I'll have to hold the decrypted data in memory, that's the whole point here).
I experienced a very similar issue (i get my files from a server however) and saw your post and thought it was a dead end and then just by chance started to experiment on the device (iPad, in this instance) and it worked when i gave the baseURL as what i used to get it from the server and it worked but does not work on the simulator. I would try that, otherwise I would submit a bug report to Apple.
Here is solution via NSURLProtocol:
class CoreDataFileURLProtocol : NSURLProtocol {
var connection: NSURLConnection!
override class func canInitWithRequest(request: NSURLRequest) -> Bool {
return (request.URL.scheme == "coredatafile")
}
override class func canonicalRequestForRequest(request: NSURLRequest) -> NSURLRequest {
return request
}
override func startLoading() {
if let file_id = self.request.URL.absoluteString?.lastPathComponent {
if let file = SAFile.MR_findFirstByAttribute("file_id", withValue: file_id) as? SAFile {
let response = NSURLResponse(URL: request.URL, MIMEType: file.mime, expectedContentLength: Int(file.filesize), textEncodingName: "utf-8")
client?.URLProtocol(self, didReceiveResponse: response, cacheStoragePolicy: .NotAllowed)
client?.URLProtocol(self, didLoadData: file.data)
client?.URLProtocolDidFinishLoading(self)
}
}
}
override func stopLoading() {
}
}
Now you need only register class:
NSURLProtocol.registerClass(CoreDataFileURLProtocol.self)
And create a request with file_id:
let url = NSURL(scheme: "coredatafile", host: "myapp.com", path: "/\(file.file_id)")
webView.loadRequest(NSURLRequest(URL: url!))