So I create an object
MyObject *object = [mainManagedObjectContext insertNewObjectForEntityForName:#"MyObject"];
SomeOtherObject *someOtherObject = [s_mainManagedObjectContext insertNewObjectForEntityForName:#"MyObject"];
// in case you're wondering the inverse relationship of someOtherObject's relation ship to myObject is one to many
// so this should remove the idea that setting someOtherObjects.relationship to something else was causing the property to nil
object.oneToOneRelationShip = someOtherObject;
// simply used for debuggin
[object addObserver:self forKeyPath:#"oneToOneRelationShip" options:NSKeyValueObservingOptionInitial context:nil];
I then post the object using the object manager to post this thing
NSMutableURLRequest *request = [objectManager requestWithObject:object method:method path:path parameters:parameters];
[objectManager managedObjectRequestOperationWithRequest:request managedObjectContext:mainManagedObjectContext success:^{
NSLog(#"blah");
}];
Now after this occurs the RestKit starts running operation objects which eventually calls save which works it way to this calls
-(void)handleManagedObjectContextDidSaveNotification:(NSNotification *)notification {
...
[self.mergeContext mergeChangesFromContextDidSaveNotification:notification];
...
This is where my KVO observer code signals to me there's a change that was made
object.oneToOneRelationShip == nil
I believed I fixed my problem by simply saving to the persistent store before I post the object, but can someone please explain to me why this occurred. Am I doing something wrong? This bug was very confusing to me. I create objects off the main context so I don't know how this problem could occur.
Related
I'm trying to perform object mapping manually by using RKMappingOperation, but it seems like it's not connecting the relationships which are set in RKEntityMapping.
I've created a demo project, you can check out here: https://github.com/HiveHicks/RKMappingOperationTest
There are two entities in the model: Employee and Department with a many-to-one relationship called (department can have many employees, and one employee can work only in one department). There is also a class called TestOperation, which performs mapping in background using RKMappingOperation. As you can see in -[TestOperation mappingForObject:], I set up a connection for employee mapping like this:
[mapping addConnectionForRelationship:EmployeeRelationships.department connectedBy:#{
EmployeeAttributes.departmentGuid : DepartmentAttributes.guid
}];
However, when the operation finishes, the mapped managed objects have all the attributes, but no relationship
How can I fix this?
UPDATE
It turns out that RKMapperOperation maps attributes for managed objects synchronously, but creates asynchronous operation for connecting relationships. That leads to the following situation:
When -[RKMapperOperation execute:] returns, I save the context. All the attributes are there, but relationship is not, because RKRelationshipConnectionOperation is added to the queue, on which I am right now.
RKRelationshipConnectionOperation only sets the relationship, it doesn't save the context.
I'm left with a dirty context and no idea about when to save it, because RKMapperOperationDelegate's mapperDidFinishMapping: method is called before RKRelationshipConnectionOperation starts.
The picture below proves what I just said
So, the solution that I see right now is to create new operation queue in SyncOperation, add RKMapperOperation to it, and then wait until all the operations in this internal queue finish executing. WOW. Sounds like a hack. I'll give it a try right now.
UPDATE 2
It works with the solution I described. Solved. I've commited the final version.
The solution is to create temporary operation queue, add RKMapperOperation to it and call waitUntilAllOperationsAreFinished on this queue. After that, your context is guaranteed to have all the needed relationships set.
The working project is posted on github: https://github.com/HiveHicks/RKMappingOperationTest
id<RKManagedObjectCaching> cache = [[RKInMemoryManagedObjectCache alloc] initWithManagedObjectContext:_context];
RKManagedObjectMappingOperationDataSource *dataSource =
[[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:_context cache:cache];
RKMapperOperation *mapperOperation = [[RKMapperOperation alloc] initWithRepresentation:objects mappingsDictionary:#{
[NSNull null] : [self mapping]
}];
mapperOperation.mappingOperationDataSource = dataSource;
mapperOperation.delegate = self;
NSOperationQueue *operationQueue = [NSOperationQueue new];
operationQueue.name = #"Internal SyncOperation queue";
[operationQueue addOperation:mapperOperation];
[operationQueue waitUntilAllOperationsAreFinished];
[_context performBlockAndWait:^{
NSError *saveError = nil;
if (![_context saveToPersistentStore:&saveError]) {
NSLog(#"Error saving to store: %#", saveError);
}
}];
I've implemented a simple REST request in a structure as follows:
RKManagedObjectRequestOperation *operation = [[RKManagedObjectRequestOperation alloc] initWithRequest:request responseDescriptors:#[responseDescriptor]];
operation.targetObject = nil;
operation.savesToPersistentStore = YES;
operation.managedObjectContext = [RKManagedObjectStore defaultStore].mainQueueManagedObjectContext;
operation.managedObjectCache = [RKManagedObjectStore defaultStore].managedObjectCache;
[operation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *result) {
// Handle success
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
// Handle failure
}];
How can I modify the code so that:
in case of success I need the related entity on DB to be emptied and its content replaced with the new one arrived from the WS (no update, entire content replaced);
in case of failure nothing should happen and the entity on DB must remain as it is.
In order to delete the DB entity, before the operation I would do as reported here:
Core Data: Quickest way to delete all instances of an entity,
but that would empty the entity no matter what the WS answer is.
Any help is really appreciated,
Thanks a lot,
DAN
EDIT1:
As suggested by #Wain, I've added a fetch request block to the operation like this:
RKFetchRequestBlock fetchRequestBlock = ^NSFetchRequest *(NSURL *URL)
{
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"EntityToBeUpdated"];
fetchRequest.sortDescriptors = #[ [NSSortDescriptor sortDescriptorWithKey:#"order" ascending:NO] ];
return fetchRequest;
};
operation.fetchRequestBlocks = #[fetchRequestBlock];
operation.deletesOrphanedObjects = YES;
but the result doesn't change: the items on db are not updated or deleted.
RestKit is based around updating rather than replacing. As such your requirement is hard to manage during mapping. You could consider:
Have RestKit create a new instance and delete the old one in the success block - duplicate objects for a short time.
Use a dynamic mapping to check the response before mapping is started to decide wether to delete or not - kinda hacky.
Have the JSON response contain values for all keys so everything will be overwritten.
From an error point of view, if the error is returned with an HTTP status code and you have no response descriptor for that code then RestKit will change nothing.
Based on your comment below, you are actually looking for orphaned item deletion. This is done using fetch request blocks. See section Fetch Request Blocks and Deleting Orphaned Objects
here. You don't need a predicate, just a fetch request.
I'm not sure if i cause a leak here, is it ok to return allocated NSError back to
the calling method by perform selector?
Is it OK to create the NSMutableArray and store it in the same object i got for the callback? and later pass it to the delegate?
The code works fine, but because i'm new to arc i have the fear of doing something wrong.
(i'm using perform selector because my selector is dynamic. just for the example i wrote it statically).
AFHTTPRequestOperation *operation = [self.client HTTPRequestOperationWithRequest:request
success:^(AFHTTPRequestOperation *operation, id responseObject) {
//-----------------Callback--------------------
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
SEL callback = #selector(getOperationCallback:);
NSError *error = [self performSelector:callback withObject:operation];
//------------------Delegate Call---------------
if(operation.delegate)
[operation.delegate onFinish:operation.requestIdentifier error:error
data:operation.parsedObject];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//------------------Delegate Call---------------
if(operation.delegate)
[operation.delegate onFinish:operation.requestIdentifier error:error data:nil];
}];
- (NSError *)getOperationCallback:(AFHTTPRequestOperation *)operation{
NSArray *rawJson = (NSArray *)operation.jsonObject;
NSError *error;
NSMutableArray *array = [[NSMutableArray alloc] init];
for(id json in rawJson){
MyObject *object = [[MyObject alloc] initWithJson:json];
if(object){
[array addObject:object];
}else{
error = [NSError errorWithDomain:#"myErrors" code:1000 userInfo:nil];
break;
}
}
operation.parsedObject = array;
return error;
}
Generally, performSelector: in ARC could only cause a leak, if the selector you pass to it starts with alloc, new, retain, copy, or mutableCopy.
is it ok to return allocated NSError back to the calling method by perform selector?
Is it OK to create the NSMutableArray and store it in the same object
i got for the callback?
and later pass it to the delegate?
Answer to all questions are OK, nothing wrong with anything. All objects are created in autorelease way.
As long as the return value of the method your are invoking via performSelector:withObject: is an object, it's perfectly ok to do that.
It won't leak since ARC will take care of releasing array and error is autoreleased.
In general, steer clear of performSelector:. The reason being that ARC cannot help you. Even if you think your app works and you've tested that it does, it might break later down the line when you change something.
Sure, if the selector you're calling does not start with alloc, new, copy, mutableCopy, etc then it won't be a problem. But there's cases, such as using __attribute__((ns_returns_retained)) that make it non-obvious that a method might return something retained. In any case, having code that ARC cannot help you out with, is a bad thing.
There's always a way to make it such that you don't have to use performSelector:. Why not make use of a callback block for example?
I am attempting to load a collection of objects via RestKit and have found that this operation is blocking the main thread. Specifically, this functionality is called when a UIViewController appears in the -(void)viewDidAppear:(BOOL)animated; message. As long as request is loading, the user is unable to interact with the UI, and has to wait until the operation has completed.
How can I instruct RestKit to execute the request asynchronously (I thought it already was) and stop blocking the main thread?
-(void)rtrvArtistsByStudio:(ISStudio *)studio
{
NSLog(#"Retrieving Artists for studio %ld", [studio studioID]);
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
NSDictionary *params = [NSDictionary dictionaryWithObject:[NSString stringWithFormat:#"%ld", [studio studioID]] forKey:#"studioID"];
NSURLRequest *request = [restkit requestWithObject:nil method:RKRequestMethodGET path:#"/restAPI/rtrvArtistsByStudio" parameters:params];
RKManagedObjectRequestOperation *operation = [restkit managedObjectRequestOperationWithRequest:request managedObjectContext:restkit.managedObjectStore.persistentStoreManagedObjectContext success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSArray *artists = [mappingResult array];
[studio setArtists:artists];
[notificationCenter postNotificationName:nStudioLoadedArtists object:studio];
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Failed to load artists for studio %ld due to %#", [studio studioID], error);
[notificationCenter postNotificationName:nStudioFailedLoadingArtists object:studio];
}];
[restkit enqueueObjectRequestOperation:operation];
}
EDIT:
I should also note that restkit is a pointer to an instance of RKObjectManager.
RestKit version is 0.20
The request is definitely being dispatched asynchronously. If you press the pause button in the Xcode UI and look at the threads, what is happening on the main thread and the background threads during the request? That should provide insight into what is causing your thread blockage.
Turns out that it was actually my own fault (of course). I have a UIViewController category that performs some transitions. During the transition I have the UIApplication ignore touch events until the transition has completed (0.2s, very brief). I completely forgot that this existed.
Because I am loading some remote objects in viewDidAppear it was still ignoring touch events until that completed. Removing the touch ignore fixed the problem.
I am working on an iOS application that will use RestKit 0.20 to make REST-based calls to a service that is running on JBoss AS 7.1.1 and using restEASY as its REST-based web service framework.
The REST service that the client app will be calling is used to retrieve objects based on their unique identifier. Since these objects can be small or large (> 1MB in size) and great in number (20? 50? 100 or more at a time) I don't want to make one large call to retrieve them all at once. Rather, I was planning on using RestKit's queued operation support to create a GET request for each object based on the object identifier, and execute the calls asynchronously. Once the GET has completed, each object will be processed through the use of Objective-C blocks so as to avoid any unnecessary blocking.
My RestKit client code looks like this...
NSArray *identifiers = ...
RKObjectManager *objectManager = [RKObjectManager sharedManager];
RKResponseDescriptor *getObjResp = [RKResponseDescriptor responseDescriptorWithMapping:[MyObject mapping] pathPattern:[WebServiceHelper pathForServiceOperation:#"/objects/:uniqueIdentifier"] keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
for (int i=0; i < identifiers.count; i++) {
NSString *identifier = [identifiers objectAtIndex:i];
NSURL *serviceURL = [WebServiceHelper urlForServiceOperation:[NSString stringWithFormat:#"/objects/%#", identifier]];
NSURLRequest *request = [NSURLRequest requestWithURL:serviceURL];
RKObjectRequestOperation *requestOp = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:#[getObjResp]];
[requestOp setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
MyObject *obj = [mappingResult firstObject];
if (self.delegate != nil) {
[self.delegate didLoadObjectWithIdentifier:identifier myObj:obj];
}
} failure:^(RKObjectRequestOperation *operation, NSError *error){
if (self.delegate != nil) {
[self.delegate didFinishWithError:error];
}
}];
[objectManager enqueueObjectRequestOperation:requestOp];
}
From there, the delegate method that gets called when an object has been retrieved looks like this:
-(void)didLoadObjectWithIdentifier:(NSString *)identifier myObj:(MyObject *)myObj {
if(secureMessage != nil) {
NSLog(#"Message %# retrieved successfully : %#:%#", identifier, myObj);
} else {
NSLog(#"NO OBJ");
}
}
The calls appear to be functioning as expected, as I am able to print out information about the retrieve objects. However, I am seeing some weird/unexepcted behavior on the service side.
First, I see a number of Exceptions being thrown by restEASY:
13:22:02,903 WARN [org.jboss.resteasy.core.SynchronousDispatcher] (http--0.0.0.0-8080-10) Failed executing GET /objects/BBFE39EA126F610C: org.jboss.resteasy.spi.WriterException: ClientAbortException: java.net.SocketException: Broken pipe
at org.jboss.resteasy.core.ServerResponse.writeTo(ServerResponse.java:262) [resteasy-jaxrs-2.3.2.Final.jar:]
at org.jboss.resteasy.core.SynchronousDispatcher.writeJaxrsResponse(SynchronousDispatcher.java:585) [resteasy-jaxrs-2.3.2.Final.jar:]
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:506) [resteasy-jaxrs-2.3.2.Final.jar:]
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:119) [resteasy-jaxrs-2.3.2.Final.jar:]
at org.jboss.seam.resteasy.ResteasyResourceAdapter$1.process(ResteasyResourceAdapter.java:145) [jboss-seam-resteasy.jar:2.3.0.Final]
at org.jboss.seam.servlet.ContextualHttpServletRequest.run(ContextualHttpServletRequest.java:65) [jboss-seam.jar:2.3.0.Final]
at org.jboss.seam.resteasy.ResteasyResourceAdapter.getResource(ResteasyResourceAdapter.java:120) [jboss-seam-resteasy.jar:2.3.0.Final]
...
It would appear as though RestKit is closing the socket somehow (or some other error is preventing the object from being read from the server). I am unable to find anything in the documentation that could explain what is going on here.
Secondly, though, I also see another call for the very same object when a request fails with this error. Why is the GET being called more than once? Is RestKit redoing the failed GET request?
I'm mostly concerned about why the Exception is occurring within restEASY, as it will make it difficult to diagnose calls that really do fail. Has anyone seen this behavior before? Any tips as to how I can correct these issues? Thanks for any help you can give!!
Those exception are resulted from disconnected Clients i.e. some of the users might quit the app while waiting for the process to complete OR has a network failure (at the client end).
Hence, Broken Pipe.