I have a scenario where I need to call a method with an optional closure argument where the closure receives a generic argument. Here's my simplified code:
class FooModel
{
}
class FooSubClass1 : FooModel
{
}
class FooSubClass2 : FooModel
{
}
class Client
{
func httpGet<T:FooModel>(closure:((T) -> Void)? = nil)
{
// Doing some network request stuff here and call onHTTPRequestComplete() when done!
onHTTPRequestComplete(data: result, closure: closure)
}
func onHTTPRequestComplete<T:FooModel>(data:[String:Any], closure:((T) -> Void)? = nil)
{
if let c = closure
{
switch(T)
{
case is FooSubClass1:
var foo = FooSubClass1()
// Process data into new FooSubClass1 object here!
c(foo)
case is FooSubClass2:
var foo = FooSubClass2()
// Process data into new FooSubClass2 object here!
c(foo)
}
}
}
}
class App
{
func someFunc()
{
client.httpGet()
{
response in
print("\(response)")
}
}
}
What I'm trying to do here:
I have a data model with a super class and several subclasses. In Client I do http requests to retrieve data and want to populate model objects with the data retrieved. The calling method in App should be able to provide a closure that is called after the asynchronous network request is done and in which I get back the correct data model object.
There are several issues with my code:
switch(T): Error: expected member name or constructor call after type name
c(foo): Error: '(FooSubClass1) -> Void' is not convertible to '(T) -> Void'
Not sure my closure definition makes sense for what I'm trying to do.
Updated code:
protocol FooModel
{
init()
}
class FooModelBase : FooModel
{
}
class FooSubClass1 : FooModelBase
{
}
class FooSubClass2 : FooModelBase
{
}
class Client
{
func httpGet<T:FooModel>(closure:((T) -> Void)? = nil)
{
// Doing some network request stuff here and call onHTTPRequestComplete() when done!
onHTTPRequestComplete(data: result, closure: closure)
}
func onHTTPRequestComplete<T:FooModel>(data:[String:Any], closure:((T) -> Void)? = nil)
{
if let c = closure
{
switch T.self
{
case is FooSubClass1.Type:
var foo = FooSubClass1()
// Assign retrieved values to model!
closure(foo)
case is FooSubClass2.Type:
var foo = FooSubClass2()
// Assign retrieved values to model!
closure(foo)
default:
break
}
}
}
}
class App
{
func someFunc()
{
client.httpGet()
{
response in
print("\(response)") // Should output FooSubClass1 instance with assigned values!
}
}
}
Almost works but I get the following compile error:
'closure(foo): Error: '(#lvalue FooSubClass1) -> Void' is not convertible to '(T) -> Void'
for the switch(T) error of course you get this error. Because you're trying to switch a type but you should be switching a value. For your second error it makes perfect sense because then what would closure be expecting then because T is generic.
For more info on generics in closures have a look at this stackoverflow thread
generics as parameters to a closure in swift
All in all what you're trying to do there just isn't possible and at first glance I believe this isn't the correct direction to go in (Although I could be wrong without seeing the whole context). One thing you can do instead maybe is have your closure be ((Any) -> Void)? but then unfortunately the type would be of Any and you would have to do a switch inside of the closure.
The value of T in the closure will be passed to the closure itself. You can't switch on it. But what I think you want is to switch on the type. You could do something like this:
let type = String(reflecting: T.self)
switch (type) {
case "Module.FooSubClass1":
var foo = FooSubClass1()
}
Or you could say:
if FooSubClass1.self == T.self {
var foo = FooSubClass1();
} else if FooSubClass2.self == T.self {
var foo = FooSubClass2();
}
However, this code smells. You seem to want to use generics but then have a hardcoded set of classes inside your generic method, which half-defeats the entire purpose of generics. Why not just define FooModel as
protocol FooModel {
init()
}
Then instead of your switch statement, just create one and pass it to your closure.
func onHTTPRequestComplete<T: FooModel>(data: [String: Any], closure: ((T) -> Void)? = nil) {
guard let closure = closure else { return }
closure(T())
}
Or why not create T outside of onHTTPRequestComplete and pass it?
onHTTPRequestComplete(data: data: model: FooSubClass1()) { model in
// Do something awesome with `model` right here.
}
However, what I'd probably do is this:
func httpGet(callback: (([String: Any]) -> Void)? = nil) {
// Do some network stuff and get the data
callback?(data)
}
Then inside of callback, just create whatever type you want:
httpGet { data in
let foo = FooSubClass1(data: data)
// foo on you
}
Related
Given the below, this will throw a compile time error about the protocol not being able to adhere to itself and only struct/enum can adhere to the protocol. This seems to defeat the purpose of being able to use protocols in generics. I'm trying to understand why this doesn't work, but if I remove the generic and just put the protocol where 'Z' is everything is fine. It seems antithetical to what protocols and generics should be allowed for.
**Edit for question clarity: I need to take a type of Any that can be cast to a dictionary of [String:MyProtocol] and pass it into the method printEm. printEm must use the generic as it will be instantiating instances of the class Z.
protocol MyProtocol {
init()
var whoAmI:String { get }
}
func genericPassing(unknownThing:Any) {
let knownThing = unknownThing as? [String:MyProtocol]
if(knownThing != nil){
self.printEm(knownThing)
}
}
func printEm<Z:MyProtocol>(theThings:[String:Z]) {
let zCollection:[Z] = []
for thing in theThings {
print(thing.whoAmI)
zCollection.append(Z())
}
}
**Edited printEm to illustrate why generic is needed.
** Edit for more complex code. The two primary requirements are the use of a generic to call Z() and the ability to take an Any and somehow type check it and/or cast it so that it can be used in a genericized method.
private func mergeUpdates<Z:RemoteDataSyncable>(source:inout Z, updates:[WritableKeyPath<Z, Any>:Any]) throws {
for key in updates.keys {
let value = updates[key]!
let valueDict = value as? [String:[WritableKeyPath<RemoteDataSyncable, Any>:Any]]
if(valueDict != nil) {
var currentValueArray = source[keyPath: key] as? [RemoteDataSyncable]
if(currentValueArray != nil) {
self.mergeUpdates(source: ¤tValueArray!, updates: valueDict!)
}
else {
throw SyncError.TypeError
}
}
else {
source[keyPath: key] = value
}
}
}
private func mergeUpdates<Z:RemoteDataSyncable>(source:inout [Z], updates:[String:[WritableKeyPath<Z,Any>:Any]]) {
for key in updates.keys {
var currentObject = source.first { syncable -> Bool in
return syncable.identifier == key
}
if(currentObject != nil) {
try! self.mergeUpdates(source: ¤tObject!, updates: updates[key]!)
}
else {
var newSyncable = Z()
try! self.mergeUpdates(source: &newSyncable, updates: updates[key]!)
source.append(newSyncable)
}
}
}
This is a perfect example of why protocols do not conform to themselves. In your code Z is MyProtocol, so Z() is MyProtocol(). How would that work? What type is it? How big is it? You then can't put them into a [Z], since they might be different types.
You mean to pass arbitrary MyProtocols and call init on the type of each element:
func printEm(theThings:[String: MyProtocol]) {
var zCollection:[MyProtocol] = []
for thing in theThings.values {
print(thing.whoAmI)
zCollection.append(type(of: thing).init())
}
}
When I suggested using closures, this is the kind of thing I mean. This Updater can accept arbitrary ReferenceWritableKeyPaths, and when you pass it Any update value, it'll assign it to every key path that can accept it. That's kind of useless, but shows the technique. (Keep in mind that the updater is retaining the object, so that may be a problem that you need to address.)
class Updater {
private(set) var updaters: [(Any) -> ()] = []
func add<Root, Value>(keyPath: ReferenceWritableKeyPath<Root, Value>, on root: Root) {
updaters.append { value in
if let value = value as? Value {
root[keyPath: keyPath] = value
}
}
}
func updateAll(with value: Any) {
for updater in updaters {
updater(value)
}
}
}
class Client {
var updateMe: Int = 0
}
let client = Client()
let updater = Updater()
updater.add(keyPath: \.updateMe, on: client)
updater.updateAll(with: 3)
client.updateMe // 3
The key lesson in this code is that the generic types on the add are erased (hidden) by the (Any) -> () closure at compile-time. And the runtime as? check is done inside that closure, where the types are all known.
var closureA: (String)->()
class Test {
func instanceMethod(string: String) {
}
}
let a = Test()
closureA = Test.instanceMethod(a)
closureA("hello")
Xcode10 Playground show error:
error: cannot assign value of type '(String) -> ()' to type '(String) -> ()'
closureA = Test.instanceMethod(a)
I already read: https://oleb.net/blog/2014/07/swift-instance-methods-curried-functions/
I think you are missing the point of closure, you can't store the function in the closure, but you can store the piece of code that call to the function with the variable you are passing into the closure, and the function still need a class instance to call it, so it should be like this:
var closureA: ((String)->())?
class Test {
func instanceMethod(string: String) {
print(string)
}
}
let a = Test()
//Assume you have a variable `str: String` before hand that will execute the code inside closure
closureA = { str in
a.instanceMethod(string: str)
}
//Actual call to the closure to execute it
closureA?("hello")
I think is the bug of the playground. If we create a temp closure B, and assign it to closure A. Then it works.
var closureA: (String)->()
class Test {
func instanceMethod(string: String) {
print("Test")
}
}
let a = Test()
let closureB = Test.instanceMethod(a)
closureA = closureB
closureA("hello") // works
I've got an error "Closure cannot implicitly capture self parameter". Tell me please how it fix?
struct RepoJson {
...
static func get(url: String, completion: #escaping (RepoJson!) -> ()) {
...
}
}
struct UsersJson {
var repo: RepoJson!
init() throws {
RepoJson.get(url: rep["url"] as! String) { (results:RepoJson?) in
self.repo = results //error here
}
}
}
It's because you're using struct. Since structs are value, they are copied (with COW-CopyOnWrite) inside the closure for your usage. It's obvious now that copied properties are copied by "let" hence you can not change them. If you want to change local variables with callback you have to use class. And beware to capture self weakly ([weak self] in) to avoid retain-cycles.
I'm trying to call a static method in a base class from a generic subclass. See simplified playground code below.
Calling the non-static 'dump' function works.
The similar static call fails. Various attempts to typecast the array also fail.
In the full production code, "ClassA" is in a 3rd-party module and can't be changed. It can be further subclassed and extended.
Does Swift offer some magical typecast to have ClassT call ClassA.dump() directly?
class ClassT<T> {
var dict=[String:T]()
func add(key:String, obj:T) {
dict[key]=obj
let arr=Array(dict.values)
dump(arr) // works -> but not as expected, see comment below !!!
ClassA.dump(arr) // error: cannot convert value of type 'Array<T>' to expected argument type '[ClassA]'
ClassA.dump(arr as! [ClassA]) // error: cannot convert value of type 'Array<T>' to type '[ClassA]' in coercion
ClassA.dump(arr as! [AnyObject]) // error: 'AnyObject' is not a subtype of 'T'
ClassA.dump(arr as! [Any]) // error: 'Any' is not a subtype of 'T'
}
}
class ClassA {
func dump(arr:[ClassA]) {
ClassA.dump(arr)
}
static func dump(arr:[ClassA]) {
print(arr)
}
}
class ClassB:ClassA {
static let o=ClassT<ClassA>()
func test() {
ClassB.o.add("Elem1", obj:self)
}
}
You have to add a constraint to specify that T derives from ClassA.
class ClassT<T: ClassA> {
var dict = [String : T]()
func add(key: String, obj: T) {
dict[key] = obj
let arr = Array(dict.values) //probably unecessary
dump(arr) // works
ClassA.dump(arr)
}
//...
Without it, the compiler has no way to enforce that all conforming types T will be castable to ClassA.
I want to be able to pass a class (not an initialized object) of a certain protocol type to a method, then call the class functions of that class in the method. Code below.
I am using Swift and have an protocol defined like this
//Protocol for any object to be used with an FAUAPIConnection
protocol FAUAPIModel{
//Used to parse the object from a given dictionary to an object
class func parseFromJSON(JSON:AnyObject) -> Self
//Required default init
init()
}
What I would like to do is have a method like this
func getSomeParsingDone<T:FAUAPIModel>(model:T.Type? = nil, getPath:path, callingObj:CallingClass) -> Void
{
//GetIt is inconsequential, just logic to get an object from a certain path
var returnObj:AnyObject = GetIt.get(path)
if(model != nil){
returnObj = model!.parseFromJSON() <<<<<< Type 'T' does not conform to protocol 'AnyObject'
}
callingObj.done(returnObj)
}
Object that implements the protocol
import Foundation
class MyObj: FAUAPIModel{
var neededVal:String
var nonneededVal:String
required convenience init(){
self.init(neededVal:"VALUE")
}
init(neededVal:String, nonneededVal:String = ""){
self.neededVal = neededVal
self.nonneededVal = nonneededVal
}
class func parseFromJSON(JSON:AnyObject) -> WGMPart
{
return WGMPart() <<<<<<<< Method 'parseFromJSON' in non-final class 'WGMPart' must return 'Self' to conform to protocol 'FAUAPIModel'
}
}
However, I keep getting two errors. I have indicated these above with '<<<<<<<<<<<<'
compile error.
Lots of little things to consider here, but let's get to the heart of your question. The signature you want looks like this:
func getSomeParsingDone<T:FAUAPIModel>(model:T.Type, path:String) -> T?
I'm making the return optional beause there are a lot of things that could fail here, and you really shouldn't turn all of those into crashes.
I'd recommend your protocol look like this:
protocol FAUAPIModel {
class func parseFromJSON(JSON:AnyObject) -> Self
}
That way, you're promising that your return your own class, not just anything that is parseable. That does tend to mean that you need to make your classes final. If you don't want them to be final, you'll need to promise some init method in order to construct it. See Protocol func returning Self for more details on how to deal with that if you need it.
So putting it together, it might look something like this in practice:
protocol FAUAPIModel {
class func parseFromJSON(JSON:AnyObject) -> Self
}
func createObjectOfClass<T: FAUAPIModel>(model: T.Type, path: String) -> T? {
if let json: AnyObject = GetJSON(path) {
return model.parseFromJSON(json)
}
return nil
}
// Bogus JSON reader
func GetJSON(path: String) -> AnyObject? {
let json: AnyObject? = NSJSONSerialization.JSONObjectWithData(path.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)!, options: NSJSONReadingOptions(0), error: nil)
return json
}
// Bogus model class that returns trivial version of itself
final class Something: FAUAPIModel {
class func parseFromJSON(JSON:AnyObject) -> Something {
return Something()
}
}
// Using it
let something = createObjectOfClass(Something.self, "/path/to/file")
I just want to note that the answer to your exact question would be to declare your function like this:
func getSomeParsingDone(model:FAUAPIModel.Type? = nil, getPath:path) -> FAUAPIModel