Słabe łączenie - sprawdź, czy klasa istnieje i użyj tej klasy

87

Próbuję stworzyć uniwersalną aplikację na iPhone'a, ale używa ona klasy zdefiniowanej tylko w nowszej wersji SDK. Framework istnieje w starszych systemach, ale klasa zdefiniowana w frameworku nie.

Wiem, że chcę użyć jakiegoś słabego linkowania, ale każda dokumentacja, którą mogę znaleźć, mówi o sprawdzaniu istnienia funkcji w czasie wykonywania - jak sprawdzić, czy klasa istnieje?

psychotik
źródło
Jaka jest klasa? Może są inne obejścia.
Marcelo Cantos

Odpowiedzi:

164

TLDR

Obecny:

  • Szybki :if #available(iOS 9, *)
  • Obj-C, iOS :if (@available(iOS 11.0, *))
  • Obj-C, OS X :if (NSClassFromString(@"UIAlertController"))

Dziedzictwo:

  • Swift (wersje wcześniejsze niż 2.0) :if objc_getClass("UIAlertController")
  • Obj-C, iOS (wersje wcześniejsze niż 4.2) :if (NSClassFromString(@"UIAlertController"))
  • Obj-C, iOS (wersje wcześniejsze niż 11.0) :if ([UIAlertController class])

Swift 2+

Chociaż historycznie zaleca się sprawdzanie możliwości (lub istnienia klas), a nie konkretnych wersji systemu operacyjnego, nie działa to dobrze w Swift 2.0 ze względu na wprowadzenie sprawdzania dostępności .

Zamiast tego użyj tego sposobu:

if #available(iOS 9, *) {
    // You can use UIStackView here with no errors
    let stackView = UIStackView(...)
} else {
    // Attempting to use UIStackView here will cause a compiler error
    let tableView = UITableView(...)
}

Uwaga: jeśli zamiast tego spróbujesz użyć objc_getClass(), pojawi się następujący błąd:

⛔️ „UIAlertController” jest dostępny tylko w systemie iOS 8.0 lub nowszym.


Poprzednie wersje Swift

if objc_getClass("UIAlertController") != nil {
    let alert = UIAlertController(...)
} else {
    let alert = UIAlertView(...)
}

Zauważ, że objc_getClass() jest bardziej niezawodny niż NSClassFromString()lubobjc_lookUpClass() .


Objective-C, iOS 4.2+

if ([SomeClass class]) {
    // class exists
    SomeClass *instance = [[SomeClass alloc] init];
} else {
    // class doesn't exist
}

Zobacz odpowiedź code007, aby uzyskać więcej informacji .


OS X lub poprzednie wersje iOS

Class klass = NSClassFromString(@"SomeClass");
if (klass) {
    // class exists
    id instance = [[klass alloc] init];
} else {
    // class doesn't exist
}

Użyj NSClassFromString(). Jeśli zwróci nil, klasa nie istnieje, w przeciwnym razie zwróci obiekt klasy, którego można użyć.

Jest to zalecany sposób według Apple w tym dokumencie :

[...] Twój kod sprawdziłby istnienie [a] klasy, używając, NSClassFromString()która zwróci prawidłowy obiekt klasy, jeśli [ta] klasa istnieje, lub zerową, jeśli nie. Jeśli klasa istnieje, Twój kod może z niej korzystać [...]

Rozsądne
źródło
Dzięki. Aby uzupełnić odpowiedź dla innych, po wykryciu klasy tworzysz ją za pomocą Classinstancji zwróconej przez NSClassFromString(przypisz ją do id) i wywołaj selektory tej instancji.
psychotik
2
Warto zauważyć, że jest to jedyny sposób na OSX, ale nie „najlepszy” sposób na iOS (4.2+), chociaż będzie działał. Zobacz odpowiedź code007 specjalnie dla iOS.
Ben Mosher,
Co jeśli moja klasa nie jest prawdziwą klasą, ale raczej strukturą C?
Nathan H
Swift 4.1 nadchodzi z nowym czekiem canImport. Wniosek
dispatchMain
69

W przypadku nowych projektów korzystających z podstawowego zestawu SDK systemu iOS 4.2 lub nowszego istnieje nowe zalecane podejście polegające na użyciu metody klasy NSObject w celu sprawdzenia dostępności słabo połączonych klas w czasie wykonywania. to znaczy

if ([UIPrintInteractionController class]) {
    // Create an instance of the class and use it.
} else {
    // Alternate code path to follow when the
    // class is not available.
}

źródło: https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/cross_development/Using/using.html#//apple_ref/doc/uid/20002000-SW3

Ten mechanizm wykorzystuje makro NS_CLASS_AVAILABLE, które jest dostępne dla większości frameworków w iOS (zauważ, że mogą istnieć frameworki, które jeszcze nie obsługują NS_CLASS_AVAILABLE - sprawdź informacje o wydaniu iOS). Może być również wymagana dodatkowa konfiguracja ustawień, którą można przeczytać w podanym powyżej łączu do dokumentacji firmy Apple, jednak zaletą tej metody jest to, że uzyskuje się statyczne sprawdzanie typu.

code007
źródło
3
Trochę późno w grze, ale właśnie natknąłem się na ten problem, próbując zbudować kod, który zawierał UIAlertController, mimo że nadal obsługuje iOS 7. Odpowiedź code007 jest poprawna, ale potrzebna dodatkowa konfiguracja to słabe połączenie (ustawienie z Requireddo Optional) UIKit w twoim projekcie (przynajmniej w tej sytuacji).
Evan R