NSJSONSerialization not updating (possibly getting cached) - ios

Hi I'm downloading JSON in my app to the device with NSJSONSerialization and it seems that when I update the JSON file and re-run the download JSON function it doesn't download the new JSON. It's just downloading the non updated JSON file. I've tried running the app on different devices and it works the first time but once I update the JSON again it doesn't download the new JSON. I'm thinking it may be getting cached but I'm not sure.
Here's my code:
func downloadJSON(){
let p = NSURLRequest(URL: NSURL(string: "https://themartini.co/pythonStuff/quotey.json")!)
let sharedSession = NSURLSession.sharedSession()
let dwn = sharedSession.dataTaskWithRequest(p, completionHandler: { (data : NSData!,re : NSURLResponse!, error: NSError!) -> Void in
if error == nil {
let js : AnyObject! = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil) as NSArray // downloads JSON and puuts it into an array
println(js)
dispatch_async(dispatch_get_main_queue()){
self.quotes = js as NSArray
NSNotificationCenter.defaultCenter().postNotificationName("dataTaskFinished", object: nil)
}
}
})
dwn.resume()
}
Any ideas? Thanks.

You should pass the correct cache policy to your NSURLRequest. There is an alternative constructor that includes this parameter.
let p = NSURLRequest(URL: url,
cachePolicy: ReloadIgnoringLocalCacheData,
timeoutInterval: 15.0)

You can add a cachebuster to your NSURLRequest. (change the url to https://themartini.co/pythonStuff/quotey.json?23421231231
where the string of numbers at the end is random. This will break the cache. Also, instead of using NSURLSession, look into NSURLConnection.SendAsynchroniousRequest

Related

How to check if server is responding in Swift 5

I'm looking for a Swift 5 version of something like the fileExists function (below). For context I have an app that is heavily dependent on interaction with a remote server. For resilience we have created a second remote server and want the app to check if the first one is available, and if not, use the second one. I have found possible solutions using URLSession.shared.dataTask but they all just display a message rather than return an alternative url to use. Any suggestions would be most welcome.
func fileExists(url : NSURL!) -> Bool {
let req = NSMutableURLRequest(URL: url)
req.HTTPMethod = "HEAD"
req.timeoutInterval = 1.0 // Adjust to your needs
var response : NSURLResponse?
NSURLConnection.sendSynchronousRequest(req, returningResponse: &response, error: nil)
return ((response as? NSHTTPURLResponse)?.statusCode ?? -1) == 200
}

Using Facebook's Graph API in iOS

I went over the Facebook developer docs and generally around the internet but couldn't quite understand how to use the Facebook Graph API in iOS.
Specifically, I need to fetch a list of nearby locations using a given latitude&longitude. I came across this:
GET /search?q={query}&type=place&center={lat},{lng}&distance={distance}
But I don't understand where to even begin.
I have the Facebook SDK set up.
How do I call such request?
Thanks in advance
I think that you need to use the Facebook Graph request methods, some thing like this
NSDictionary *params = #{#"fields" : #"", #"redirect" : #NO, #"type" : #"large"};
NSString *getPath = [NSString stringWithFormat:#"/%#/picture", #"fabebook_user_id"];
FBSDKGraphRequest *photoRequest = [[FBSDKGraphRequest alloc] initWithGraphPath:getPath parameters:params HTTPMethod:#"GET"];
with this method i am getting the user photo, I think so this method can be useful for you too :)
I managed by performing a simple URL request:
if let accessToken: FBSDKAccessToken? = FBSDKAccessToken.currentAccessToken() {
let urlString = "https://graph.facebook.com/search?type=place&center=\(geoPoint!.latitude),\(geoPoint!.longitude)&distance=1000&limit=10&access_token=\(accessToken!.tokenString)"
let URL = NSURL(string: urlString)!
let request = NSMutableURLRequest(URL: URL)
request.HTTPMethod = "GET"
let task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
if response != nil {
let json = try! NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers)
print(json)
}
})
task.resume()
}

How can I process multiple links of JSON data?

The code works perfectly. The problem is that, after trying for a while, I cannot figure out how to make my program process a second link of different JSON data.
Here is my viewDidLoad where everything goes on:
override func viewDidLoad() {
super.viewDidLoad()
var err: NSError?
let urlPath: String = "https://na.api.pvp.net/api/lol/na/v1.4/summoner/by-name/" + searchFieldDataPassed + "?api_key=(removed my private api key for obvious reasons"
var url: NSURL = NSURL(string: urlPath)!
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url) { data, response, error in
// cast response as NSHTTPURLResponse and switch on statusCode if you like
if let httpResponse = response as? NSHTTPURLResponse { switch httpResponse.statusCode { case 200..<300: println("OK") default: println("Not OK") } }
// parse JSON using NSJSONSerialization if you've got data
if let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as? NSDictionary,
let include = jsonResult.objectForKey(self.searchFieldDataPassed) as? NSDictionary {
if let summLevel = include[ "summonerLevel" ] as? NSNumber {
dispatch_async(dispatch_get_main_queue()) {
self.summonerLevel.text = "\(summLevel.integerValue)"
println("summoner level: \(summLevel.integerValue)")
}
}
if let profIconId = include[ "profileIconId" ] as? NSNumber {
dispatch_async(dispatch_get_main_queue()) {
self.profileIconId.text = "\(profIconId.integerValue)"
println("profile icon id: \(profIconId.integerValue)")
}
}
if let idNum = include [ "id" ] as? NSNumber {
dispatch_async(dispatch_get_main_queue()) {
self.idNumber = idNum
println("id number: \(self.idNumber)")
}
}
}
// spawn off another network call here if you like
}
task.resume()
}
That is from my secondViewController where all the processing goes on for JSON and then is displayed.
Here is the JSON data that I'm processing (for the first JSON parsing):
{"soon2challenger":{"id":43993167,"name":"soon2challenger","profileIconId":844,"summonerLevel":30,"revisionDate":1435549418000}}
All of that works fine, now, I want to process this JSON data which actually takes the id from the first parsed JSON data and uses it in the link to process more data, which I would like to output, part of it, to the screen.
Second JSON data:
{"summonerId":43993167,"playerStatSummaries":[{"playerStatSummaryType":"AramUnranked5x5","wins":25,"modifyDate":1423007927000,"aggregatedStats":{"totalChampionKills":676,"totalTurretsKilled":20,"totalAssists":991}},{"playerStatSummaryType":"CAP5x5","wins":15,"modifyDate":1429065922000,"aggregatedStats":{"totalChampionKills":312,"totalMinionKills":4885,"totalTurretsKilled":31,"totalNeutralMinionsKilled":511,"totalAssists":216}},{"playerStatSummaryType":"CoopVsAI","wins":28,"modifyDate":1421882181000,"aggregatedStats":{"totalChampionKills":266,"totalMinionKills":2802,"totalTurretsKilled":50,"totalNeutralMinionsKilled":385,"totalAssists":164,"maxChampionsKilled":0,"averageNodeCapture":0,"averageNodeNeutralize":0,"averageTeamObjective":0,"averageTotalPlayerScore":49,"averageCombatPlayerScore":0,"averageObjectivePlayerScore":49,"averageNodeCaptureAssist":0,"averageNodeNeutralizeAssist":0,"maxNodeCapture":0,"maxNodeNeutralize":0,"maxTeamObjective":0,"maxTotalPlayerScore":49,"maxCombatPlayerScore":0,"maxObjectivePlayerScore":49,"maxNodeCaptureAssist":0,"maxNodeNeutralizeAssist":0,"totalNodeNeutralize":0,"totalNodeCapture":0,"averageChampionsKilled":0,"averageNumDeaths":0,"averageAssists":0,"maxAssists":0}},{"playerStatSummaryType":"CoopVsAI3x3","wins":15,"modifyDate":1421882181000,"aggregatedStats":{"totalChampionKills":140,"totalMinionKills":1114,"totalTurretsKilled":9,"totalNeutralMinionsKilled":449,"totalAssists":91}},{"playerStatSummaryType":"OdinUnranked","wins":1,"modifyDate":1421882181000,"aggregatedStats":{"totalChampionKills":31,"totalAssists":45,"maxChampionsKilled":10,"averageNodeCapture":4,"averageNodeNeutralize":4,"averageTeamObjective":0,"averageTotalPlayerScore":843,"averageCombatPlayerScore":268,"averageObjectivePlayerScore":575,"averageNodeCaptureAssist":3,"averageNodeNeutralizeAssist":1,"maxNodeCapture":6,"maxNodeNeutralize":7,"maxTeamObjective":2,"maxTotalPlayerScore":1468,"maxCombatPlayerScore":529,"maxObjectivePlayerScore":939,"maxNodeCaptureAssist":5,"maxNodeNeutralizeAssist":2,"totalNodeNeutralize":22,"totalNodeCapture":25,"averageChampionsKilled":5,"averageNumDeaths":5,"averageAssists":8,"maxAssists":19}},{"playerStatSummaryType":"RankedSolo5x5","wins":116,"losses":120,"modifyDate":1433630047000,"aggregatedStats":{"totalChampionKills":1699,"totalMinionKills":33431,"totalTurretsKilled":219,"totalNeutralMinionsKilled":6501,"totalAssists":1969}},{"playerStatSummaryType":"RankedTeam3x3","wins":0,"losses":0,"modifyDate":1377726216000,"aggregatedStats":{}},{"playerStatSummaryType":"RankedTeam5x5","wins":3,"losses":0,"modifyDate":1383784473000,"aggregatedStats":{"totalChampionKills":28,"totalMinionKills":636,"totalTurretsKilled":6,"totalNeutralMinionsKilled":101,"totalAssists":41}},{"playerStatSummaryType":"Unranked3x3","wins":9,"modifyDate":1421882181000,"aggregatedStats":{"totalChampionKills":90,"totalMinionKills":1427,"totalTurretsKilled":11,"totalNeutralMinionsKilled":428,"totalAssists":105}},{"playerStatSummaryType":"URF","wins":4,"modifyDate":1435024847000,"aggregatedStats":{"totalChampionKills":68,"totalMinionKills":642,"totalTurretsKilled":14,"totalNeutralMinionsKilled":182,"totalAssists":55}},{"playerStatSummaryType":"Unranked","wins":566,"modifyDate":1435549418000,"aggregatedStats":{"totalChampionKills":8419,"totalMinionKills":128213,"totalTurretsKilled":960,"totalNeutralMinionsKilled":26117,"totalAssists":7812}}]}
Heres the link of the second JSON data I want to parse (just adding it, could be useful, but not sure):
https://na.api.pvp.net/api/lol/na/v1.3/stats/by-summoner/43993167/summary?season=SEASON2015&api_key=(took-out-my-private-api-key-for-obvious-reasons)
The link doesn't work because I have to keep my api key private to myself, but the JSON data that it displays is right above the link, which is the what it would result if you were to use the link with the api key.
Just to restate, I would like to process the second part (above of this) of JSON data, but I do not understand how to process multiple links of JSON. I have the first JSON data parsed, but am unable to parse the second JSON data.
I believe Apple is deprecating NSURLConnection. Take a look at NSURLSession. Using it, you can pass in a completion block that takes three arguments: NSData?, NSURLResponse?, and NSError?. The data object contains the JSON you can pass into the JSON serializer. After that, if you need to make another network call, just call it from inside the completion block with another NSURLSession data task. Alamofire is a great framework, but sometimes you don't need everything it provides, and it adds complexity into your app that if something goes wrong or doesn't behave the way you intend/understand, you may not fully understand why. If you want to keep it simple and under your control, use NSURLSession.
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url) { data, response, error in
// cast response as NSHTTPURLResponse and switch on statusCode if you like
// parse JSON using NSJSONSerialization if you've got data
// spawn off another network call here if you like
}
task.resume() // or in Swift 2, task?.resume()
First, i would totally prefer using some common frameworks for http requests - expecially if youre new in swift. For example here with alamofire.
https://github.com/Alamofire/Alamofire
There is also a version with integrated SwiftyJSON, so you are able to parse JSON Responses very easily.
https://github.com/SwiftyJSON/Alamofire-SwiftyJSON
So if you want to make a request, use this:
Alamofire.request(.GET, "http://httpbin.org/get")
.responseJSON { (_, _, json, _) in
var json = JSON(json)
// get the id out (depends on your structure of JSON):
let id = json["id"].int
}
Now you are able to perform a second Request (with the same Code) - Read the Documentation, how to make different Requests (like with POST) and add Parameters.
If you want to use Segues, so you want to load more data from the ID in another ViewController, you can use Segues to push the data to a second ViewController, and Load the new Content from JSON when the new ViewController is initialised.
Check out this how to send data through segues:
Sending data with Segue with Swift

Does Apple test iOS apps offline? How to handle error if no connection?

I have submitted an app to iTunes and Apple rejected it because of a crash.
I symbolicated and analyzed the crashreport and saw that it crash at a json call.
I try to reproduce it and I found that it just happens when I turn off my wlan.
Does Apple test apps offline?
How can I handle this error? And make my jsoncall better.
This is my method:
var session = NSURLSession.sharedSession();
var uri = "/GetNews";
let request : NSMutableURLRequest = CreateRequest(uri, HTTPmethod: "GET");
let task : NSURLSessionDataTask = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
var error: AutoreleasingUnsafeMutablePointer<NSError?> = nil;
let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: error) as? Dictionary<String, AnyObject>;
let resp : NewsResponse = NewsResponse(jsonData: jsonResult!);
completionHandler?(resp);
});
task.resume();
It crashs at let resp..., because jsonResult is nil and I use !
Of course Apple tests apps offline, and you should too. You should plan on every call that requires an internet connection failing, and you should handle every error appropriately.
The appropriately part is up to your app. For example, some apps (like Facebook) let you read posts you've already downloaded, and queue up posts you write to be sent when you get an internet connection. Some apps just don't work at all without an internet connection and it doesn't make sense for them to do anything but put up an error message (like, for example, a iTunes radio).
If your app is a news reader, perhaps the best thing to do is use a cache and let them read news they've downloaded in the past. A simple, unobtrusive message letting them know they're offline and new articles will be downloaded once they're back on would suffice; a crash, though, is very bad in terms of usability and utility.
TO FIX CRASH
As Eric stated, you should use "safe unwrapping", better known as optional binding. Try this:
var session = NSURLSession.sharedSession();
var uri = "/GetNews";
let request : NSMutableURLRequest = CreateRequest(uri, HTTPmethod: "GET");
let task : NSURLSessionDataTask = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
var error: AutoreleasingUnsafeMutablePointer<NSError?> = nil;
let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: error) as? Dictionary<String, AnyObject>;
//if non-nil, assigns jsonResult to nonNilJsonResult
if let nonNilJsonResult = jsonResult {
let resp : NewsResponse = NewsResponse(jsonData: nonNilJsonResult!);
completionHandler?(resp);
}
});
task.resume();

save session in http request swift

in my app I'm using JSON and I made a session recently so if I would like to make some http request to get data for a specific user, the user must log in before (also used by http request).
in the safari when I entering the url's of login and then the url of receive data, it does that as needed.
but in my app, I first call login and then the url for getting data, but it's probably starting a new session in every url request which leads me to get an error and not receive the data.
my url request function is:
static func urlRequest (adress: String, sessionEnded: (NSDictionary->Void)?){
println(adress)
var urli = NSURL(string: adress)
var request = NSURLRequest(URL: urli!)
var rVal = "";
self.task = NSURLSession.sharedSession().dataTaskWithURL(urli!) {(data, response, error) in
var parseError: NSError?
let parsedObject: AnyObject? = NSJSONSerialization.JSONObjectWithData(data,
options: NSJSONReadingOptions.AllowFragments,
error:&parseError)
let po = parsedObject as NSDictionary
if let a = sessionEnded{
sessionEnded!(po)
}
}
task!.resume()
}
thanks in advance!!
You have shared only half of the puzzle with us, the client code. We can't comment on why the app isn't working with a clearer picture of what the server API. For example, once you "log in", how do subsequent queries confirm that the request is coming from valid session. Furthermore, you report that "every url request which leads me to get an error". Well, what error do you receive? You have to be far more specific regarding the precise errors/crashes you are receiving. BTW, are you logging on to some service with a well-defined API or are you writing that code yourself, too?
Having said that, I might suggest a few refinements to this method:
The sessionEnded (which I've renamed completionHandler to conform to informal standard naming conventions), probably should return an optional NSError object, too, so the caller can detect if there was an error.
Your unwrapping of the sessionEnded completion handler can be simplified to use ?.
When you parse the object, you should feel free to perform the optional cast, too.
You probably want to detect a network error (in which case data would be nil) and return the network NSError object.
Minor point, but I'd probably also rename the function to conform to Cocoa naming conventions, using a verb to start the name. Perhaps something like performURLRequest.
This is your call, but I'd be inclined to have the method return the NSURLSessionTask, so that the caller could use that task object if it wanted to (e.g. save the task object so that it could cancel it later if it wanted to).
Thus, that yields something like:
func performURLRequest (address: String, completionHandler: ((NSDictionary!, NSError!) -> Void)?) -> NSURLSessionTask {
let url = NSURL(string: address)
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) {(data, response, error) in
if data == nil {
sessionEnded?(nil, error)
} else {
var parseError: NSError?
let parsedObject = NSJSONSerialization.JSONObjectWithData(data, options: nil, error:&parseError) as? NSDictionary
completionHandler?(parsedObject, parseError)
}
}
task.resume()
return task
}
And you'd invoke it like:
performURLRequest("http://www.example.com/some/path") { responseDictionary, error in
if responseDictionary == nil {
// handle error, e.g.
println(error)
return
}
// use `responseDictionary` here
}

Resources