Jak przekazać typ klasy jako parametr funkcji

146

Mam ogólną funkcję, która wywołuje usługę internetową i serializuje odpowiedź JSON z powrotem do obiektu.

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 */
}

To, co próbuję osiągnąć, jest odpowiednikiem tego kodu Java

public <T> T invokeService(final String serviceURLSuffix, final Map<String, String> params,
                               final Class<T> classTypeToReturn) {
}
  • Czy podpis mojej metody dla tego, co próbuję osiągnąć, jest prawidłowy?
  • Dokładniej, czy określanie AnyClassjako typu parametru jest właściwe?
  • Wywołując metodę, przekazuję MyObject.selfjako wartość returningClass, ale pojawia się błąd kompilacji „Nie można przekonwertować typu wyrażenia '()' na typ 'String'”
CastDAO.invokeService("test", withParams: ["test" : "test"], returningClass: CityInfo.self) { cityInfo in /*...*/

}

Edytować:

Próbowałem użyć object_getClass, jak wspomniał Holex, ale teraz otrzymuję:

błąd: „Typ 'CityInfo.Type' nie jest zgodny z protokołem 'AnyObject'”

Co należy zrobić, aby dostosować się do protokołu?

class CityInfo : NSObject {

    var cityName: String?
    var regionCode: String?
    var regionName: String?
}
Jean-Francois Gagnon
źródło
Nie sądzę, żeby generyczne jerzyki działały jak javas. zatem wnioskujący nie może być tak inteligentny. id pomijać Class <T> Thing i jawnie określa typ ogólnyCastDAO.invokeService("test", withParams: ["test" : "test"]) { (ci:CityInfo) in }
Christian Dietrich

Odpowiedzi:

144

Podchodzisz do tego w niewłaściwy sposób: w Swift, w przeciwieństwie do Objective-C, klasy mają określone typy, a nawet mają hierarchię dziedziczenia (to znaczy, jeśli klasa Bdziedziczy po A, to B.Typerównież dziedziczy po 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

Dlatego nie powinieneś używać AnyClass, chyba że naprawdę chcesz zezwolić na jakiekolwiek zajęcia. W tym przypadku właściwy byłby typ T.Type, ponieważ wyraża powiązanie między returningClassparametrem a parametrem zamknięcia.

W rzeczywistości użycie go zamiast AnyClasspozwala kompilatorowi poprawnie wywnioskować typy w wywołaniu metody:

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())
}

Teraz pojawia się problem konstruowania instancji Tdo przekazania handler: jeśli spróbujesz teraz uruchomić kod, kompilator narzeknie, że Tnie można go skonstruować (). I słusznie: Tmusi być wyraźnie ograniczone, aby wymagać implementacji określonego inicjatora.

Można to zrobić za pomocą protokołu takiego jak następujący:

protocol Initable {
    init()
}

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

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

Wtedy wystarczy zmienić ogólne ograniczenia z invokeServiceod <T>do <T: Initable>.

Wskazówka

Jeśli otrzymujesz dziwne błędy, takie jak „Nie można przekonwertować typu wyrażenia '()' na typ 'String'”, często przydatne jest przeniesienie każdego argumentu wywołania metody do jego własnej zmiennej. Pomaga zawęzić kod, który powoduje błąd i odkryć problemy z wnioskiem o typie:

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

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

}

Są teraz dwie możliwości: błąd przenosi się do jednej ze zmiennych (co oznacza, że ​​jest tam zła część) lub pojawia się tajemniczy komunikat, taki jak „Nie można przekonwertować typu wyrażenia na ()typ ($T6) -> ($T6) -> $T5”.

Przyczyną tego drugiego błędu jest to, że kompilator nie jest w stanie wywnioskować typów tego, co napisałeś. W tym przypadku problem polega na tym, że Tjest on używany tylko w parametrze zamknięcia, a przekazane zamknięcie nie wskazuje żadnego konkretnego typu, więc kompilator nie wie, jaki typ wywnioskować. Zmieniając typ returningClassdo uwzględnienia T, dajesz kompilatorowi sposób na określenie parametru generycznego.

EliaCereda
źródło
Dzięki za wskazówkę T.Type - właśnie to, czego potrzebowałem do przekazania typu klasy jako argumentu.
Echelon
„Wskazówka” na końcu uratowała mnie
Alex
Zamiast używać funkcji szablonowej <T: Initiable>, można również przekazać returningClass jako Initiable.Type
Devous
W oryginalnym kodzie dałoby to inne wyniki. Parametr ogólny Tsłuży do wyrażania relacji między returningClassobiektem a przekazanym do completionHandler. Jeśli Initiable.Typejest używany, ten związek jest tracony.
EliaCereda
I? Generics nie pozwalają na pisaniefunc somefunc<U>()
Gargo
34

możesz uzyskać klasę w AnyObjectten sposób:

Swift 3.x

let myClass: AnyClass = type(of: self)

Swift 2.x

let myClass: AnyClass = object_getClass(self)

i jeśli chcesz, możesz później przekazać go jako parametr.

holex
źródło
1
możesz też to zrobićself.dynamicType
newacct
1
Za każdym razem, gdy próbuję użyć myClass, powoduje to błąd „użycie niezadeklarowanego typu„ myClass ”. Swift 3, najnowsze wersje Xcode i iOS
Confused
@Confused, nie widzę w tym problemu, być może będziesz musiał podać mi więcej informacji o kontekście.
holex
Robię guzik. W kodzie tego przycisku (to SpriteKit, więc inside touchesBegan) chcę, aby przycisk wywoływał funkcję Class, więc potrzebuję odwołania do tej klasy. Więc w Button Class utworzyłem zmienną przechowującą odniesienie do Class. Ale nie mogę sprawić, by utrzymywał klasę lub typ, który jest tą klasą, bez względu na prawidłowe sformułowanie / terminologię. Mogę go tylko pobrać do przechowywania odniesień do instancji. Najlepiej byłoby, gdybyśmy przekazali odwołanie do typu klasy do przycisku. Ale właśnie zacząłem od tworzenia wewnętrznego odniesienia i nie mogłem nawet sprawić, by działało.
Zmieszany
To była kontynuacja metodologii i myślenia, które tutaj wykorzystałem: stackoverflow.com/questions/41092440/… . Od tego czasu nauczyłem się trochę logiki pracy z protokołami, ale szybko doszedłem do faktu, że będę musiał również znaleźć powiązane typy i typy ogólne, aby uzyskać to, co sądziłem, że mogę zrobić z prostszymi mechanizmami. Lub po prostu skopiuj i wklej dużo kodu. Co zwykle robię;)
Confused
7

Mam podobny przypadek użycia w 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
źródło
Staram się zrobić coś podobnego, ale pojawia się błąd przy callsite: Static method … requires that 'MyDecodable.Type' conform to 'Decodable'. Czy mógłbyś zaktualizować swoją odpowiedź, aby podać przykładowe wezwanie loadItem?
Barry Jones
Okazuje się, że jest to punkt, w którym muszę nauczyć się różnicy między .Typei .self(typem a metatypem).
Barry Jones
0

Zastosowanie obj-getclass:

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

}

Zakładając, że ja jest obiektem informacji o mieście.

Cofnij
źródło
0

Szybki 5

Nie do końca ta sama sytuacja, ale miałem podobny problem. W końcu pomogło mi to:

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

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

        default: break
    }
}

Następnie możesz wywołać to wewnątrz instancji wspomnianej klasy w następujący sposób:

myFunction(type(of: self))

Mam nadzieję, że to pomoże komuś w tej samej sytuacji.

Merricat
źródło