So übergeben Sie einen Klassentyp als Funktionsparameter

146

Ich habe eine generische Funktion, die einen Webdienst aufruft und die JSON-Antwort zurück zu einem Objekt serialisiert.

class func invokeService<T>(service: String, withParams params: Dictionary<String, String>, returningClass: AnyClass, completionHandler handler: ((T) -> ())) {

            /* Construct the URL, call the service and parse the response */
}

Was ich versuche zu erreichen, ist das Äquivalent dieses Java-Codes

public <T> T invokeService(final String serviceURLSuffix, final Map<String, String> params,
                               final Class<T> classTypeToReturn) {
}
  • Ist meine Methodensignatur für das, was ich erreichen möchte, korrekt?
  • Ist die Angabe AnyClassals Parametertyp das Richtige?
  • Beim Aufrufen der Methode übergebe ich MyObject.selfden Wert returnClass, erhalte jedoch den Kompilierungsfehler "Der Typ '()' des Ausdrucks kann nicht in den Typ 'String' konvertiert werden."
CastDAO.invokeService("test", withParams: ["test" : "test"], returningClass: CityInfo.self) { cityInfo in /*...*/

}

Bearbeiten:

Ich habe versucht object_getClass, wie von holex erwähnt, aber jetzt bekomme ich:

Fehler: "Typ 'CityInfo.Type' entspricht nicht dem Protokoll 'AnyObject'"

Was muss getan werden, um dem Protokoll zu entsprechen?

class CityInfo : NSObject {

    var cityName: String?
    var regionCode: String?
    var regionName: String?
}
Jean-Francois Gagnon
quelle
Ich glaube nicht, dass Swifts-Generika wie Javas funktionieren. daher kann der Inferrer nicht so intelligent sein. Ich würde die Klasse <T> weglassen und den generischen Typ explizit angebenCastDAO.invokeService("test", withParams: ["test" : "test"]) { (ci:CityInfo) in }
Christian Dietrich

Antworten:

144

Sie nähern sich dem falsch: In Swift haben Klassen im Gegensatz zu Objective-C bestimmte Typen und sogar eine Vererbungshierarchie ( Bdh wenn Klasse von erbt A, dann B.Typeerbt auch von A.Type):

class A {}
class B: A {}
class C {}

// B inherits from A
let object: A = B()

// B.Type also inherits from A.Type
let type: A.Type = B.self

// Error: 'C' is not a subtype of 'A'
let type2: A.Type = C.self

Aus diesem Grund sollten Sie nicht verwenden AnyClass, es sei denn , Sie wirklich zulassen wollen jede Klasse. In diesem Fall wäre der richtige Typ T.Type, da er die Verknüpfung zwischen dem returningClassParameter und dem Parameter des Abschlusses ausdrückt .

Die Verwendung anstelle von AnyClassermöglicht es dem Compiler, die Typen im Methodenaufruf korrekt abzuleiten:

class func invokeService<T>(service: String, withParams params: Dictionary<String, String>, returningClass: T.Type, completionHandler handler: ((T) -> ())) {
    // The compiler correctly infers that T is the class of the instances of returningClass
    handler(returningClass())
}

Jetzt gibt es das Problem, eine Instanz Tzu handlererstellen, an die übergeben werden soll : Wenn Sie versuchen, den Code jetzt auszuführen, beschwert sich der Compiler, dass dies Tnicht möglich ist (). Und das zu Recht: Muss Texplizit eingeschränkt werden, damit ein bestimmter Initialisierer implementiert werden kann.

Dies kann mit einem Protokoll wie dem folgenden erfolgen:

protocol Initable {
    init()
}

class CityInfo : NSObject, Initable {
    var cityName: String?
    var regionCode: String?
    var regionName: String?

    // Nothing to change here, CityInfo already implements init()
}

Dann müssen Sie nur noch die generischen Einschränkungen von ändern invokeService von <T>bis<T: Initable> .

Trinkgeld

Wenn seltsame Fehler wie "Der Typ '()' des Ausdrucks kann nicht in 'String' konvertiert werden" angezeigt werden, ist es häufig hilfreich, jedes Argument des Methodenaufrufs in eine eigene Variable zu verschieben. Es hilft dabei, den Code einzugrenzen, der den Fehler verursacht, und Probleme mit der Typinferenz aufzudecken:

let service = "test"
let params = ["test" : "test"]
let returningClass = CityInfo.self

CastDAO.invokeService(service, withParams: params, returningClass: returningClass) { cityInfo in /*...*/

}

Jetzt gibt es zwei Möglichkeiten: Der Fehler wird in eine der Variablen verschoben (was bedeutet, dass der falsche Teil vorhanden ist) oder Sie erhalten eine kryptische Nachricht wie "Der Ausdruckstyp kann nicht konvertiert werden." () in Typ($T6) -> ($T6) -> $T5 ".

Die Ursache für den letzteren Fehler ist, dass der Compiler nicht in der Lage ist, auf die von Ihnen geschriebenen Typen zu schließen. In diesem Fall besteht das Problem darin, dass Tnur der Parameter des Abschlusses verwendet wird und der von Ihnen übergebene Abschluss keinen bestimmten Typ angibt, sodass der Compiler nicht weiß, auf welchen Typ er schließen soll. Durch Ändern des Typs von returningClassinclude Tgeben Sie dem Compiler eine Möglichkeit, den generischen Parameter zu bestimmen.

EliaCereda
quelle
Vielen Dank für den T.Type-Hinweis - genau das, was ich brauchte, um einen Klassentyp als Argument zu übergeben.
Echelon
Der "Tipp" am Ende hat mich gerettet
Alex
Anstatt eine Template-Funktion <T: Initiable> zu verwenden, ist es auch möglich, die Rückgabeklasse als Initiable.Type
Devous
Im ursprünglichen Code hätte das zu unterschiedlichen Ergebnissen geführt. Der generische Parameter Twird verwendet, um die Beziehung zwischen dem returningClassund dem übergebenen Objekt auszudrücken completionHandler. Wenn verwendet Initiable.Typewird, geht diese Beziehung verloren.
EliaCereda
UND? Generika erlauben nicht zu schreibenfunc somefunc<U>()
Gargo
34

Sie können die Klasse von AnyObjectüber diesen Weg erhalten:

Swift 3.x.

let myClass: AnyClass = type(of: self)

Swift 2.x.

let myClass: AnyClass = object_getClass(self)

und Sie können es später als Parameter übergeben, wenn Sie möchten.

holex
quelle
1
Sie können auch tunself.dynamicType
Newacct
1
Immer wenn ich versuche, myClass zu verwenden, verursacht dies den Fehler "Verwendung des nicht deklarierten Typs" myClass ". Swift 3, neueste Builds von Xcode und iOS
Confused
@Confused, ich sehe kein solches Problem damit. Möglicherweise müssen Sie mir weitere Informationen zum Kontext geben.
Holex
Ich mache einen Knopf. Im Code dieser Schaltfläche (es ist SpriteKit, also innerhalb von touchBegan) soll die Schaltfläche eine Klassenfunktion aufrufen, daher muss auf diese Klasse verwiesen werden. Daher habe ich in der Button-Klasse eine Variable erstellt, die einen Verweis auf die Klasse enthält. Aber ich kann es nicht dazu bringen, eine Klasse oder den Typ zu halten, der diese Klasse ist, unabhängig von der richtigen Formulierung / Terminologie. Ich kann nur Verweise auf Instanzen speichern. Vorzugsweise möchte ich einen Verweis auf den Klassentyp in die Schaltfläche übergeben. Aber ich habe gerade mit der Erstellung einer internen Referenz begonnen und konnte das nicht einmal zum Laufen bringen.
Verwirrt
Es war eine Fortsetzung der Methodik und des Denkens, die ich hier verwendete: stackoverflow.com/questions/41092440/… . Seitdem habe ich eine gewisse Logik für die Arbeit mit Protokollen, aber ich bin schnell auf die Tatsache gestoßen, dass ich auch die zugehörigen Typen und Generika herausfinden muss, um das zu bekommen, was ich mit einfacheren Mechanismen zu tun glaubte. Oder kopieren Sie einfach viel Code und fügen Sie ihn ein. Welches ist, was ich normalerweise mache;)
Verwirrt
7

Ich habe einen ähnlichen Anwendungsfall in swift5:

class PlistUtils {

    static let shared = PlistUtils()

    // write data
    func saveItem<T: Encodable>(url: URL, value: T) -> Bool{
        let encoder = PropertyListEncoder()
        do {
            let data = try encoder.encode(value)
            try data.write(to: url)
            return true
        }catch {
            print("encode error: \(error)")
            return false
        }
    }

    // read data

    func loadItem<T: Decodable>(url: URL, type: T.Type) -> Any?{
        if let data = try? Data(contentsOf: url) {
            let decoder = PropertyListDecoder()
            do {
                let result = try decoder.decode(type, from: data)
                return result
            }catch{
                print("items decode failed ")
                return nil
            }
        }
        return nil
    }

}
Johney
quelle
Ich versuche etwas Ähnliches zu tun, aber auf der Anrufseite wird eine Fehlermeldung angezeigt : Static method … requires that 'MyDecodable.Type' conform to 'Decodable'. Würde es Ihnen etwas ausmachen, Ihre Antwort zu aktualisieren, um einen Beispielanruf zu erhalten loadItem?
Barry Jones
Es stellt sich heraus, dass dies der Punkt ist, an dem ich den Unterschied zwischen .Typeund .self(Typ und Metatyp) lernen muss.
Barry Jones
0

Verwendung obj-getclass:

CastDAO.invokeService("test", withParams: ["test" : "test"], returningClass: obj-getclass(self)) { cityInfo in /*...*/

}

Angenommen, Selbst ist ein Stadtinfo-Objekt.

Rückgängig machen
quelle
0

Swift 5

Nicht genau die gleiche Situation, aber ich hatte ein ähnliches Problem. Was mir schließlich geholfen hat, war Folgendes:

func myFunction(_ myType: AnyClass)
{
    switch type
    {
        case is MyCustomClass.Type:
            //...
            break

        case is MyCustomClassTwo.Type:
            //...
            break

        default: break
    }
}

Dann können Sie es in einer Instanz dieser Klasse wie folgt aufrufen:

myFunction(type(of: self))

Hoffe das hilft jemandem in meiner gleichen Situation.

Merricat
quelle