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];
Related
This is a really weird bug, when grabbing JSON from my server (which is produced via PHP), I get this error when calling:
json = [NSJSONSerialization JSONObjectWithData:kivaData
options:kNilOptions
error:&jsonError];
JSON Error: Error Domain=NSCocoaErrorDomain Code=3840 "The operation couldn’t be completed. (Cocoa error 3840.)" (Garbage at end.) UserInfo=0x178467d00 {NSDebugDescription=Garbage at end.}
My (NSData* kivaData) grabs everything perfectly, but it cant parse the JSON.
I have run my JSON code in http://jsonlint.com/ and it comes out Valid everytime.
Its really weird because it can parse the JSON when I connect to Wifi, but when I try doing it via cellular, it wont work. It does work over cellular on some peoples phones, but every time.
using swift 4, first of all check the JSON Data using print :
print (String(data:data!, encoding: .utf8)!)
check for the white spaces or unwanted characters, then remove them :
var string = String(data: data!, encoding: .utf8)
string = string?.replacingOccurrences(of: "/r/n", with: "")
after that, assign the string back to data variable :
let data1 = string!.data(using: .utf8)
encoding is very important. If your json is valid, the issue might be you have special characters in your json data, which is not correctly parsed by the json serializer. When you send the data, make sure you have the correct url-encoding when sending content so client will parse it correctly. Using utf-8 always or base64.
I was able to solve the same problem (works on wifi, but not on carrier network) by sending a content-length header just before the response:
header("Content-length: ".strlen($response));
echo $response;
exit;
I ended up having to change my php file from echoing the json syntax to simply outputting with json_encode.
JsonData is usually stored in dictionary format. Since the json is not able to parse the continuous data[its not able to separate the responses] its throwing this error .
You can maintain a dictionary to store the responses obtained from server .
Each task will have a unique response . So create a dictionary with "keys" as "taskIdentifier" of tasks and "values" as "data".
Eg:
Inside didReceiveData or any other equivalent methods [where you get response from server ] store response in dictionary with taskIdentifier as keys .
NSString *taskID = [#(dataTask.taskIdentifier) stringValue];
[_task_data_dictionary setObject:data forKey:taskID];
Here _task_data_dictionary is the dictionary.In this way you can get rid of the above error .
After this you can get data using the same dictionary using this code
NSData *data = [_task_data_dictionary objectForKey:taskNumber];
again using the taskIdentifier .
Hope this helps .
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);
}
I created a servlet which responds to get requests with a byte array created from json data. I am trying to consume this data in iOS and use NSJSONSerialization to parse it into a NSDictionary, but it fails with the following error
Error Domain=NSCocoaErrorDomain Code=3840 "The operation couldn’t be completed. (Cocoa error 3840.)" (Duplicate key for object around character 11.) UserInfo=0x6833200 {NSDebugDescription=Duplicate key for object around character 11.}
Here is my code:
NSString *query = #"http://localhost:8888/url?method=retrieve";
NSData *jsonData = [NSData dataWithContentsOfURL:[NSURL URLWithString:query]];
NSError *error = nil;
NSString *stringData = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
NSLog(#"substring to index 255: %#", [stringData substringToIndex:255]);
NSDictionary *results = jsonData ? [NSJSONSerialization JSONObjectWithData:[stringData dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&error] : nil;
NSLog(#"Response as Dictionary:\n%#", results.description);
if (error) {
NSLog(#"Error: %#", error);
}
the value of stringData is
{"APPEALS":{"APPEAL":{"AppealID":387423483,"LastEdit":"1 . . .
Response as Dictionary returns (null) obviously since there is an error
I am guessing that it has something to do with the fact that my server sends the data in an output stream as a byte[] (java) and it is not formatted correctly as json when received in iOS, but it doesn't make sense to me why it would fail at character 11 ":"
FYI, the server is written on Google App Engine in java and the localhost url is the local dev server. The json data was created using Jackson Generator library. Thanks!
I discovered the answer myself: when the error points to a duplicate key at a ":" character, that means that some key within the following json array is duplicated, not necessarily the one immediately following that character index. From my json data above, I had many "APPEAL" entries, that when turned into an NSDictionary will throw an error since there can be only 1 value for a given key. I believe my confusion arose from reading a Jackson json generator tutorial which described creating entries with the same key so that they can later be serialized into many instances of an object with "key" as the object class name (so I could have created many APPEAL objects using a Jackson parser, but not so in NSJSONSerialization).
I also had concatenated several json files server side:
( {"table":{"title":value}}{"anotherTable":{"title":value}} )
so that my url request response could serve several files with 1 request (cost efficiency), but these had to be split client side and serialized individually since "}}{" isn't legal json format.
The json had a duplicate key.For example,{"json":"3","string":"34","json":"3"}.The json can't be parsed before iOS6.0.
Currently I am able to retrieve data from a MySQL db by using HTTP GET and an NSURL connection, but I need to know how to encapsulate this action into a method. Could somebody inform me of how to put the following code into a method so that I can call it throughout my app, and also so that I can show a UIActivityIndicator while the process is underway?
//somethign like this?
-(void)getDataFromServer:(NSString *)url{
//enter code here
}
My current server request: I am using this in didselectrowforindexpath, so that the following stuff is done whenever somebody selects a cell. The issue is that I keep re-using this same code in several different methods, so I assume that it is bad practice to keep copying/pasting this code rather than having a method.
// retrieve data
NSString *buildURL = [[NSString alloc] initWithFormat:#"http://myurl.com"];
// begin query
NSURL *url = [NSURL URLWithString:buildURL];
NSError *e;
NSString *jsonreturn = [[NSString alloc] initWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&e];
NSLog(#"jsonreturn: %#",jsonreturn);
NSData *jsonData = [jsonreturn dataUsingEncoding:NSUTF32BigEndianStringEncoding];
NSError *error = nil;
EDIT:
Much better way to do this, which I just implemented, is to use ASIHTTPRequest. The library provides for all the needs which I was trying to accomplish with the method above.
Just take your whole second code block and put it inside the {} of the first code block. You probably want to return that jsonData variable instead of void, though.
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.