How to update JSON File from web server - ios

I have my local data in json file like :
{
"locations": [
{
"title": "The Pump Room",
"place": "Bath",
"latitude": 51.38131,
"longitude": -2.35959,
"information": "The Pump Room Restaurant in Bath is one of the city’s most elegant places to enjoy stylish, Modern-British cuisine.",
"telephone": "+44 (0)1225 444477",
"visited" : true
},
{
"title": "The Eye",
"place": "London",
"latitude": 51.502866,
"longitude": -0.119483,
"information": "At 135m, the London Eye is the world’s largest cantilevered observation wheel. It was designed by Marks Barfield Architects and launched in 2000.",
"telephone": "+44 (0)8717 813000",
"visited" : false
},
{
"title": "Chalice Well",
"place": "Glastonbury",
"latitude": 51.143669,
"longitude": -2.706782,
"information": "Chalice Well is one of Britain's most ancient wells, nestling in the Vale of Avalon between the famous Glastonbury Tor and Chalice Hill.",
"telephone": "+44 (0)1458 831154",
"visited" : true
}
]
}
I want to update the json file which is there on the webserver whenever the refresh button touched?
The overall idea is to refresh the local data from server and use it without internet connectivity
Please Help...

The most appropriate solution depends on your exact requirements. For example:
Do you need authentication?
Do you require to load the JSON in background mode?
Is your JSON huge, so that loading it into memory wouldn't be that nice to the system?
Do you need to cancel a running request, since there are chances that it stalls or takes too long?
Do you need to load many requests at once in parallel?
and a few more.
If you can answer all requirements with No, then the most simple approach will suffice:
You can use NSURLConnection's convenient class method in the asynchronous version
+ (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler
You can find more info here:
Using NSURL Connection
Furthermore Cocoa and Cocoa Touch provides more advances techniques in NSURLConnection and NSURLSession to accomplish this task. You can read more in the official documentation URL Loading System Programming Guide.
Here a short sample how you can use the asynchronous convenience class method: sendAsynchronousRequest:queue:completionHandler::
// Create and setup the request
NSMutableURLRequest* urlRequest = [NSURLRequest requestWithURL:url];
[urlRequest setValue: #"application/json; charset=utf-8" forHTTPHeaderField:#"Accept"];
// let the handler execute on the background, create a NSOperation queue:
NSOperationQueue* queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:urlRequest
queue:queue
completionHandler:^(NSURLResponse* response,
NSData* data,
NSError* error)
{
if (data) {
// check status code, and optionally MIME type
if ( [(NSHTTPURLResponse*)(response) statusCode] == 200 /* OK */) {
NSError* error;
// here, you might want to save the JSON to a file, e.g.:
// Notice: our JSON is in UTF-8, since we explicitly requested this
// in the request header:
if (![data writeToFile:path_to_file options:0 error:&error]) {
[self handleError:err]; // execute on main thread!
return;
}
// then, process the JSON to get a JSON object:
id jsonObject = [NSJSONSerialization JSONObjectWithData:data
options:0
error:&error];
... // additionally steps may follow
if (jsonObject) {
// now execute subsequent steps on the main queue:
dispatch_async(dispatch_get_main_queue(), ^{
self.places = jsonObject;
});
}
else {
[self handleError:err]; // execute on main thread!
}
}
else {
// status code indicates error, or didn't receive type of data requested
NSError* err = [NSError errorWithDomain:...];
[self handleError:err]; // execute on main thread!
}
}
else {
// request failed - error contains info about the failure
[self handleError:error]; // execute on main thread!
}
}];
See also: [NSData] writeToURL:options:error
Edit:
handleError: SHALL be implemented as follows:
- (void) handlerError:(NSError*)error
{
dispatch_async(dispatch_get_main_queue(), ^{
[self doHandleError:error];
});
}
This ensures, when you display a UIAlertView for example, that your UIKit methods will be executed on the main thread.

Related

Waiting for network call to finish

What I'm trying to achieve is to make a network request and wait for it to finish, so that I can make a decission what should be apps next step.
Normally I would avoid such solution, but this is a rare case in which codebase has a lot of legacy and we don't have enough time to apply necessary changes in order to make things right.
I'm trying to write a simple input-output method with following definition:
- (nullable id<UserPaymentCard>)validCardForLocationWithId:(ObjectId)locationId;
The thing is that in order to perform some validation inside this method I need to make a network request just to receive neccessary information, so I'd like to wait for this request to finish.
First thing that popped in my mind was using dispatch_semaphore_t, so I ended up with something like this:
- (nullable id<UserPaymentCard>)validCardForLocationWithId:(ObjectId)locationId {
id<LocationsReader> locationsReader = [self locationsReader];
__block LocationStatus *status = nil;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[locationsReader fetchLocationProviderStatusFor:locationId completion:^(LocationStatus * _Nonnull locationStatus) {
status = locationStatus;
dispatch_semaphore_signal(sema);
} failure:nil];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
return [self.paymentCards firstCardForStatus:status];
}
Everything compiles and runs, but my UI freezes and I actually never receive sempahore's signal.
So, I started playing with dispatch_group_t with exactly the same results.
Look like I might have some problems with where code gets executed, but I don't know how to approach this and get the expected results. When I try wrapping everything in dispatch_async I actually stop blocking main queue, but dispatch_async return immediatelly, so I return from this method before the network request finishes.
What am I missing? Can this actually be acheived without some while hacks? Or am I trying to fight windmills?
I was able to achieve what I want with the following solution, but it really feels like a hacky way and not something I'd love to ship in my codebase.
- (nullable id<UserPaymentCard>)validCardForLocationWithId:(ObjectId)locationId {
id<LocationsReader> locationsReader = [self locationsReader];
__block LocationStatus *status = nil;
__block BOOL flag = NO;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[locationsReader fetchLocationProviderStatusFor:locationId completion:^(LocationStatus * _Nonnull locationStatus) {
status = locationStatus;
flag = YES;
} failure:nil];
});
while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) && !flag){};
return [self.paymentCards firstCardForStatus:status];
}
I guess fetchLocationProviderStatusFor:completion:failure: calls those callbacks in main queue. That's why you get deadlock. It's impossible. We can't time travel yet.
The deprecated NSURLConnection.sendSynchronousRequest API is useful for those instances when you really can't (or just can't be bothered to) do things properly, like this example:
private func pageExists(at url: URL) -> Bool {
var request = URLRequest(url: url)
request.httpMethod = "HEAD"
request.timeoutInterval = 10
var response: URLResponse?
try! NSURLConnection.sendSynchronousRequest(request,
returning: &response)
let httpResponse = response as! HTTPURLResponse
if httpResponse.statusCode != 200 { return false }
if httpResponse.url != url { return false }
return true
}
Currently, your method causes work to be done on the main thread, which freezes the UI. Your solution works, but it would be best to change the method to include a completion block. Then, you could call the completion block at the end of the async block. Here's the example code for that:
- (void)validCardForLocationWithId:(ObjectId)locationId completion:(nullable id<UserPaymentCard> (^)(void))completion {
id<LocationsReader> locationsReader = [self locationsReader];
__block LocationStatus *status = nil;
[locationsReader fetchLocationProviderStatusFor:locationId completion:^(LocationStatus * _Nonnull locationStatus) {
status = locationStatus;
completion([self.paymentCards firstCardForStatus:status]);
} failure:nil];
}

CJSONDeserialize error

I am working on a JSON parsing application. I am using touchjson for parsing the json url which is --
https://dl.dropboxusercontent.com/u/746330/facts.json
{
"title":"About Canada",
"rows":[
{
"title":"Beavers",
"description":"Beavers are second only to humans in their ability to manipulate and change their environment. They can measure up to 1.3 metres long. A group of beavers is called a colony",
"imageHref":"http://upload.wikimedia.org/wikipedia/commons/thumb/6/6b/American_Beaver.jpg/220px-American_Beaver.jpg"
},
{
"title":"Flag",
"description":null,
"imageHref":"http://images.findicons.com/files/icons/662/world_flag/128/flag_of_canada.png"
},
{
"title":"Transportation",
"description":"It is a well known fact that polar bears are the main mode of transportation in Canada. They consume far less gas and have the added benefit of being difficult to steal.",
"imageHref":"http://1.bp.blogspot.com/_VZVOmYVm68Q/SMkzZzkGXKI/AAAAAAAAADQ/U89miaCkcyo/s400/the_golden_compass_still.jpg"
},
{
"title":"Hockey Night in Canada",
"description":"These Saturday night CBC broadcasts originally aired on radio in 1931. In 1952 they debuted on television and continue to unite (and divide) the nation each week.",
"imageHref":"http://fyimusic.ca/wp-content/uploads/2008/06/hockey-night-in-canada.thumbnail.jpg"
},
{
"title":"Eh",
"description":"A chiefly Canadian interrogative utterance, usually expressing surprise or doubt or seeking confirmation.",
"imageHref":null
},
{
"title":"Housing",
"description":"Warmer than you might think.",
"imageHref":"http://icons.iconarchive.com/icons/iconshock/alaska/256/Igloo-icon.png"
},
{
"title":"Public Shame",
"description":" Sadly it's true.",
"imageHref":"http://static.guim.co.uk/sys-images/Music/Pix/site_furniture/2007/04/19/avril_lavigne.jpg"
},
{
"title":null,
"description":null,
"imageHref":null
},
{
"title":"Space Program",
"description":"Canada hopes to soon launch a man to the moon.",
"imageHref":"http://files.turbosquid.com/Preview/Content_2009_07_14__10_25_15/trebucheta.jpgdf3f3bf4-935d-40ff-84b2-6ce718a327a9Larger.jpg"
},
{
"title":"Meese",
"description":"A moose is a common sight in Canada. Tall and majestic, they represent many of the values which Canadians imagine that they possess. They grow up to 2.7 metres long and can weigh over 700 kg. They swim at 10 km/h. Moose antlers weigh roughly 20 kg. The plural of moose is actually 'meese', despite what most dictionaries, encyclopedias, and experts will tell you.",
"imageHref":"http://caroldeckerwildlifeartstudio.net/wp-content/uploads/2011/04/IMG_2418%20majestic%20moose%201%20copy%20(Small)-96x96.jpg"
},
{
"title":"Geography",
"description":"It's really big.",
"imageHref":null
},
{
"title":"Kittens...",
"description":"Éare illegal. Cats are fine.",
"imageHref":"http://www.donegalhimalayans.com/images/That%20fish%20was%20this%20big.jpg"
},
{
"title":"Mounties",
"description":"They are the law. They are also Canada's foreign espionage service. Subtle.",
"imageHref":"http://3.bp.blogspot.com/__mokxbTmuJM/RnWuJ6cE9cI/AAAAAAAAATw/6z3m3w9JDiU/s400/019843_31.jpg"
},
{
"title":"Language",
"description":"Nous parlons tous les langues importants.",
"imageHref":null
}
]
}
For some reason i am getting the error when i use CJONDeserializer ---
Error Domain=CJSONDeserializerErrorDomain Code=-104 "Could not scan dictionary. Failed to scan a value." UserInfo=0x7feb38d99ed0 {NSLocalizedDescription=Could not scan dictionary. Failed to scan a value., line=0, character=0, location=0, snippet=!HERE>!{
"title":"About Can}
and if I use NSJSONSerializer it gives me the following error ---
"Error Domain=NSCocoaErrorDomain Code=3840 "The operation couldn’t be completed. (Cocoa error 3840.)" (Unable to convert data to string around character 2643.) UserInfo=0x7f8559c56700 {NSDebugDescription=Unable to convert data to string around character 2643.}”
For the code I am using, it doesn't give me any error if I use any other json url.
Here is the code that I am using --
- (void)viewDidLoad {
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
if (connection) {
self.receivedData = [[NSMutableData alloc] init];
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// The request is complete and data has been received
// You can parse the stuff in your instance variable now
NSError *error = nil;
NSDictionary *receivedDataDictionary = [[CJSONDeserializer deserializer] deserializeAsDictionary:self.receivedData error:&error];
NSArray *arrayFromJson = [receivedDataDictionary objectForKey:#"title"];
for (NSDictionary *tempDict in arrayFromJson) {
NSLog(#"blah %#", tempDict);
}
}
Any help will be much appreciated. Thanks in advance.
First thing from your JSON : "title" doesnt contain array, array is in key "rows".
Second : It looks like Problem is in your JSON. Your JSON contains "É" in third last object. CJSONDeserializer is not able to decode this kind of chars. Use NSJSONSerialization. Try following code.
NSError *error;
NSString *string = [NSString stringWithContentsOfURL:[NSURL URLWithString: urlString] encoding:NSISOLatin1StringEncoding error:&error];
NSData *resData = [string dataUsingEncoding:NSUTF8StringEncoding];
id jsonObject = [NSJSONSerialization JSONObjectWithData:resData options:kNilOptions error:&error];
if (error) {
//Error handling
} else {
//use your json object
NSArray *arrayFromJson = [jsonObject objectForKey:#"rows"];
for (NSDictionary *tempDict in arrayFromJson) {
NSLog(#"blah %#", tempDict);
}
}

How one tests http requests in iOS 8?

In ruby I used to test http requests with vcr gem which recorded the request so the tests didn't send request to real host. Is there anything like this in iOS8 world?
The requests I want to test really need to be recorded since those requests may be outdated in some time and will return some other response
P.S. It would be great if it was some default Apple/iOS approach/library like XCTest for testing in general
What you want is something like OHHTTPStubs or Nocilla or AMY server. All of them essentially use NSURLProtocol to intercept your request and allow you to designate a response. We used OHHTTPStubs but pick the one with the feature set closest to your use case.
Here's an example of an OHHTTPStubs implementation in a unit test for a service that talks to a single REST endpoint:
NSString *loadRoomJSON = #{ #"key" : #"value" }; /* some JSON */
NSNumber identifier = #1;
[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
NSString *url = [NSString stringWithFormat:#"v1/user/%#/room", identifier];
XCTAssert([request.URL.relativePath containsString:url], #"Expected certain URL");
return YES;
} andRespond:^OHHTTPStubsResponse *(NSURLRequest *request) {
return [OHHTTPStubsResponse responseWithJSONObject:loadRoomJSON statusCode:200 headers:nil];
}];
XCTestExpectation *loadPromise = [self expectation:#"Room loaded"];
[service loadRoomOnSucceed:^(Room *room) {
// Do your asserts here. For us, the JSON is mapped to an object
// so for example you could assert that the object is mapped correctly
[loadPromise fulfill];
} onFail:^(NSError *error) {
expect(error).to.beNil();
}];
[self waitForExpectationsWithTimeout:1.0 handler:^(NSError *error) {
expect(error).to.beNil();
}];
In reality our tests are shorter since we write wrapper/helpers to make it read better so this is an exploded-out version. Should give you the general idea. OHHTTPStubs (if you use it) has helper functions to load responses directly from files as well.
Im not sure if I understood you correct. But if I understand you right, you should be able to use XCTest to test your request and response.
class Tests:XCTestCase{
func testing(){
var expectation = self.expectationWithDescription("Your request")
var url = NSURL(string: "http://yourUrl.com")
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) {(data, response, error) in
if let httpRes = response as? NSHTTPURLResponse {
println("status code=",httpRes.statusCode)
//200 means OK
if httpRes.statusCode == 200 {
println(NSString(data: data, encoding: NSUTF8StringEncoding))
}
}else{
println("error \(error)")
}
}
}
}

How to queue up batch operations in AFNetworking 2.0

I have this function which calls a GET method on AFHTTPRequestOperationManager:
var request:NSMutableURLRequest = ParseAPIClient.sharedClient.GET(className, parameters: parameters, success: { (operation:AFHTTPRequestOperation!, response:AnyObject!) -> Void in
if response.isKindOfClass(NSDictionary) {
self.writeJSONResponse(response, toDiskForClassWithName:className)
} else { NSLog("something happened") }
}, failure: { (operation:AFHTTPRequestOperation!, error:NSError!) -> Void in
NSLog("Request for class %# failed with error: %#", className, error)
})
This generates a request uses that request to create an AFHTTPRequestOperation. That operation is returned in that method along with a response to the request. The block passed into it writes the response to disk.
In my old AF1.x code, I would then use:
SDAFParseAPIClient.sharedClient.enqueueBatchOfHTTPRequestOperations:operations progressBlock:^(NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations) {
} completionBlock:^(NSArray *operations) {
if (!toDelete) {
self.processJSONDataRecordsIntoCoreData
} else {
self.processJSONDataRecordsForDeletion
}
}];
method to take those operations created above and do something else afterwards. Iow, I would take the data written to disk and parse it with those self.processJSONDataRecords... methods.
Im not sure what would be the new equivalent?
The equivalent functionality in AFNetworking 2.0 is provided by AFURLConnectionOperation +batchOfRequestOperations:progressBlock:completionBlock:.
The difference here is that the developer is ultimately responsible for enqueuing the array of batched operations, using NSOperationQueue -addOperations:waitUntilFinished:.

How to unit test api calls using AFNetworking

I have an iOS app I'm working on, which connects to a third-party web service. I have around 50 different calls, and want to write unit tests using Kiwi, but I have no idea where to start.
As I'm not responsible for the API, I need to just check that my calls are pointing to the correct URL, using the correct GET or POST method.
Is there any way to test this properly?
Heres an example of one of my calls:
+ (void)listsForUser:(NSString *)username
response:(void (^)(NSArray *lists))completion
{
NSString *path = [NSString stringWithFormat:#"user/list.json/%#/%#", TRAKT_API_KEY, username];
[TNTraktAPIManager requestWithMethod:GET
path:path
parameters:nil
callback:^(id response) {
completion(response);
}];
}
Which calls the following helper method
+ (void)requestWithMethod:(HTTPMethod)method
path:(NSString *)path
parameters:(NSDictionary *)params
callback:(void (^)(id response))completion
{
NSString *methodString = #"POST";
if (method == GET) {
methodString = #"GET";
}
// Setup request
NSURLRequest *request = [[TraktAPIClient sharedClient] requestWithMethod:methodString
path:path
parameters:params];
// Setup operation
AFJSONRequestOperation *operation =
[AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request,
NSHTTPURLResponse *response,
id JSON) {
id jsonResults = JSON;
completion(jsonResults);
} failure:^(NSURLRequest *request,
NSHTTPURLResponse *response,
NSError *error,
id JSON) {
id jsonResults = JSON;
completion(jsonResults);
NSLog(#"%s: error: %#", __PRETTY_FUNCTION__, error);
}];
// TODO: Queue operations
[operation start];
}
If you set a shouldEventually expectation on the helper class and use the receive:(SEL)withArguments:(id)... form, then you can check that the argument received is what you'd expect it to be.
Two gotchas worth knowing about are setting the expectation before making the call; and using the shouldEventually form rather than should so that the test is delayed long enough for the call to be made.
I highly recommend checking out OHHTTPStubs for unit testing your API Classes.
Unit tests should be deterministic and adding internet a potentially unpredictable API into the mix makes the testing conditions non-deterministic.
OHTTPStubs will allow you to stub the response to your outgoing HTTP Requests. Basically, it intercepts your HTTP Traffic and if the request matches criteria that you set, it gives a canned response that you declare in JSON (rather than an unpredictable result from the API). This allows you to configure different response scenarios in your test classes: ie. 404 error, partial response data, etc.. to use in your tests.
Here's an example:
I created this JSON Stub and saved as a JSON file:
{
"devices" : [
{
"alarm" : {
"alarm_id" : 1,
"attack_time" : "<null>",
"defined_time" : "2014-04-14T04:21:36.000Z",
"device_id" : 7,
"is_protected" : 0
},
"device_type" : "iPhone",
"id" : 7,
"os_version" : "7.1"
}
],
"email" : "mystubemail#gmail.com",
"facebook_id" : 5551212,
"id" : 3,
"name" : "Daffy Duck"
}
Whenever I network requests are made in the API Call, this JSON is returned because of this OHHTTPStub which is declared in the test class to run before all tests.
[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
BOOL stringFound;
if ([[request.URL description] rangeOfString:[NSString stringWithFormat:#"%#devices/register?", BASEURL]].location != NSNotFound)
{
stringFound = YES;
}
NSLog(#"%d", stringFound);
return stringFound;
} withStubResponse:^OHHTTPStubsResponse*(NSURLRequest *request) {
// Stub it with our "RegisterDevice.json" stub file
return [[OHHTTPStubsResponse responseWithFileAtPath:OHPathForFileInBundle(#"RegisterDevice.json",bundle)
statusCode:200 headers:#{#"Content-Type":#"text/json"}] requestTime:1.0 responseTime:1.0];
}];
I'm not sure if Kiwi allows for async testing, but if not I also recommend looking into Specta and the matching framework Expecta. They allow for super easy Asynchronous unit testing, which when combined with OHHTTPStubs provides all you need to unit test API Calls.

Resources