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.
Related
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.
I am testing the following method with OCMock:
- (void)methodToTest {
[self.someObject doFirstActionWithParam:#"aparam" completion:(void(^)(BOOL success)) {
if (success) {
[self.someObject doSecondActionWithParam:#"aparam" completion:(void(^)(BOOL success)) {
if (success) { [self doSomething]; }
}];
}
}];
}
I have a partial mock for self.someObject.
The doFirstActionWithParam, I am able to set an expectation for. I am also overriding the completion block (via NSInvocation and invoking a YES completion).
However, I am not able to get the call for doSecondActionWithParam method. I also set up expectation for doSomething method. That never comes through.
Any suggestion for an approach to test nested completion blocks?
There shouldn't be a difference due to the nesting level when you mock a method (stub or expect). As you didn't include your test code it's really difficult to diagnose the problem. My assumption is that the block that is passed to doFirstActionWithParam:completion: is never invoked, in which case doSecondActionWithParam:completion: would als never be invoked.
I'm creating an app where i call my API through this function:
func serverCallAPI(function:String,
success:((result:NSDictionary) -> Void)?) -> Void {
//code here to set up request to send with NSURLConnection
let connection:NSURLConnection = NSURLConnection(request: request,
delegate: self,
startImmediately: true)!
//I WANT TO RUN THE PASSED ON SUCCESS FUNCTION WHEN connectionDidFinishLoading IS CALLED.
}
I need to use NSURLConnection to update my UI when fetching the data
I might have a queue with serverCallAPIs waiting in line to be run, every single now with their own success function. The final function needs to be able to do that
It needs to cancel if the connection fails. Perhaps something with a dispatch or NSOperation? I'm lost on this one... :(
My problem is that I cannot think of a way to call the passed on success function when connectionDidFinishLoading is done processing. How can you do that?
I am developing an OS X Application that uses a single-threaded Core Data model without nested contexts.
I am creating objects on the main thread in the defaultContext and try to save them after creation, but the save fails without returning an error. I have not overwritten any methods in my CoreData objects, but I am using the latest version of MagicalRecord.
The code that fails:
// pseudocode for createOrFetchWithData:inContext:
// fetch object from value in objectDict
// if(!object) create project in context
// [object importValuesForKeysWithObject:objectData] // MR method
// return object
MyObject *object = [MyObject createOrFetchWithData:objectData
inContext:[NSManagedObjectContext defaultContext]];
if(!object) return; // just to emphasise that I am sure the object is not nil.
[[NSManagedObjectContext defaultContext] saveOnlySelfWithCompletion:^(BOOL saveSuccessful, NSError *error) {
if(saveSuccessful) {
NSLog(#"yay");
} else {
NSLog(#"nay");
}
}];
The return value of [NSManagedObjectContext defaultContext] is not nil and I have verified that the code is executed on the main thread.
I have a relationship that isn't set in the MyObject *object, but it is marked as optional in the data model.
Any idea what might cause this simple operation to fail? I have other entities that save just fine, but this particular case fails.
Note: I am running OS X Mavericks DP 8.
In the end, it was my misunderstanding of MagicalRecord's (and possibly CoreData's) implementation of the save: methods: in case there is no change to the context ([context hasChanges] == NO) AND the parent context (!!), the save will be aborted, and the completion block is called with NO for successful and no error object.
For an iPhone app, I'm using a NSOperationQueue to limit access to the SQLite database to one query at a time. I created a subclass of NSOperation and in the main function I have the following:
- (void)main
{
// ... other code here ...
if( [_delegate respondsToSelector:#selector(queryCompleted:)] )
{
[_delegate performSelectorOnMainThread:#selector(queryCompleted:)
withObject:self
waitUntilDone:NO];
}
}
Delegate side:
- (void)queryCompleted:(QueryOperation*)aQueryOperation
{
// Breakpoint here allows me to explore and see the members of aQueryOperation
id results = [aQueryOperation resultSet]; // Crashes here
// ... more code here ...
}
The reason I am passing self is to allow the delegate to access the ID for the query operation (in the case where there is more than one request open per delegate) and the results from the query.
In the documentation for performSelectorOnMainThread:withObject:waitUntilDone:, it clearly states:
"This method retains the receiver and the arg parameter until after the selector is performed."
However when the delegate method tries to access the argument, an "EXC_BAD_ACCESS" exception is thrown. Any thoughts on why?
Oddly enough, if I set a breakpoint before the crashing reference to the NSOperation object, the debugger permits me to see the object instance and the values of all the parameters.
Try setting the waitUntilDone: parameter to YES. Possibly there is a race condition which is allowing the NSOperation to deallocate itself.