I have this code:
import Alamofire
extension Alamofire.Request {
public func responseObject<T: ResponseJSONObjectSerializable>(completionHandler:(NSURLRequest?,NSHTTPURLResponse?, Result<T>) -> Void) -> Self {
let responseSerializer = GenericResponseSerializer<T> { request, response, data in
guard let responseData = data else {
let failureReason = "Object could not be serialized because input data was nil."
let error = Error.errorWithCode( .DataserializableFailed, failureReason: failureReason)
//Error: Type of expression is ambiguous without more context
return .Failure(data, error)
}}}
The .DataserializableFailed is defined in Alamofire:
public struct Error {
public enum Code: Int {
case InputStreamReadFailed = -6000
case OutputStreamWriteFailed = -6001
case ContentTypeValidationFailed = -6002
case StatusCodeValidationFailed = -6003
case DataSerializationFailed = -6004
case StringSerializationFailed = -6005
case JSONSerializationFailed = -6006
case PropertyListSerializationFailed = -6007
}
}
If I put Code before .DataserializableFailed it will say: Use of unresolved identifier 'Code'
What's wrong?
Obviously your capitalization/spelling is different. It should be .DataSerializationFailed, not .DataserializableFailed.
Or you could refer to Error.Code.DataSerializationFailed.
Related
can someone explain what is the error here ?
Member 'success' in 'SingleEvent<_>' produces result of type 'SingleEvent<Element>', but context expects 'SingleEvent<_>'
I return a Single either error or success with a generic type
if the result case is .success I want to pass JSON in the single event
this is the full code
import Foundation
import RxSwift
public class APIClient<T: APIProtocol> {
var disposeBag: DisposeBag = DisposeBag()
}
extension APIClient {
public func get<U>(apiService: T, key: String) -> Single<U> {
return Single.create { single in
let task = TaskRequestManager.run(apiService, completion: { result in
DispatchQueue.main.async {
switch result {
case .error(let error):
single(.error(error))
case .success(let json):
guard let JSON = json[key] as? [String: Any] else {
single(.error(ApiError.jsonParsingFailure))
return
}
single(.success(JSON))
}
}
})
task.resume()
return Disposables.create {
task.cancel()
}
}
}
}
Return type of get function is Single<U>, while calling of single(.success(JSON)) violates this by specifying concrete type Single<[String: Any]>. Either replace generic type U with Dictionary<String, Any> in get function, either make TaskRequestManager.run accept generic type.
change this
return Single.create { single in
into this
return Single<U>.create { single in
I've created a JSON parser and error class for it:
enum ParseError: Error {
case cannotParseField(fieldName: String)
case cannotFormatDate(fieldName: String)
case cannotFormatEnum(fieldName: String)
}
class SPUserParser {
static func parseSPUsers(data:Data) throws -> [SPUser] {
var spUsers:[SPUser] = []
let json = JSON(data: data)
if let array = json["value"].array {
for user in array {
guard let id = user["Id"].int else { throw ParseError.cannotParseField(fieldName: "Id") }
//PARSE OTHER STUFF...
}
}
return spUsers
}
}
Then when I'm trying to use my parser like this:
sendQuery(requestMethod: "GET", url: requestUrl, body: "", includeFormDigest:false, completed: { (data:Data) in
var result = ""
do {
let newUsers:[SPUser] = try SPUserParser.parseSPUsers(data: data)
self.spUsers.removeAll()
self.spUsers.append(contentsOf: newUsers)
}
catch ParseError.cannotParseField(let fieldName) {
result = "cannotParseField: \(fieldName)"
}
catch ParseError.cannotFormatDate(let fieldName) {
result = "cannotFormatDate: \(fieldName)"
}
catch ParseError.cannotFormatEnum(let fieldName) {
result = "cannotFormatEnum: \(fieldName)"
}
print(result)
The project won't compile.
The error is: Invalid conversion from throwing function of type '(Data) throws -> ()' to non-throwing function type '(Data) -> ()'
To fix this error I had to add catch {} after this three catches. Why I have to do this?
My code is based on Apple documentation: https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html
What you can do to simplify your code is using the let keyword in your catch like this :
do {
try SPUserParser.parseSPUsers(data: data)
// Your Stuff
} catch let parseError as ParseError {
result = handle(parseError)
} catch {
// Handles other kinds of errors
result = "other kind of error: \(error)"
}
And in the handle method that I used, you check which kind of ParseError it is, basically just a switch and then handle it accordingly
func handle(_ parseError: ParseError)
switch parseError {
case .cannotParseField(let fieldName):
return "cannotParseField: \(fieldName)"
case .cannotFormatDate(let fieldName):
return "cannotFormatDate: \(fieldName)"
case .cannotFormatEnum(let fieldName):
return "cannotFormatEnum: \(fieldName)"
}
I'm working with Alamofire and SwiftyJSON. I want to build general request and parse model for common situations. Firstly, I make a protocol called JSONConvertible.
protocol JSONConvertible {
init?(json: JSON)
}
Secondly, I extend Request class in Alamofire.
extension Request {
func getResult(format: [String: AnyClass]) {
self.responseJSON { (response) in
guard let statusCode = response.response?.statusCode else {
return
}
switch statusCode {
case 200:
var result = [String: AnyObject]()
let json = JSON(rawValue: response.result.value!)!
for (key, className) in format {
if className.self is JSONConvertible {
let value = className.self(json: json[key]) // get error in this line
}
}
case 201..<400:
break
case 400...Int.max:
break
default:
break
}
}
}
}
But I get an error from the compiler. Because AnyObject is only protocol and it doesn't have this initializer. I don't want get a dictionary or array only. I want to get instances with concrete class. Please help me. Many thanks!
That's because inside the if the type for className.self is still AnyObject. You need to cast it to JSONConvertible and then can use the initializer.
if let concreteClass = className.self as? JSONConvertible.Type
{
let value = concreteClass.init(json: json[key])
}
If you are familiar with kotlin, swift doesn't do casting automatically when testing for type in an if clause.
I found another way to solve this. Define a new protocol.
protocol JSONConvertibleObject: AnyObject, JSONConvertible {
}
And use this instead.
extension Request {
func getResult(format: [String: JSONConvertibleObject]) {
self.responseJSON { (response) in
guard let statusCode = response.response?.statusCode else {
return
}
switch statusCode {
case 200:
var result = [String: AnyObject]()
let json = JSON(rawValue: response.result.value!)!
for (key, className) in format {
let value = className.self.dynamicType.init(json: json[key])
}
case 201..<400:
break
case 400...Int.max:
break
default:
break
}
}
}
}
I am having trouble figuring out the syntax for accessing the raw value of an enum. The code below should clarify what I am trying to do.
enum Result<T, U> {
case Success(T)
case Failure(U)
}
struct MyError: ErrorType {
var status: Int = 0
var message: String = "An undefined error has occured."
init(status: Int, message: String) {
self.status = status
self.message = message
}
}
let goodResult: Result<String, MyError> = .Success("Some example data")
let badResult: Result<String, MyError> = .Failure(MyError(status: 401, message: "Unauthorized"))
var a: String = goodResult //<--- How do I get the string out of here?
var b: MyError = badResult //<--- How do I get the error out of here?
You can make it without switch like this:
if case .Success(let str) = goodResult {
a = str
}
It's not the prettiest way, but playing around in a playground I was able to extract that value using a switch and case:
switch goodResult {
case let .Success(val):
print(val)
case let .Failure(err):
print(err.message)
}
EDIT:
A prettier way would be to write a method for the enum that returns a tuple of optional T and U types:
enum Result<T, U> {
case Success(T)
case Failure(U)
func value() -> (T?, U?) {
switch self {
case let .Success(value):
return (value, nil)
case let .Failure(value):
return (nil, value)
}
}
}
Related question: Generic completion handler in Swift
In a Swift app I'm writing, I'm downloading JSON and I want to convert it into model objects. Right now, I'm doing that like this:
func convertJSONData<T: Entity>(jsonData: NSData?, jsonKey: JSONKey, _: T.Type) -> [T]? {
var entities = [T]()
if let data = jsonData {
// Left out error checking for brevity
var json = JSON(data: data, options: nil, error: nil)
var entitiesJSON = json[jsonKey.rawValue]
for (index: String, subJson: JSON) in entitiesJSON {
// Error: EXC_BAD_ACCESS(code=EXC_I386_GPFLT)
let entity = T(json: subJson)
entities.append(entity)
}
}
return entities
}
Each object conforming to Entity implements init(json: JSON). JSON is a type defined in the SwiftyJSON library. That's also the reason the enumeration looks a bit weird.
I call convertJSONData() in this method:
public func performJSONRequest<T where T: Entity>(jsonRequest: JSONRequest<T>) {
var urlString = ...
Alamofire.request(.GET, urlString, parameters: nil, encoding: .JSON).response { (request, response, data, error) -> Void in
var books = self.convertJSONData(data as? NSData, jsonKey: jsonRequest.jsonKey, T.self)
jsonRequest.completionHandler(books, error)
}
}
I get a runtime EXC_BAD_ACCESS(code=EXC_I386_GPFLT) error calling T(json: subJSON). There are no compiler warnings or errors. Although I left out error checking in the above code, there is error checking in the actual code and error is nil.
I'm not sure whether this is a compiler bug or my fault and any help figuring that out is much appreciated.
Several things are going on here, and I suspect the problem lies somewhere in the initializer of the class implementing the Entity protocol.
Assuming the code resembles the following:
protocol Entity {
init(json: JSON)
}
class EntityBase: Entity {
var name: String = ""
required init(json: JSON) { // required keyword is vital for correct type inference
if let nameFromJson = json["name"].string {
self.name = nameFromJson
}
}
func getName() -> String { return "Base with \(name)" }
}
class EntitySub: EntityBase {
convenience required init(json: JSON) {
self.init(json: json) // the offending line
}
override func getName() -> String { return "Sub with \(name)" }
}
The code compiles with self.init(json: json) in the sub-class, but actually trying to initialize the instance using the convenience method results in an EXC_BAD_ACCESS.
Either remove the initializer on the sub-class or simply implement required init and call super.
class EntitySub: EntityBase {
required init(json: JSON) {
super.init(json: json)
}
override func getName() -> String { return "Sub with \(name)" }
}
The method to convert the jsonData to an Entity (modified slightly to specifically return .None when jsonData is nil):
func convertJSONData<T:Entity>(jsonData: NSData?, jsonKey: JSONKey, type _:T.Type) -> [T]? {
if let jsonData = jsonData {
var entities = [T]()
let json = JSON(data: jsonData, options:nil, error:nil)
let entitiesJSON = json[jsonKey.rawValue]
for (index:String, subJson:JSON) in entitiesJSON {
let entity:T = T(json: subJson)
entities.append(entity)
}
return entities
}
return .None
}