I'm trying to create an class to retrieve JSON from a web API in Swift. Instead of going with delegates, I thought of using closures but I'm struggling with a few things.
let login = {(email: String, password: String) -> (Int, String) in
let response = { (response: NSHTTPURLResponse!, data: HTTPHandler.Data!, error: NSError!) -> Void in
var value: String? = response.allHeaderFields[HTTPHeaderFields.token] as AnyObject? as? String
var headerData = value?.dataUsingEncoding(NSASCIIStringEncoding)
var values: NSArray = NSJSONSerialization.JSONObjectWithData(headerData, options: .AllowFragments, error: nil) as NSArray
println(values)
return (values[0], values[1]) // Tuple types '(AnyObject!, AnyObject!)' and '()' have a different number of elements (2 vs. 0)
}
let httpHandler = HTTPHandler.POST(SERVER_URL + POSTendpoints.login, data: ["email": email, "password": password], response: response)
return nil // Type '(Int, String)' does not conform to protocol 'NilLiteralConvertible'
}
Here is the closure I wrote. It accepts two parameters (email, password) and should return a tuple (User ID, API Token). I have a separate class called HTTPHandler which calls the server and I get and can parse the response successfully. Here's the example JSON output.
(
2,
JDJ5JDEwJFZ4ZkR4eXlkYUxiYS93TXUwbjBtbnUzaVhidFZBUVVtMTRJM0J3WFFBemszSVVjZ3RWd05h
)
But I can't return the value in a tuple. I get following two errors. I've commented them in the above code snippet where they occur.
I'm still struggling with closure syntax in Swift so I'm not entirely sure if I have done this right. I've defined the closure as a constant. That way can I call this closure from another class? If not, how can I make it possible to do so?
Can someone please help me to resolve these issues?
Thank you.
First of all, in order to return nil you need to make your closure return an optional tuple. Use (Int, String)? or (Int, String)! instead of (Int, String)
You're getting the error "Tuple types '(AnyObject!, AnyObject!)' and '()' have a different number of elements" because you're trying to return a (Int, String) tuple instead of Void which is what the closure returns.
I can't test it myself right now, but it would look something like this:
let login = {(email: String, password: String) -> (Int, String)? in
var returnTuple: (Int, String)? = nil
let response = { (response: NSHTTPURLResponse!, data: HTTPHandler.Data!, error: NSError!) -> Void in
var value: String? = response.allHeaderFields[HTTPHeaderFields.token] as AnyObject? as? String
var headerData = value?.dataUsingEncoding(NSASCIIStringEncoding)
var values: NSArray = NSJSONSerialization.JSONObjectWithData(headerData, options: .AllowFragments, error: nil) as NSArray
println(values)
returnTuple = (values[0], values[1])
}
let httpHandler = HTTPHandler.POST(SERVER_URL + POSTendpoints.login, data: ["email": email, "password": password], response: response)
return returnTuple
}
Regarding the first error, it seems that you should cast values elements to proper types:
return (values[0] as Int, values[1] as String)
Regarding the second error, non-optional type just can't be nil in Swift, if you want to indicate absence of value, you should change return type of your closure to (Int, String)?.
Related
I've built a generic repository which returns the resources I ask for. This is working just fine until I've tried to perform a grouped request by wrapping several fetch calls into a DispatchGroup. The repository is a generic class which indicates the return type.
I'm storing the response of each individual fetch to a dictionary, once all of them are ready I'm transforming the dictionary into the expected return type.
The problem comes when the generic is of type [Something] (i.e. T == [Something])
public typealias RepoCompletion<T: Codable> = (Result<T?, Error>) -> Void
public func fetch<T: Entity>(_ resources: [FetchResource<T>], completion: #escaping RepoCompletion<T>) {
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
let group = DispatchGroup()
let valueSyncQueue = DispatchQueue(label: "Repo.ValueSync")
var values = [String: T]()
for resource in resources {
group.enter()
self.localDataSource.fetch(resource) { result in
switch result {
case .success(let value):
if let value = value {
valueSyncQueue.sync {
values[resource.description] = value
}
}
case .failure(_): break
}
group.leave()
}
}
group.wait(timeout: .now() + 10)
let flattenedValues = resources.compactMap({ values[$0.description] }).flatMap({ $0 })
completion(.success(flattenedValues))
}
}
The above code doesn't compile. The error says Member 'success' in 'Result<_?, Error>' produces result of type 'Result<Success, Failure>', but context expects 'Result<_?, Error>'. I think the problem comes because the generic type is an [] and the compiler needs help.
Check out the types when debugging:
(lldb) po type(of: resources.compactMap({ values[$0.description] }))
Swift.Array<Swift.Array<Schedule>>
(lldb) po type(of: resources.compactMap({ values[$0.description] }).flatMap({ $0 }))
Swift.Array<Schedule>
(lldb) po type(of: flattenedValues)
Swift.Array<Swift.Array<Schedule>>
I don't understand why the expression let flattenedValues = resources.compactMap({ values[$0.description] }).flatMap({ $0 }) has different types for the left side and the right side once evaluated :S
Let's say T == [Schedules]. I expect values to be [String: [Schedules]]. Then when .compactMap I expect a result of [[Schedules]] and finally the .flatMap to return [Schedules] (i.e. T). But I'm missing something or I don't get it.
If I try to help the compiler and force cast the flattenedValues to be as! T, it crashes returning
Could not cast value of type 'Swift.Array' (0x1058073b0) to 'Schedule' (0x1044ac928).
2019-10-17 09:48:14.585362+0200 Chicisimo[86448:2426001] Could not cast value of type 'Swift.Array' (0x1058073b0) to 'Schedule' (0x1044ac928).
Which I don't really understand because T is of type [Schedule] and not of type Schedule.
Any help would be appreciated! Thanks!
You got this error because you are trying to create a Result with an array of T, when your completion expect a single T value.
The values: [String: T] build up a list of T in your for loop:
for resource in resources {
// here value is a T
values[resource.description] = value
}
The flattened values variable is the result of a compact map of your values dictionary
let flattenedValues = resources.compactMap({ values[$0.description] }).flatMap({ $0 })
This means at this point, the type of flattenedValues is [T].
The completion ((Result<T?, Error>) -> Void) expect a Result with a single value of type T, but you're are trying to resolve it with an array ([T]).
This is why you are getting this error.
Solution:
From what I understand, you want to fetch your entities from multiple resources (FetchResource), which mean you probably want to always return an array of entities ([T]).
If so, you can just change your RepoCompletion to an array: [T]. This would look like this:
// change RepoCompletion<T> to RepoCompletion<[T]>
public func fetch<T: Entity>(_ resources: [FetchResource<T>], completion: #escaping RepoCompletion<[T]>) {
// ...
}
I have generic method to create object that extend protocol FromResponse.
extension FromResponse {
static func object<T>(_ response: [String: Any]?) -> T? where T: FromResponse, T: NSObject {
guard let response = response else { return nil }
let obj: T = T()
return obj
}
}
So whenever I want to call it from anywhere in a code there is no issue. Let's say:
let myObject: MyObject? = MyObject.object(response)
Work's perfectly. But sometimes I'm getting array of objects from my response so I would like to have generic parser as well:
static func objects<T>(_ response: [[String: Any]]?) -> [T]? where T: FromResponse, T: NSObject {
guard let response = response else { return nil }
var returnArray: [T] = [T]()
for singleResponse in response {
if let object: T = T.object(singleResponse) {
returnArray.append(object)
}
}
return returnArray
}
So I expect from this method to return array of MyObject, but In fact I'm getting compiler error when I'm calling this:
let myObjects: [MyObject]? = MyObject.objects(response)
It says:
Generic parameter 'T' could not be inferred
Well, I know what does it mean but I did specify type, so this error should not happen. Also when I do this:
var typ: [MyObject] = [MyObject]()
for singleResponse in (response as? [[String: Any]])! {
let pack: MyObject? = MyObject.object(singleResponse)
typ.append(pack!)
}
It works!
Why? How to have parser that returns array of generics objects?
I don't know for sure why Swift says “Generic parameter 'T' could not be inferred”, but my guess is it has to do with array covariance.
What's covariance? Consider this:
class Base { }
class Sub: Base { }
func f(_ array: [Base]) { }
Can you pass an [Sub] to f? In Swift, you can. Because Sub is a subtype of Base, [Sub] is a subtype of [Base]. (This is called “covariance”.) So you can pass a [Sub] anywhere that a [Base] is allowed:
f([Sub]())
// No errors.
And you can return a [Sub] where a [Base] is expected:
func g() -> [Base] { return [Sub]() }
// No errors.
And you can assign a [Sub] to a [Base] variable:
let bases: [Base] = [Sub]()
// No errors.
So back to your code:
static func objects<T>(_ response: [[String: Any]]?) -> [T]? ...
let myObjects: [MyObject]? = MyObject.objects(response)
Certainly MyObject.objects(_:) must return a type that can be treated as [MyObject]?. But any subtype of [MyObject]? is also acceptable. The type is not tightly constrained. I guess this is why Swift doesn't like it.
The fix is to tell Swift explicitly what type you want, using a pattern you'll see in many places in the Swift standard library:
static func objects<T>(ofType type: T.Type, from response: [[String: Any]]?) -> [T]? ...
// Note that you might not actually have to use the `type` parameter
// in the method definition.
let myObjects = MyObject.objects(ofType: MyObject.self, from: response)
It's not clear why this method is on the MyObject class at all. Perhaps you should make it a method on [[String: Any]]:
extension Collection where Element == [String: Any] {
func objects<T>(ofType type: T.Type) -> [T]? ...
}
let myObjects = response.objects(ofType: MyObject.self)
I'm trying to convert my string into SHA256 hash, but I get the next error:
Cannot invoke initializer for type 'UnsafeMutablePointer<UInt8>' with an argument list of type '(UnsafeMutableRawPointer)'
That's my function:
func SHA256(data: String) -> Data {
var hash = NSMutableData(length: Int(CC_SHA256_DIGEST_LENGTH))!
if let newData: Data = data.data(using: .utf8) {
let bytes = newData.withUnsafeBytes {(bytes: UnsafePointer<CChar>) -> Void in
CC_SHA256(bytes, CC_LONG(newData.length), UnsafeMutablePointer<UInt8>(hash.mutableBytes))
}
}
return hash as Data
}
so, for this part
UnsafeMutablePointer<UInt8>(hash.mutableBytes)
I get this error:
Cannot invoke initializer for type 'UnsafeMutablePointer<UInt8>' with an argument list of type '(UnsafeMutableRawPointer)'
How can I fix that and what I do wrong?
You'd better use Data also for the result hash.
In Swift 3, withUnsafePointer(_:) and withUnsafeMutablePointer(:_) are generic types and Swift can infer the Pointee types correctly when used with "well-formed" closures, which means you have no need to convert Pointee types manually.
func withUnsafeBytes((UnsafePointer) -> ResultType)
func withUnsafeMutableBytes((UnsafeMutablePointer) -> ResultType)
func SHA256(data: String) -> Data {
var hash = Data(count: Int(CC_SHA256_DIGEST_LENGTH))
if let newData: Data = data.data(using: .utf8) {
_ = hash.withUnsafeMutableBytes {mutableBytes in
newData.withUnsafeBytes {bytes in
CC_SHA256(bytes, CC_LONG(newData.count), mutableBytes)
}
}
}
return hash
}
In Swift 3, the initializers of UnsafePointer and UnsafeMutablePointer, which was used to convert Pointee types till Swift 2, are removed. But in many cases, you can work without type conversions of pointers.
I have a big dictionary in my App looks like that:
var data = [["type":"Sport", "model":"R6"],["type":"Enduro", "model":"Tenerre"],["type":"Chopper", "model":"Intruder"]]
which I would like to sort in UISearchController using this function func updateSearchResultsForSearchController(searchController: UISearchController)
inside this function up I would like to filter my array by "type" not using for but filter(includeElement: (Self.Generator.Element) throws -> Bool) but I recive an error Cannot convert value of type String -> Bool to expected argument type ([String:String]) -> Bool
I am doing something like this:
self.data.filter({ (type: String) -> Bool in
return true
})
how should I do it correct?
data has this type [[String:String]]. In other words is an Array of [String:String].
So the parameter of the closure you pass to filter must have this type: [String: String].
You can use the following code and replacing my simple return true with your logic.
let filteredData = self.data.filter { (dict:[String:String]) -> Bool in
return true
}
Please keep in mind that dict is the i-th element of the Array data.
I'm implementing Swift data structures into Objective-C and I'm having trouble 'returning' 2 values.
My swift implementation originally used a tuple to return resultData AND an error:
func getData(email: String) -> (SomeResultdata?, NSError?)
but this function isn't exposed to Objective-C due to tuples not being a feature in Objective-C.
I've now implemented a method dedicated to returning SomeResultData exclusively, but also assigning an NSError to the passed pointer reference.
See below:
#objc func getData(email: String, var withError err: UnsafeMutablePointer<NSError?>) -> SomeResultdata? {
let (credentials, error) = self.getData(email)
// This self.getData(email) is the Swift implementation (returns tuple)
err = &error
return credentials
}
The two errors I'm getting are
Method cannot be marked #objc because the type of the parameter 2 cannot be represented in Objective-C
and
Cannot convert the expression's type '()' to type 'inout $T2'
For example NSJSONSerialization.dataWithJSONObject() has similar pattern:
class func dataWithJSONObject(obj: AnyObject, options opt: NSJSONWritingOptions, error: NSErrorPointer) -> NSData?
It's NSErrorPointer.
typealias NSErrorPointer = AutoreleasingUnsafeMutablePointer<NSError?>
So you can:
#objc func getData(email: String, withError err: NSErrorPointer) -> SomeResultdata? {
let (credentials, error) = self.getData(email)
// This self.getData(email) is the Swift implementation (returns tuple)
err.memory = error
return credentials
}
As for AutoreleasingUnsafeMutablePointer, see the documentation.