In RestKit 0.2.x, overriding the NSURLConnectionDataDelegate implementation found in RKHTTPRequestOperation to achieve download progress indication - nsurlconnection

The newer RestKit 0.2.x is great but I cannot find any hook out of the RKHTTPRequestOperation : AFHTTPRequestOperation : AFURLConnectionOperation<NSURLConnectionDataDelegate> class to extend it's implementation of NSURLConnectionDataDelegate, which is quite necessary I believe to implement a download progress HUD.
What I would like to do is something like this:
RKObjectRequestOperation *operation =
[[RKObjectManager sharedManager]
appropriateObjectRequestOperationWithObject:nil
method:RKRequestMethodGET path:_requestPath
parameters:_requestParameters];
operation.RKHTTPRequestOperation.onDidReceiveResponse = ^(void)(NSURLConnection *connection, NSURLResponse *response) {
// store response or at least its estimated length
_response = response; //OR AT LEAST
// show progress hud at 0 / length = 0%
_estimated = response.expectedContentLength;
[SVProgressHUD showProgress:0.0f];
}
operation.RKHTTPRequestOperation.onDidReceiveData = ^(void)(NSURLConnection *connection, NSData *data) {
// show updated progress
[_myData appendData:data];
[SVProgressHUD showProgress:((float)myData.length/_estimated * 100)];
}
So, I'd like to know:
Is there a hook/protocol/block/selector/whatever I am not aware of that RestKit or AFNetworking provides to make this possible? (All I see is success and failure, which are both too late to be of use).
If not, what is the most appropriate and maintainable and safe way to swizzle/override/modify the NSURLConnectionDataDelegate callbacks to expose them to my client?

Similar to your proposition you should be able to use:
[operation.RKHTTPRequestOperation setDownloadProgressBlock:...]

Related

Using Apple Activity Tracing

Apple introduced Activity Tracing to support debugging asynchronous code. I have some difficulties using it properly. My example scenario is a small MacOS app just downloading a file:
- (IBAction)actionDownload:(NSButton *)sender {
os_activity_label_useraction("actionDownload");
os_log_t logDemo = os_log_create("ActivityTracingDemo", "demo");
os_log_debug(logDemo, "actionDownload start (1)");
os_activity_t actTop = os_activity_create(
"HTTP Download",
OS_ACTIVITY_NONE,
OS_ACTIVITY_FLAG_DETACHED);
os_activity_apply(actTop, ^{
os_log_debug(logDemo, "actionDownload start");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
(int64_t)(2 * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
os_log_debug(logDemo,
"actionDownload two second later");
});
NSURL *url = [NSURL URLWithString:
#"https://www.google.de/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png"];
// Get the current activity (or create a new one,
// if no current activity exists):
os_activity_t act = os_activity_create(
"HTTP Response",
OS_ACTIVITY_CURRENT,
OS_ACTIVITY_FLAG_IF_NONE_PRESENT);
NSURLSessionDownloadTask *downloadPhotoTask =
[ [NSURLSession sharedSession]
downloadTaskWithURL:url
completionHandler:^(NSURL *location,
NSURLResponse *response,
NSError *error)
{
os_activity_apply(act, ^{
// Now the same activity is active,
// that initiated the download.
os_log_debug(logDemo, "actionDownload received data (restored activity)");
});
os_log_debug(logDemo, "actionDownload received data");
}];
[downloadPhotoTask resume];
});
}
Filtering for my messages in Console.app I am getting:
Obviously NSURLSession does not call the completionHandler with the same activity active that initiated the download. I had to manually apply that activity within the callback. Is there a better way to do this? I thought that activities are designed to trace things across process. In that case it is not even working inside the same process without doing some extra work.
In the activity view of the Console.appI am getting:
The tree view looks promising, to get a quick overview about what application scenarios are triggered. Initially I thought that it is not
necessary to apply a new activity in an action callback and instead it would be possible to use os_activity_label_useraction to get the scenario displayed in Console.appactivity view on top level. Obviously that's not the case. I can't find that label in any log.
My solution is to create a new detached activity in actionDownload. This activity is visible on top level in the Console.appactivity view. What I dislike with this solution are two things:
First, I had to explicitly create a new activity with a new scope. This adds lots of noise to the source code. I have many very short action methods in my project. Also the messages view works without this. There I am just getting what I am interested in by filtering for subsystem, category and activity id.
Second, the connection to the initiating activity gets lost.
Would be great to get some hints about how to properly use Activity Tracing and especially the hierarchy thingy.

Is Caching NSMangagedObject Instances A Bad Idea?

I have a core data entity called Product which is initially populated when the user first logs into the application. It can then be loaded again, if the user requests a refresh.
The Product entity is queried at several places in the app. So, I decided to implement a simple cache that can be shared across the app. The cache keeps the Product NSManagedObjects
in a map. Is this a bad idea?
The ProductCache class:
#interface ProductCache ()
#end
#implementation ProductCache {
}
static NSDictionary *productsDictionary = nil;
static ProductCache *sharedInstance;
+ (ProductCache *)sharedInstance {
#synchronized (self) {
if (sharedInstance == nil) {
sharedInstance = [[self alloc] init];
[sharedInstance reload];
}
}
return sharedInstance;
}
- (void) reload{
NSMutableDictionary *productsMap = [[NSMutableDictionary alloc] init];
CIAppDelegate *delegate = (CIAppDelegate *) [UIApplication sharedApplication].delegate;
NSManagedObjectContext *managedObjectContext = delegate.managedObjectContext;
NSArray *allProducts = [CoreDataManager getProducts:managedObjectContext];
for (Product *product in allProducts) {
[productsMap setObject:product forKey:product.productId];
}
productsDictionary = productsMap;
}
- (NSArray *)allProducts{
return [productsDictionary allValues];
}
- (Product *) productForId:(NSNumber *)productId {
return productId ? [productsDictionary objectForKey:productId] : nil;
}
#end
Personally I would not cache Core Data objects like this. Core Data is your cache. When you also factor in the the issue of threading (NSManagedObject instances cannot cross the thread boundary) it becomes even more risky to put an in memory cache on top of Core Data. This does not even take memory issues into consideration which all things being equal, your cache is not going to perform as well as Apple's cache (i.e. Core Data).
If you need instant access (vs. the nanosecond access on disk) to your Core Data objects then consider copying your on disk cache into an in memory cache and then access that. However, if nanosecond access times are sufficient, leave it in Core Data and fetch the entities when you need them. Build convenience methods for the fetch if you find it repetitive but don't put a cache on top.
I'd like to say it's unnecessary, but I guess it really depends on a few factors.
How large is the Products object?
Is the querying/re-querying extremely CPU intensive?
Does your main thread get blocked?
Personally, I think you could avoid caching and simply re-query the Products object on a background thread whenever you need it.
The better question to ask is: Is it even worthwhile using Core Data for this specific aspect of your project?
Why not store the dictionary in NSUserDefaults (if your has is small)?

RestKit Best practices with the sendSynchronously method

I am trying to load objects synchronously with RestKit and to do that I am using [anObjectLoader sendSynchronously] on a background thread. Then in the RKObjectLoader didFinishLoad: the application is currently stopping at the first line: NSAssert([NSThread isMainThread], #"RKObjectLoaderDelegate callbacks must occur on the main thread");
Looking at the documentation, the sendSynchronously method from the RKRequest class says that the request will be synchronously requested and a hydrated response object will be returned.
This is a snapshot of my code:
RKObjectLoader *anObjectLoader = [self.objectManager loaderWithResourcePath:resourcePath];
NSLog(#"Response: %#", [anObjectLoader sendSynchronously]);
On console:
*** Assertion failure in -[RKManagedObjectLoader didFinishLoad:], ...RestKit/Code/ObjectMapping/RKObjectLoader.m:423
Is it Ok to use RestKit with synchronous calls?
Are there better ways to send synchronous requests?
Am I missing something?
You should never make synchronous calls. Use the send method and catch the response using either delegates or block callbacks. Among other things, this method is optimized for network bandwidth usage and also handles threading correctly.
As an aside, the reason RKObjectLoader requires the main thread is because that is where your main object context is.
I recently had this same question. I figured out how to send a synchronous call using blocks and it's actually quite nice. Basically you do whatever restkit call you were intending to do, but instead of setting the delegate to self, you use usingBlock. Then, within that block you can handle the various responses from your API call.
Block Example (APIUser is class I wrote that represents the current user):
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:[#"/api/users/" stringByAppendingString:userName] usingBlock:^(RKObjectLoader* loader) {
loader.onDidLoadResponse = ^(RKResponse *response) {
NSLog(#"Response: \n%#", [response bodyAsString]);
};
loader.onDidLoadObjects = ^(NSArray *objects) {
APIUser *apiUser = [objects objectAtIndex:0];
};
loader.onDidFailWithError = ^(NSError *error) {
NSLog(#"Response: \n%#", [response bodyAsString]);
};
}];
My original question and answer can be found here.

Get variable from void function in Objective C

I'm VERY new to Objective C and iOS development (like 5 hours new :-). I've got some code that calls an API to authenticate a user and returns a simple OK or FAIL. I can get the result to write to the console but what I need to do is get that result as part of my IBAction.
Here's the IBAction code:
- (IBAction) authenticateUser
{
[txtEmail resignFirstResponder];
[txtPassword resignFirstResponder];
[self performAuthentication];
if (authResult == #"OK")
What I need is for authResult to be the JSON result (OK or FAIL). Here is the code that gets the result:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[connection release];
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
NSLog(#"%#", responseString);
[responseData release];
NSMutableDictionary *jsonResult = [responseString JSONValue];
if (jsonResult != nil)
{
NSString *jsonResponse = [jsonResult objectForKey:#"Result"];
NSLog(#"%#", jsonResponse);
}
}
Thank you so much for any help and sorry if I'm missing something obvious!
I'm a little confused as to what's going on here... it looks like your -performAuthentication method must start an asynchronous network request via NSURLConnection, and your connection's delegate's -connectionDidFinishLoading: gets to determine the result of the request. So good so far? But your -authenticateUser method expects authResult to be determined as soon as -performAuthentication returns. If the network request is asynchronous, that's not going to happen. If I'm following you, I think you need to do the following:
Fix up -connectionDidFinishLoading: so that it actually sets authResult based on the Result value in jsonResponse. I'm sure you'd get around to this at some point anyway.
Change -authenticateUser such that it doesn't expect to have an answer immediately. You've got to give the network request a chance to do its thing.
Add another method, possibly called -authenticationDidFinish or something along those lines. Everything currently in -authenticateUser from the 'if (authResult...' to the end goes in this new method.
Call the new method from -connectionDidFinishLoading:.
Fix your string comparison. If you want to compare two strings in Cocoa, you say (for example):
if ([authResult isEqualToString:#"OK") { }

How to objects from a fetchedResultsController to a Plist?

Can someone help me. I have a coredata application but I need to save the objects from a fetchedResultsController into an NSDictionary to be used for sending UILocalNotifications.
Should I use an NSMutableSet, or a NSDictionary, or an array. I'm not used to using collections and I can't figure out the best way to do that.
Could you please give me clues on how to do that please ?
Thanks,
Mike
If I'm reading your question correctly, you're asking how you should pack objects into the userInfo dictionary of a UILocalNotification. Really, it's however works best for you; userInfo dictionaries are created by you and only consumed by you.
I'm not sure why you would be using an NSFetchedResultsController - that class is for managing the marshaling of managed objects between UI classes (like UITableView) efficiently, whereas here it sounds like you would be better off just getting an NSArray of results from your managedObjectContext and the corresponding request, like this:
NSError *error = nil;
NSArray *fetchedObjects = [myManagedObjectContext executeFetchRequest: myRequest error: &error];
if (array == nil)
{
// Deal with error...
}
where you have a pre-existing managed object context and request. You don't need to use an NSFetchedResultsController here.
From there, the simplest suggestion would be to build your userInfo dictionary like this:
NSDictionary* myUserInfo = [NSDictionary dictionaryWithObject: fetchedObjects forKey: #"AnythingYouWant"];
UILocalNotification *localNotif = [[UILocalNotification alloc] init];
// ... do other setup tasks ...
localNotif.userInfo = myUserInfo;
[[UIApplication sharedApplication] scheduleLocalNotification:localNotif];
[localNotif release];
Then when it comes time to receive that notification, you can read that dictionary like this:
- (void)application:(UIApplication *)app didReceiveLocalNotification:(UILocalNotification *)notif
{
NSArray* myFetchedObjects = [notif.userInfo objectForKey: #"AnythingYouWant"];
for(id object in myFetchedObjects)
{
// ... do other stuff ...
}
}
Now, hopefully that's clarified how the userInfo dictionary works. I don't know the details of your app, so it's hard to say, but I'm suspicious that actually passing fetched objects is NOT what you want to do here, mainly because I'm not sure that you have any guarantee that the receiving delegate method will be working with the same object context as the sending method. I would suggest perhaps putting the entity name and predicate in the dictionary and then refetching the objects at receive time with whatever the current MOC is at that moment.
Good luck!

Resources