I have a JSON structure retreived via web socket, that I want to apply to an existing managed object. The particular object to be modified is identified by one of the keys in the JSON. The JSON may not contain all the attributes, but I only want to update the attributes that are present in the JSON (not nullify the others).
I got some initial pointers on the RestKit IRC channel to use RKMappingOperation but am now stuck on the implementation.
First I tried this:
RKMappingOperation *mappingOperation = [[RKMappingOperation alloc] initWithSourceObject:parsedObject destinationObject:nil mapping:[MyManagedObjectClass customMapping]];
Since I don't have the instance of the object to be updated at hand, I passed nil to destinationObject, hopping the mapper would figure it out based on the mapping provided.
Alas, I get nil from mappingOperation's mappingInfo after performing the mapping (but no error).
[mappingOperation performMapping:&localError];
if (localError != nil) {
NSLog(#"%#", [mappingOperation mappingInfo]); // outputs nil
} else {
NSLog(#"error: %#", localError); // no error
}
So my hunch is that I do indeed need to get the managed object instance that I want updated and provide it to the mapping operation, but I can't figure out how. I tried using existingObjectWithID on the managed object context, passing it the ID in my JSON, but no luck. When passing that to the mapping operation i get a 'null' error.
Am I on the right track? What am I missing?
EDIT: After fiddling around some more, I realize the docs specify that you must provide a dataSource if destinationObject is set to nil. So here's what I tried next:
RKManagedObjectMappingOperationDataSource *mappingDS = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:[[[RKObjectManager sharedManager ] managedObjectStore] mainQueueManagedObjectContext] cache:[[[RKObjectManager sharedManager] managedObjectStore] managedObjectCache]];
mappingOperation.dataSource = mappingDS;
Embarrassingly, I had also mixed up the error conditional, so that's why I couldn't see the error on the previous attempt (without data source). Now it seems that the mapping operation performs successfully. Will report back if it's actually the case, and answer my own question :-)
The solution:
RKMappingOperation *mappingOperation = [[RKMappingOperation alloc] initWithSourceObject:parsedObject destinationObject:nil mapping:[MyManagedObjectClass customMapping]];
RKManagedObjectMappingOperationDataSource *mappingDS = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:[[[RKObjectManager sharedManager ] managedObjectStore] mainQueueManagedObjectContext] cache:[[[RKObjectManager sharedManager] managedObjectStore] managedObjectCache]];
mappingOperation.dataSource = mappingDS;
[mappingOperation performMapping:&localError];
if (localError != nil) {
NSLog(#"%#", [mappingOperation mappingInfo]);
} else {
NSLog(#"error: %#", localError);
}
Related
In a given json document, how to validate if a json path exists ?
I am using jayway-jsonpath and have the below code
JsonPath.read(jsonDocument, jsonPath)
The above code can potentially throw below exception
com.jayway.jsonpath.PathNotFoundException: No results for path:
$['a.b.c']
In order to mitigate it, I intend to validate if the path exists before trying to read it with JsonPath.read
For reference I went through the following 2 documentations, but couldn't really get what I want.
http://www.baeldung.com/guide-to-jayway-jsonpath
https://github.com/json-path/JsonPath
Whilst it is true that you can catch an exception, like it is mentioned in the comments there might be a more elegant way to check if a path exists without writing try catch blocks all over the code.
You can use the following configuration option with jayway-jsonpath:
com.jayway.jsonpath.Option.SUPPRESS_EXCEPTIONS
With this option active no exception is thrown. If you use the read method, it simply returns null whenever a path is not found.
Here is an example with JUnit 5 and AssertJ showing how you can use this configuration option, avoiding try / catch blocks just for checking if a json path exists:
#ParameterizedTest
#ArgumentsSource(CustomerProvider.class)
void replaceStructuredPhone(JsonPathReplacementArgument jsonPathReplacementArgument) {
DocumentContext dc = jsonPathReplacementHelper.replaceStructuredPhone(
JsonPath.parse(jsonPathReplacementArgument.getCustomerJson(),
Configuration.defaultConfiguration().addOptions(Option.SUPPRESS_EXCEPTIONS)),
"$.cps[5].contactPhoneNumber", jsonPathReplacementArgument.getUnStructuredPhoneNumberType());
UnStructuredPhoneNumberType unstructRes = dc.read("$.cps[5].contactPhoneNumber.unStructuredPhoneNumber");
assertThat(unstructRes).isNotNull();
// this path does not exist, since it should have been deleted.
Object structRes = dc.read("$.cps[5].contactPhoneNumber.structuredPhoneNumber");
assertThat(structRes).isNull();
}
You can also create a JsonPath object or ReadContext with a Configuration if you have a use case to check multiple paths.
// Suppress errors thrown by JsonPath and instead return null if a path does not exist in a JSON blob.
Configuration suppressExceptionConfiguration = Configuration
.defaultConfiguration()
.addOptions(Option.SUPPRESS_EXCEPTIONS);
ReadContext jsonData = JsonPath.using(suppressExceptionConfiguration).parse(jsonString);
for (int i = 0; i < listOfPaths.size(); i++) {
String pathData = jsonData.read(listOfPaths.get(i));
if (pathData != null) {
// do something
}
I'm new to swift and databases. All I'm trying to do is read from my firebase database. I'm using the example code provided here: https://www.firebase.com/docs/ios/guide/retrieving-data.html.
I'm calling this from my viewcontroller.swift file in override func viewDidLoad(){}
func getRootRef(){
// Read data and react to changes
println("entered getRootRef")
myRootRef.observeEventType(.Value, withBlock: { snapshot in
println("value1")
println(String(stringInterpolationSegment: snapshot.value))
println("value2")
}, withCancelBlock: { error in
println(error.description)
println("error")
})
println("left getRootRef")
}
Output:
entered getRootRef
left getRootRef
ViewController3
ViewController4
value1
(Function)
value2
I do not understand why "(Function)" is printing out instead of my data. both read and write permission are set to true. I am making a Mac app.
Your code sample doesn't give enough info as to what you want to do - however, assuming you want to print out the contents of the myRootRef node...
I believe that in Swift, FDataSnapshot.value is considered optional - it could contain a value or it could be nil. Therefore we need to unwrap the value:
myRootRef.observeEventType(.Value, withBlock: { snapshot in
println( snapshot.value()! )
})
The ! is a forced unwrap of the variable so it must contain a value. It should be checked for nil before printing or else it would cause a runtime error.
This may be safer
myRootRef.observeEventType(.Value, withBlock: { snapshot in
if let value: AnyObject = snapshot.value() {
println(value)
}
})
Note that snapshot.value could contain one of a number of different objects: Data types returned: * NSDictionary * NSArray * NSNumber (also includes booleans) * NSString
I am fully aware that Swift doesn't have a try/catch mechanism to catch exceptions (OK, Swift 2.0 now supports them). I also understand that many API methods return a NSError that will be filled with an error object if something goes wrong. So please don't point me to this question: Error-Handling in Swift-Language
But this still doesn't explain how to react to runtime errors in your own code, like array-out-of-bounds accesses or force-unwrapping an optional value that is nil. For example:
var test: String?
test = nil
println(test!) //oops!
or
var arr = [0,1,2]
for i = 0...3 {
println(arr[i]) //oops!
}
Every programmer makes such mistakes occasionally and there should be a way to at least log them for later analysis. While debugging, Xcode can show us those, but what if this happens to an end-user or beta-tester? In pure C there is signal handling and it could be used in Objective-C as well. Is there something like this in Swift? A centralized callback entered just before the app dies?
Update:
Let me rephrase the question: in a large project, it is not feasible to manually check for the above errors on every loop and force-unwrapping. When a runtime error does happen eventually, is there a callback like Objective C's segfault handling or NSSetUncaughtExceptionHandler that will get called so that the error can be logged/e-mailed together with a stacktrace of the crash?
Edit: This answer is not updated with swift 2.0. As swift now has error handling I have not updated the below answer. Some aspect of error handling will be updated in future with swift 3.0. You can follow this answer Error-Handling in Swift-Language
Swift is made to be typeSafe language.It get error at compile time rather than waiting to cause at runtime.
In first example you are using Optional.
var test: String?
First understand meaning of optional.When you specifying optional you are saying it could be nil or have no value.Now when you unwrapping test you are saying i know this value is not nil.Please unwrap it i am sure about that.So its your responsibility to see where it nil.If you are not sure about that than you should use optional binding here.When you are unsure about value always use if condition while unwrrapping
if let notNilTest = test {
//use notNilTest
}
else{
//handle error
}
In second example it should make sense to have the runtime exception handling but you can easily get this with if condition having count.So in second example as developer you should use if condition to get count of array.
From swift guide:
If you try to use subscript syntax to retrieve or set a value for an
index that is outside of an array’s existing bounds, you will trigger
a runtime error. However, you can check that an index is valid before
using it, by comparing it to the array’s count property. Except when
count is 0 (meaning the array is empty), the largest valid index in an
array will always be count - 1, because arrays are indexed from zero.
They clearly mention about this and you should take care of these things to make your code less buggy.Some things they have provided and we should know about how to use these things.
Consider using a guard statement instead of multiple if lets.
var arr = [0,1,2]
for i = 0...3 {
Guard arr[i] != nil else {
Continue
}
println(arr[i]) //oops!
}
Or instead of
if let x = some value {
If let y = someOtherVal {
If let z = yetanotherVal {
Product = x * y* z
}
}
}
Is not nearly as neat as:
Let x = someVal
Let y = someOtherVal
Let z = yetAnotherVal
Guard x != nil,
y != nil,
z != nil
Else {
Return
}
Product = x * y * z
this is my first time connecting an app to the web, I just want to make sure i am clear on how this works.
I want to download data from a mysql db hosted online, it also seems to have php attached (hostgator) is the host.
So steps are:
1. In php (on the server module), set up a method to transcribe mysql information into JSON using the attached PHP module. Return an array (json i think is a mutilayered dictionary array object)
In xcode, use apple's json framework to create a url request, and download data into a json object (or array or dictionary?
Go through the data and create objects and save to coredata.
Please let me know if im following the logic correctly.
Also does JSON return 1 object or all the objects on the mysql db. So if i need to input 10,000 objects on coredata i have to make 10,000 requests or one request and parse 10,000 objects worth of info?
Also is this the best way to do what i need to do? I have heard of http request but it seems complicated, and I have no clue what it is.
Sorry for such a noob question.
Thanks for the help.
There are many ways to do it, some are "more correct" than others :-) but you are on the right way.
I explain what I would do in a similar situation:
1- for the PHP engine, you should create an API to access your data. Which data?
The simpliest and probably the first thing you can do TO TEST (only for testing purpose!!!!) is to create a page that receive a query via POST from your ios APP and answer with an encoded JSON string. With JSON you can transfer an entire hierarchy of objects: variables, arrays, dictionary...is up to you to decide how link the objects together.
If the data you want to encode is a table, you can do something similar to this:
// first connect to the database and execute the query, then...
$data = array();
while(!$RS->EOF){
for ($i = 0; $i < $cols; $i++){
$rowName = utf8_encode($RS->Fields[$i]->name);
$rowValue = utf8_encode($RS->Fields[$i]->value);
$rowData[$rowName] = $rowValue;
}
array_push($data, $rowData);
$RS->MoveNext();
}
$response['data'] = $data;
echo json_encode($response);
the result is a JSON object with a first dictionary with a key named "data".
Inside the "data" key there is an array of dictionaries. Each dictionary has the column name for the key, and the column value for the data:
data = (
{
attivita = "this is the first row in the table";
id = 2548;
},
{
attivita = "this is the second row in the table";
id = 2547;
};
}
You can simply use json_encode to create your json string.
On the iPhone side, I suggest you to download and use AFNetworking. It's a very good and complete open source framework, with a lot of builtin objects and methods for http/https requests, XML, JSON ecc...
Using AFNetworking you can make the request in a similar way:
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:#"http://url.for.the.php.page"];
NSMutableURLRequest *request = [httpClient requestWithMethod:#"POST" path:mainPath parameters:params];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
// your success code here
// remember, the first object is a dictionary with a key called data
NSArray *arrayOfData = [JSON objectForKey:#"data"];
NSMutableArray *arrayOfActivities = [NSMutableArray array];
for (NSDictionary *objectDictionary in arrayOfData) {
// objectDictionary is your dictionary containing
// a key with the column name and a value with the column content
// here you can init your custom objects
}
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
// your failure code here
}];
[operation start];
As you can see, the JSONRequestOperationWithRequest: method has a completion handler returning you the decoded JSON, so you can access your dictionaries/arrays directly using objectForKey or objectAtIndex ;-)
Last suggestions: in a production and secure environment, you should avoid sending query over post requests. The code I pasted here is for a private application (used only by me for testing purpose). It's better to use a different API for each kind of request and a secure authentication method (look at oAuth).
I suggest you to give a look to the Instagram or Twitter API (Instagram is simpler), trying to use it. They will give you some ideas on how to create your own API.
Good luck
Your first step of creating a method to return JSON-ified MySQL data is correct. What exactly is returned however, is completely up to you! You will find that JSON is an extremely flexible format that allows you to put nearly any format of data in your response. It may make sense, depending on what you want to do, to have several methods in your PHP module that return different things. For example, if you have a database of books, maybe you want to create a method that allows you to specify an author, and will return a JSON response with all the books in your database written by that author.
You could use HttpRequest to actually perform the call to your module, but I found the Obj-C NSURLRequest quite easy to use:
NSURLRequest *request = [NSURLRequest requestWithURL:
[NSURL URLWithString:#"http://yoururl/books/38917"]];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
Don't think that you have to manually parse the JSON response into your own Objective-C objects; this is a very common task and there exist many great libraries to do so. Check out this tutorial on the SBJSON framework to view one of the most popular solutions.
As an example, here is how you could parse the JSON response into an NSDictionary for easy traversal:
// Create SBJSON object to parse JSON
SBJSON *parser = [[SBJSON alloc] init];
// parse the JSON string into an object - assuming json_string is a NSString of JSON data
NSDictionary *object = [parser objectWithString:json_string error:nil];
I've the following problem:
I'm accessing foursquares Venue API and get a JSON string back. I parse this string with this json-framework. After that I'm saving the dictionaries and arrays for accessing further information about the venues (in special I'm using the explore API). So the venue information is saved deeply (for my experience) in the json-structure tree. And after getting the needed information (venue name & coordinates) I put a corresponding pin on a map with the same coordinates and with the venue's name as the pin's name.
And exactly at the point where I want to set the pin's name, I get a memory leak. So something get's wrong here. If I don't set any title, all works fine. So the memory leak occurs only when I'm setting the name of the venue to the pin.
Here is the corresponding code fragment:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//Parse JSON string
// Store incoming data into a string
NSString *jsonString = [[NSString alloc] initWithData:self.fetchedJSONData encoding:NSUTF8StringEncoding];
[self.fetchedJSONData setLength:0];
// Create a dictionary from the JSON string
NSDictionary *results = [NSDictionary dictionaryWithDictionary:[jsonString JSONValue]];
[jsonString release];
NSDictionary *response = [NSDictionary dictionaryWithDictionary:[results objectForKey:#"response"]];
NSArray *groups = [NSArray arrayWithArray:[response objectForKey:#"groups"]];
NSDictionary *groupsDic = [groups lastObject];
NSArray *items = [NSArray arrayWithArray:[groupsDic objectForKey:#"items"]];
for (int i=0; i<[items count]; i++)
{
CLLocationCoordinate2D annotationCoord;
MKPointAnnotation *annotationPoint = [[MKPointAnnotation alloc] init];
NSDictionary* oneItemDoc = [NSDictionary dictionaryWithDictionary:[items objectAtIndex:i]];
NSDictionary *singleVenue = [NSDictionary dictionaryWithDictionary:[oneItemDoc objectForKey:#"venue"]];
/*
* Leak here in the next two lines!
*
*/
NSString *titleName = [[[singleVenue objectForKey:#"name"] copy] autorelease];
annotationPoint.title = titleName;
NSDictionary *locationOfVenue = [NSDictionary dictionaryWithDictionary:[singleVenue objectForKey:#"location"]];
annotationCoord.latitude = [[locationOfVenue objectForKey:#"lat"] doubleValue];
annotationCoord.longitude = [[locationOfVenue objectForKey:#"lng"] doubleValue];
annotationPoint.coordinate = annotationCoord;
[self.mapView addAnnotation:annotationPoint];
[self.annotationsArray addObject:annotationPoint];
[annotationPoint release];
}
}
So the leak occurs when I want to set the title for the annotationPoint.
For each venue fetched with JSON I get the following leak trace (blurred libraries are my own libraries):
Has anybody a suggestion how to solve this problem? I tried many, many things. So the key issue seems to be how to "hand over" the [singleVenue objectForKey:#"name"] correctly. I first tried to set it without a copy and an autorelease, but then I get a zombie object. So I don't know how to do this. I think the problem are not these two lines, but some lines above them. Am I right? I also have the suggestion, that my 3rd party json parser is forcing this problem (cf. leak trace).
So I hope someone can help me to fix this problem. Would be really great!
Update: The problem seems to be independent of the corresponding JSON parser. I've testet my code with another parser, same problem there. So it has to do something with my code itself.
I think I know what's the problem. So the leak occurs after closing the map. So after dealloc. So it might be, that I've missed something there. I have a mapview and I also release it in dealloc and set it to nil in viewDidUnload. I also release all the other Arrays etc. in dealloc. Is there something else (specific about the map and view) which I need to release? I think this might be the problem!
Update: Solved the problem: I had to set all Foursquare pins' title and subtitle to nil in the dealloc method, because a value (accessed via a JSON parser) was retained by the map view somehow. Now all works fine!
Solved the problem: I had to set all Foursquare pins' title and subtitle to nil in the dealloc method, because a value (accessed via a JSON parser) was retained by the map view somehow. Now all works fine!
Had a similar situation, annotation's (MKPointAnnotation) title not being released properly. Solved it by simply setting mapView to nil just before releasing it.
I think this is quite safer than calling an extra [title release], which also does work, but if mapView is fixed internally it would cause a problem.