Error handling in block with Swift syntax - ios

The error handling here doesn't feel right. Anyone have any suggests for how to improve it? Using optional binding to establish errors and a return value variable.
That cool?
class ChargePointsFetcher {
func getDevices(location: NSURL, completion handler:([ChargeDevice]?, error: NSError?) -> Void) {
let request = NSURLRequest(URL: location)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request, completionHandler: { (let data, let response, let error) -> Void in
var returnValue: NSError?
if let e = error {
returnValue = e
}
var collection: [ChargeDevice]?
if returnValue == nil {
collection = [ChargeDevice]()
var parsingError: NSError?
if let json: NSDictionary = NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments, error: &parsingError) as? NSDictionary {
if let chargeDevices = json.valueForKey("ChargeDevice") as? NSArray {
for chargeDevice in chargeDevices {
let device = ChargeDevice()
collection!.append(device)
}
}
}
if let e = parsingError {
returnValue = e
}
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
handler(collection, error: returnValue)
})
})
}
}

In my opinion, you should try very hard to avoid the (Value?, Error?) pattern when you can. It creates bad corner cases since two combinations are illegal. Instead, when possible, I suggest (Value, Error?) any time Value has a sensible "zero". In this case "zero" is "empty array". This matches the ObjC sense very closely, since a nil NSArray is very similar to an empty array.
Doing that, you can substantially simplify this code:
func getDevices(location: NSURL, completion handler:([ChargeDevice], error: NSError?) -> Void) {
// Note that error is marked "var" so we can modify it, and switched from NSError! to NSError?
let task = session.dataTaskWithRequest(request, completionHandler: { (let data, let response, var error: NSError?) -> Void in
var result = [ChargeDevice]()
if error == nil {
// Avoid NSArray and NSDictionary wherever you can in Swift
if let
json = NSJSONSerialization.JSONObjectWithData(data,
options: .AllowFragments,
error: &error
) as? [String:AnyObject],
chargeDevices = json["ChargeDevice"] as? [AnyObject] {
// map is much simpler in this case, but in your full code, for may fine fine
result = chargeDevices.map{ _ in ChargeDevice() }
}
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
handler(result, error: error)
})
})
}
Note that in your code and my code, incorrect but valid JSON will not generate an error of any kind. It'll just quietly skip through the as? calls. I would probably move all of this JSON work into another function to keep this closure from getting out of hand.
Another approach here is called a Result. My preferred example is Rob Rix's. Even though I've done a lot of work on Result, I've personally find it difficult to bridge to Cocoa cleanly. It's useful if your entire program uses it, and if you encapsulate most Cocoa interaction, but I've found it cumbersome to use as a one-off solution. (Many people would disagree with me here, it's just my experience. Result is definitely worth exploring to form your own opinion.)

Related

Migration Alamofire 4 to 5 build issue

I am doing migration after 2 years a lots things have been changed, now flagging a lots of error while building. Most are related to Alamofire 5. Now there are many error keeps coming fixing one by one.
Error: // ERROR: Cannot specialize non-generic type
public static func ObjMappingSerializer<T: Mappable>(_ keyPath: String?) -> DataResponseSerializer<T> { 'DataResponseSerializer'
return DataResponseSerializer { request, response, data, error in
//LogResponse(response, data: data, error: error)
Logger._reqresLogger.logResponse(response, data: data, error: error)
guard error == nil else {
return .failure(parseErrorResponse(data: data, response: response, errorType: error!))
}
guard let _ = data else {
return .failure(errorForNilData())
}
let JSONToMap = deserializeJSON(request: request, response: response, data: data, error: error, keyPath: keyPath)
if let json = JSONToMap as? [String:Any], let parsedObject = Mapper<T>().map(JSON:json) {
return .success(parsedObject)
}
let errorCode = response?.statusCode ?? NSURLErrorCannotParseResponse
return .failure(APIError(code: errorCode, errorUserInfo: nil))
}
}
Fixed by autosuggestion however next error comes
Error: Trailing closure passed to parameter of type 'DataPreprocessor' that does not accept a closure
public static func ObjMappingSerializer(_ keyPath: String?) -> DataResponseSerializer {
return DataResponseSerializer { request, response, data, error in
//LogResponse(response, data: data, error: error)
Logger._reqresLogger.logResponse(response, data: data, error: error)
guard error == nil else {
return .failure(parseErrorResponse(data: data, response: response, errorType: error!))
}
guard let _ = data else {
return .failure(errorForNilData())
}
let JSONToMap = deserializeJSON(request: request, response: response, data: data, error: error, keyPath: keyPath)
if let json = JSONToMap as? [String:Any], let parsedObject = Mapper<T>().map(JSON:json) {
return .success(parsedObject)
}
let errorCode = response?.statusCode ?? NSURLErrorCannotParseResponse
return .failure(APIError(code: errorCode, errorUserInfo: nil))
}
}
Now in Alamofire many methods have been removed in Alamofire 5. How can I fix these errors?
You can no longer initialize a DataResponseSerializer with a closure. I suggest you reevaluate your parsing needs and rebuild around responseDecodable. If you need, you can create your own serializer by adopting ResponseSerializer. Your logic would be the same, just copied into the parse method.

Swift 2 try/catch

I've started converting one of my projects to Swift 2 and I ran into this issue. To start this block below is perfectly valid try/catch, in fact it was generated by the Xcode migration tool.
do {
requestData = try NSJSONSerialization.dataWithJSONObject(requestContents, options: [])
} catch var error as NSError {
requestError = error
requestData = nil
}
If I use that same code inside a closure, such as a dataTaskWithRequest I get an error. The error is at the task assignment, but its the catch that causes it. The following also works but I'm not capturing the error.
let task = session.dataTaskWithRequest(request, completionHandler: { (data, response, taskError) -> Void in
if taskError != nil {
NSLog("Error making request: " + taskError!.localizedDescription)
}
else {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableLeaves) as? NSDictionary
if let parseJSON = json as NSDictionary? {
// do some parsing here
}
}
catch {
NSLog("Error in JSON serialization")
}
}
})
task.resume()
but if I try to capture the error with:
} catch let e as NSError {
I get this error:
Invalid conversion from throwing function of type '(_, _, _) throws ->
Void' to non-throwing function type '(NSData?, NSURLResponse?,
NSError?) -> Void'
I did discover that:
} catch _ {
works but a lot of good that does me.
Am I missing something or should I be filing a bug?
(This is Xcode 7b5)
The completion handler of dataTaskWithRequest is not designed to throw error like JSONObjectWithData of NSJSONSerialization, whose signature is:
class func JSONObjectWithData(data: NSData, options opt: NSJSONReadingOptions) throws -> AnyObject
Doing the following would work (same as what you have tried):
catch _
But that won't give us any detail about the error from the one that throws, namely, from class func JSONObjectWithData.
As a result, we need a way to consume the non-throwable dataTaskWithRequest while preserving the one that throws - JSONObjectWithData.
I have tried the following:
catch let error as NSError
{
//error specific to JSON serialization
NSLog("Error in JSON serialization \(error)")
}
catch
{
//exhaust the error
NSLog("Some error")
}

using JSON data in SWIFT UI controls

I need to use data from a JSON web service to update controls in a SWIFT app. I can get the data no problem, but I can't seem to access the UI controls from within the task block and I can't get the JSON to persist out of the block. I've searched around and haven't found the answer. Here's my test code. The current result is that value1Result has a value inside task, but is nil outside. Thanks in advance.
var jsonResult:NSDictionary!
var value1Result:String!
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) { data, response, error in
var error: NSError?
jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments, error: &error)!
as! Dictionary<String, AnyObject>
println(jsonResult)
if let value1 = jsonResult["value1"] {
println(value1)
value1Result = value1 as! String
}
}
task.resume()
self.textView1.text = value1Result
You can use asynchronous block to update the main UI
dispatch_async(dispatch_get_main_queue()) {
//Update your UI
}
With your code
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) { data, response, error in
var error: NSError?
jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments, error: &error)!
as! Dictionary<String, AnyObject>
println(jsonResult)
if let value1 = jsonResult["value1"] {
println(value1)
dispatch_async(dispatch_get_main_queue()) {
//Update your UI
value1Result = value1 as! String
self.yourtextview.text = value1 as! String
}
}
}
task.resume()
Doing proper network coding is hard. There's a lot of problems with your code both stylistically, in terms of robustness, and actual functionality. That is why networking vs. UI is always layered with a library like AFNetworking. Doing it right yourself is just too much manual labor.
Consider what it takes to check for errors properly and hand off the code properly to the UI thread:
let task = session.dataTaskWithURL(url) {
[unowned self]
(data: NSData?, response: NSURLResponse?, netError: NSError?) in
if let statusCode = (response as? NSHTTPURLResponse)?.statusCode {
if statusCode == 200 {
if let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments, error: &error) as? Dictionary<String, AnyObject> {
if let value1 = jsonResult["value1"] as? String {
dispatch_async(dispatch_get_main_queue()) {
self.textView1.text = value1
}
}
else {
println("JSON format error, key value1 not defined")
}
}
else {
println("JSON parsing error: \(error.localizedDescription)")
}
else { // status code other than 200
println("HTTP Error \(statusCode)")
}
}
else { // No HTTP response available at all, couldn't hit server
println("Network Error: \(netErr!.localizedDescription)")
}
}
task.resume()

Swift - Returning a JSON object from API call in Model as Dictionary to use in View Controller

I have recently started experimenting with Swift and am new to more strongly typed programming languages.
I am trying to build a basic API call to http://openweathermap.org/api which will have a searchbox in the UIView that takes a city name and returns the relevant weather data.
My problem is figuring out how to return the JSON response I get back from my API call in my Model as a Dictionary that I can then use as a variable in my ViewController.
I have experimented with a variety of methods but continue to get a 'Dictionary not convertible to Void' error. From research and this article (Dictionary is not convertible to Void) it seems returning a closure might offer the answer but I am struggling to implement given that I only want to pass a cityname string parameter in my ViewController searchButton function.
Detailed code snippets below, thanks for help!
My API call in Model below which currently works at pulling down JSON object
class API {
func weatherSearch(#urlSearch: String) -> Dictionary<String,AnyObject>
{
let urlPath = "http://api.openweathermap.org/data/2.5/weather?q=" + urlSearch
let url = NSURL(string: urlPath)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url!, completionHandler: {data, response, error -> Void in
println("Task completed")
if(error != nil) {
// If there is an error in the web request, print it to the console
println(error.localizedDescription)
}
var err: NSError?
if let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as? NSDictionary {
var dataOut = jsonResult as Dictionary<String,AnyObject>
return dataOut
//omitted some additional error handling code
}
})
task.resume()
}
}
My ViewController where instantiate API and take input from Searchfield
#IBOutlet weak var searchField: UITextField!
#IBAction func searchButton() {
let api = API()
var dataOut = api.weatherSearch(urlSearch: searchField.text!)
println(dataOut)
self.performSegueWithIdentifier("Search", sender: nil)
}
Using the callback technique as hinted to by the comment above, try something like this
func weatherSearch(#urlSearch: String, callback: (Dictionary<String,AnyObject> -> ())) {
let urlPath = "http://api.openweathermap.org/data/2.5/weather?q=" + urlSearch
let url = NSURL(string: urlPath)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url!, completionHandler: {data, response, error -> Void in
println("Task completed")
if(error != nil) {
// If there is an error in the web request, print it to the console
println(error.localizedDescription)
}
var err: NSError?
if let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as? NSDictionary {
var dataOut = jsonResult as! Dictionary<String,AnyObject>
callback(dataOut)
//omitted some additional error handling code
}
})
task.resume()
}
weatherSearch(urlSearch: "endpoint here") { dictionary in
println(dictionary)
}

How to store JSON response in Core Data?

I have a Core Data object.
I was curious, as a relatively inexperienced iPhone developer, whether anyone could recommend an approach, and a suitable JSON implementation for the iPhone, which would allow me to store JSON responses as Core Data objects.
I'm getting 5 records (dictionaries) from JSON response. I need to store them in Core Data, and retrieve them when ever necessary .
I have searched, unsuccessfully, for a tutorial/code sample on this point so any assistance would be gratefully received.
You can check here also, They have explained from beginning, you can follow and will get it
http://www.appcoda.com/fetch-parse-json-ios-programming-tutorial/
http://maniacdev.com/2013/04/library-for-integrating-web-services-turning-json-data-into-core-data-backed-native-objects
Set your Core Data property to transformable. Then use this extension:
extension NSObject {
static func storeJSON(dataToStore: [String: AnyObject], completion: (data: NSData?) -> Void) {
do {
let data = try NSJSONSerialization.dataWithJSONObject(dataToStore, options: [])
completion(data: data)
} catch let error as NSError {
print("NSJSONSerialization Error: \(error)")
completion(data: nil)
}
}
func retrieveJSON(completion: (json: JSON) -> Void) {
if let data = self as? NSData {
do {
let nsJSON = try NSJSONSerialization.JSONObjectWithData(data, options: [])
completion(json: JSON(nsJSON))
} catch let error as NSError {
print("NSJSONSerialization Error: \(error)")
completion(json: nil)
}
}
}
}
If you don't use SwiftJSON then just use:
extension NSObject {
static func storeJSON(dataToStore: [String: AnyObject], completion: (data: NSData?) -> Void) {
do {
let data = try NSJSONSerialization.dataWithJSONObject(dataToStore, options: [])
completion(data: data)
} catch let error as NSError {
print("NSJSONSerialization Error: \(error)")
completion(data: nil)
}
}
func retrieveJSON(completion: (json: AnyObject?) -> Void) {
if let data = self as? NSData {
do {
let nsJSON = try NSJSONSerialization.JSONObjectWithData(data, options: [])
completion(json: nsJSON)
} catch let error as NSError {
print("NSJSONSerialization Error: \(error)")
completion(json: nil)
}
}
}
}
Example use with user.jsonTest as a transformable in core data:
func testThis() {
makeSaveData() {
self.user.jsonTest!.retrieveJSON() {
json in
print("json: \(json)")
}
}
}
func makeSaveData(completion: () -> Void) {
var solarResourceDic: [String: String] = [:]
var srDics: [[String: String]!] = []
for i in 0..<5 {
solarResourceDic = [:]
solarResourceDic["system_capacity"] = "\(i)"
solarResourceDic["azimuth"] = "\(i + 1)"
solarResourceDic["tilt"] = "\(i + 2)"
solarResourceDic["array_type"] = "\(i + 3)"
solarResourceDic["module_type"] = "\(i + 4)"
solarResourceDic["losses"] = "\(i + 5)"
srDics.append(solarResourceDic)
}
let dic = ["Solar Resource": srDics]
NSObject.storeJSON(dic) {
data in
if data != nil {
self.user.jsonTest = data
appDelegate.coreData.saveContext()
completion()
} else {
print("Error storing data")
}
}
}
You can follow this great tutorial which shows how to save json to core data. In general you need to learn to things: how to parse a json feed (it will result to NSDictionaries with the parsed items) and how to save this dictionaries to your persistent store. This tutorial covers both.
I know this is old but there is a library called Sync which does json saving to DB
Sync eases your everyday job of parsing a JSON response and getting it into Core Data. It uses a convention-over-configuration paradigm to facilitate your workflow.
Syncing JSON to Core Data is a repetitive tasks that often demands adding a lot of boilerplate code. Mapping attributes, mapping relationships, diffing for inserts, removals and updates are often tasks that don't change between apps. Taking this in account we took the challenge to abstract this into a library. Sync uses the knowledge of your Core Data model to infer all the mapping between your JSON and Core Data, once you use it, it feels so obvious that you'll wonder why you weren't doing this before.
Link:-Sync

Resources