Swift JSON Parsing Avoid method declaration for every view - ios

I'm working on fetching data from json and manipulating it into a table view, but the problem is that I have many views, about 5-6 and in each one with separate class, and in each class I declare the same JSON Parsing method again and again, how can I avoid such repetition? I wanted to declare the method as class method in AppDelegate, but I ran into a problem:
After the return of json, I would use it in another function to extract the data, But I'm stuck over here with this error. Please help! If there's a better approach I would like to know.
Thank you
UPDATE
class func get_data_from_url(url:String) -> NSString
{
var json:NSString?
let url = NSURL(string: url)
let urlRequest = NSMutableURLRequest(URL: url!,
cachePolicy: .ReloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: 15.0)
let queue = NSOperationQueue()
NSURLConnection.sendAsynchronousRequest(
urlRequest,
queue: queue,
completionHandler: {(response: NSURLResponse!,
data: NSData!,
error: NSError!) in
if data == nil {
dispatch_async(dispatch_get_main_queue(), {
let alert = UIAlertView()
alert.title = "Connection Error"
alert.message = "Could not connect to the server"
alert.addButtonWithTitle("Ok")
alert.show()
});
}
else {
if data.length > 0 && error == nil{
json = NSString(data: data, encoding: NSASCIIStringEncoding)
}else if data.length == 0 && error == nil{
println("Nothing was downloaded")
return
} else if error != nil{
println("Error happened = \(error)")
return
}
}
}
)
return json!
}
I'm getting an error at the last line, "return json!" -> "fatal error: unexpectedly found nil while unwrapping an Optional value"
Any Suggestions?

Try something like this : your ClassB is where you load the json, so from Class A, you call the method loadJson, and when the json has been downloaded in Class B, you send the json back to Class A :
//Class A :
ClassB.loadJson{ (success, json) -> Void in
if (success) //... you use the json
//println("receive json from Class B, use it here")
return
}
//ClassB
typealias OnComplete = (Bool, NSString) -> ()
class func loadJson(completion:OnComplete){
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), { () -> Void in
//json = ... load your json here
dispatch_async(dispatch_get_main_queue(), { () -> Void in
println("json loaded")
completion(true, json) //send result back to Class A
})//main
}

Related

Unit Testing with URLProtocol subclassing returns a 0 bytes Data instead of nil

First of all, sorry for my english, I hope it won't be a pain to read me :D
I'm currently building an iOS application for a project in my school and I'm facing a problem.
I make an API call and I test it by using the URLProtocol method.
The API call :
task?.cancel()
task = exchangeSession.dataTask(with: request) { data, response, error in
// The dataTask method will execute in a separate queue, so we
// get back into the main one because
// we will modify the user interface with our exchange result
DispatchQueue.main.async {
guard let data = data, error == nil else {
callback(false, nil, error)
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
callback(false, nil, nil)
return
}
guard let responseJSON = try? JSONDecoder().decode(ConvertResponse.self, from: data), let result = responseJSON.result else {
callback(false, nil, nil)
return
}
callback(true, result, nil)
}
}
task?.resume()
The MockURLProtocol :
final class MockURLProtocol: URLProtocol {
// We return true in order to allow URLSession to use this protocol for any URL Request
override class func canInit(with request: URLRequest) -> Bool {
return true
}
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
return request
}
static var loadingHandler: ((URLRequest) -> (Data?, HTTPURLResponse?, Error?))?
override func startLoading() {
guard let handler = MockURLProtocol.loadingHandler else {
print("Loading handler is not set.")
return
}
let (data, response, error) = handler(request)
guard error == nil else {
client?.urlProtocol(self, didFailWithError: error!)
return
}
if let data = data {
client?.urlProtocol(self, didLoad: data)
}
if let response = response {
client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
}
client?.urlProtocolDidFinishLoading(self)
}
override func stopLoading() {}
}
As you can see, it contains a handler which will have the data / response and error.
In one of my tests I want to check the case where I don't have any data, so I put nil for it.
The test :
func testConvertShouldPostFailedCallbackIfNoData() {
MockURLProtocol.loadingHandler = { request in
return (nil, nil, nil)
}
let expectation = XCTestExpectation(description: "Wait for queue change.")
client.convert(from: from, to: to, amount: amount) { success, result, error in
XCTAssertFalse(success)
XCTAssertNil(result)
XCTAssertNil(error)
expectation.fulfill()
}
wait(for: [expectation], timeout: 0.01)
}
Here is my problem: nil doesn't work for my data parameter, it shows me "0 Bytes" instead.
It works for error and response, which is strange to me.
The result : The result
I wanted to ask you why the data parameter isn't nil?
With 0 bytes it's not considered as nil and I'm not going in the right loop in my code.
I tried a lot of breakpoints, but I still can't figure it out.
But one thing is really strange. If I put and error in the handler with nil data, the data will be nil.
Maybe it has something to do with the didFailWithError function?
This function forces data to be nil somehow?
startLoading is not the right place to check for the response or data or error. Instead, use the URLSessionDataDelegate's delegate methods to check the response/data/error received for that request.

Alamofire API request is getting failed with responseSerializationFailed in swift 3

I am calling normal API call with Alamofire, And I am not passing any data with that in networking manager class.
My Networking class is
func executeGetRequest(url:String,requestVC:UIViewController,completionHandler:#escaping (_ responseObject:Any?) -> Void!,failureHandler:#escaping (_ connectionError:NSError?) -> Void!){
//Checking internet alert
if !self.isConnectedToInternet(){
// requestVC.showAlert(kText_AppName, message: kText_NoInternet)
return
}
requestVC.showLoader()
Alamofire.request(url).responseJSON {
(response:DataResponse) in
requestVC.removeLoader()
switch(response.result) {
case .success(_):
if response.result.value != nil{
completionHandler (response.result.value)
}
break
case .failure(let error):
failureHandler (error as NSError?)
break
}
}
}
and i am calling it from my main class
kNetworkManager.executeGetRequest(url: kAppAccessTokenURL, requestVC: self, completionHandler: {
(responseObject) -> () in
print("responseObject:\(responseObject!)")
}, failureHandler: {(error)-> () in
print("response object:\(error!)")
self.showAlert(message: (error?.description)!, title: kText_AppName)
if error?._code == NSURLErrorTimedOut {
//timeout here
self.showAlert(message: kText_timeout, title: kText_AppName)
}
})
Its getting always request fail and showing error as responseSerializationFailed
if I call directly in Main Class without manager class like
Alamofire.request(kAppAccessTokenURL).responseString { response in
I am able to getting response, can anyone suggest me where getting wrong in Network class.
Here you
Alamofire.request(kAppAccessTokenURL).responseString
and there
Alamofire.request(url).responseJSON
look to that
let jsonText = "{\"first_name\":\"Sergey\"}"
var dictonary:NSDictionary?
if let data = jsonText.dataUsingEncoding(NSUTF8StringEncoding) {
do {
dictonary = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? [String:AnyObject]
if let myDictionary = dictonary
{
print(" First name is: \(myDictionary["first_name"]!)")
}
} catch let error as NSError {
print(error)
}
}

Add a return value to a closure [duplicate]

This question already has answers here:
Run code only after asynchronous function finishes executing
(2 answers)
Closed 5 years ago.
I'm not very familiar with closure. I'm using this function to download a JSON file from a remote server
requestJson(){
// Asynchronous Http call to your api url, using NSURLSession:
NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: "http://api.site.com/json")!, completionHandler: { (data, response, error) -> Void in
// Check if data was received successfully
if error == nil && data != nil {
do {
// Convert NSData to Dictionary where keys are of type String, and values are of any type
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! [String:AnyObject]
// Access specific key with value of type String
let str = json["key"] as! String
} catch {
// Something went wrong
}
}
}).resume()
}
Is it possible to make the function requestJson() return the JSON file when its loaded? Or it's not possible because it's loaded asynchronously and could not be ready? Want I'm trying to do is something like following:
requestJson() -> **[String : AnyObject]**{
// Asynchronous Http call to your api url, using NSURLSession:
NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: "http://api.site.com/json")!, completionHandler: { (data, response, error) -> Void in
// Check if data was received successfully
if error == nil && data != nil {
do {
// Convert NSData to Dictionary where keys are of type String, and values are of any type
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! [String:AnyObject]
// Access specific key with value of type String
**return json**
} catch {
// Something went wrong
}
}
}).resume()
}
You can give the function params an #escaping callback returning an array or whatever you need;
An example of this for a network request;
class func getGenres(completionHandler: #escaping (genres: NSArray) -> ()) {
...
let task = session.dataTask(with:url) {
data, response, error in
...
resultsArray = results
completionHandler(genres: resultsArray)
}
...
task.resume()
}
Then to call it you could do something like this;
override func viewDidLoad() {
getGenres {
genres in
print("View Controller: \(genres)")
}
}
//MARK: Request method to get json
class func requestJSON(completion: #escaping (returnModel: String?) -> Void) {
//here you write code for calling API
}
//MARK: Calling function to retrieve return string
API.requestJSON(completion: { (string) in
//here you can get your string
})
func httpGet(request: NSURLRequest!, callback: (NSString, NSString?) -> Void)
{
var session = NSURLSession.sharedSession()
var task = session.dataTaskWithRequest(request){
(data, response, error) -> Void in
if error != nil {
callback("", error.localizedDescription)
} else {
var result = NSString(data: data, encoding:
NSASCIIStringEncoding)!
callback(result, nil)
}
}
task.resume()
}
func makeRequest(callback: (NSString) ->Void) -> Void {
var request = NSMutableURLRequest(URL: NSURL(string: "http://sample_url")!)
var result:NSString = ""
httpGet(request){
(data, error) -> Void in
if error != nil {
result = error!
} else {
result = data
}
callback(data)
}
}
Usage:-
self.makeRequest(){
(data) -> Void in
println("response data:\(data)")
}

Alamofire 4.0 error from server

Hi I just migrated to alamofire 4 and I just want to send the error coming from the server, I found a couple ways but I just want to make sure that this is the correct way, here is my custom responseobject class
public protocol ResponseObject {
init?(response: HTTPURLResponse, representation: Any)
}
enum BackendError: Error {
case network(error: Error)
case dataSerialization(error: Error)
case jsonSerialization(error: Error)
case xmlSerialization(error: Error)
case objectSerialization(reason: String)
}
extension DataRequest {
public typealias Validation = (URLRequest?, HTTPURLResponse, Data?) -> ValidationResult
func responseObject<T: ResponseObject>(
queue: DispatchQueue? = nil,
completionHandler: #escaping (DataResponse<T>) -> Void)
-> Self
{
let responseSerializer = DataResponseSerializer<T> { request, response, data, error in
guard error == nil else {
let jsonResponseSerializer = DataRequest.jsonResponseSerializer(options: .allowFragments)
let result = jsonResponseSerializer.serializeResponse(request, response, data, nil)
debugPrint(result)
return .failure(BackendError.network(error: error!))
}
let jsonResponseSerializer = DataRequest.jsonResponseSerializer(options: .allowFragments)
let result = jsonResponseSerializer.serializeResponse(request, response, data, nil)
guard case let .success(jsonObject) = result else {
return .failure(BackendError.jsonSerialization(error: result.error!))
}
guard let response = response, let responseObject = T(response: response, representation: jsonObject) else {
return .failure(BackendError.objectSerialization(reason: "JSON could not be serialized: \(jsonObject)"))
}
return .success(responseObject)
}
return response(queue: queue, responseSerializer: responseSerializer, completionHandler: completionHandler)
}
}
I add a debugprint so see the error from the server and I see it but do I have to serialize de data again inside the error?? and how can I pass the message to my custom error?

Making a re-useable function of JSON URL fetching function in SWIFT 2.0

I am stuck in a problem. I think it is all due to my weak basics. I am sure someone can help me easily and put me in the right direction.
I have different segues and all get the data from JSON via remote URL.
So in-short all segues need to open URL and parse JSON and make them into an ARRAY
I have made the first segue and it is working fine.
Now i plan to use the functions where it download JSON and turns it into ARRAY as a common function
I read in another page on stackoverflow that I can declare all common functions outside the class in ViewController
I hope everyone is with me this far.
now in ViewController i declare a function
getDataFromJson(url: String)
This function code looks like following
func getJsonFromURL(url: String)
{
// some class specific tasks
// call the common function with URL
// get an array
let arrJSON = getJsonArrFromURL(url)
for element in arrJSON
{
// assign each element in json to ur table
print("Element: \(element)")
}
// some class specific tasks
}
and this will call the common function declared outside the score of class
getArrFromJson(url: String) -> NSArray
This common function is just very generic.
Take a URL, call it, open it, parse its data into ARRAY and return it back.
The problem i am stuck is where to put the return
It returns empty array as the task is not finished and i am clueless
func getJsonArrFromURL(var url: String) -> NSArray
{
var parseJSON : NSArray?
if ( url == "" )
{
url = self.baseURLHomepage
}
print("Opening a JSON URL \(url)")
let myUrl = NSURL(string: url);
let request = NSMutableURLRequest(URL:myUrl!);
request.HTTPMethod = "GET";
let postString = "";
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding);
let task = NSURLSession.sharedSession().dataTaskWithRequest(request)
{
data, response, error in
if ( error != nil )
{
print("Error open JSON url \n\(error)")
return
}
do
{
parseJSON = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? NSArray
}
catch
{
self.showAlert("Error", msg: "Error occurred while trying to process the product information data")
print("Error occured in JSON = \(error)")
}
}
task.resume()
return parseJSON!
}
You can probably add a method like below in any of your class
func post(url: String, info: String, completionHandler: (NSString?, NSError?) -> ()) -> NSURLSessionTask {
let URL = NSURL(string: url)!
let request = NSMutableURLRequest(URL:URL)
request.HTTPMethod = "GET"
let bodyData = info
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
dispatch_async(dispatch_get_main_queue()) {
guard data != nil else {
print("response String is nil")
completionHandler(nil, error)
return
}
if let dataNew = data {
completionHandler(NSString(data: (NSData(base64EncodedData: dataNew, options: NSDataBase64DecodingOptions([])))!, encoding: NSASCIIStringEncoding), nil)
}
}
}
task.resume()
return task
}
and access it anywhere like
let url = "your URL String"
let info = "The data you would like to pass"
yourClassName.post(url, info: info) { responseString, error in
guard responseString != nil else {
print("response String is nil")
print(error)
return
}
do {
if !(responseString as? String)!.isEmpty {
let json = try NSJSONSerialization.JSONObjectWithData((responseString as! String).data, options: NSJSONReadingOptions.init(rawValue: 0))
//process your json here
}
} catch {
print("Error\n \(error)")
return
}
}
Extend your string like follows
extension String {
var data:NSData! {
return dataUsingEncoding(NSUTF8StringEncoding)
}
}

Resources