Swift natywna klasa bazowa lub NSObject

105

Przetestowałem niektóre isa swizzling za pomocą Swift i stwierdziłem, że działa tylko wtedy, gdy NSObject jest super-klasą (bezpośrednio lub wyżej) lub używając dekoracji „@objc”. W przeciwnym razie będzie postępować zgodnie ze stylem static- i vtable-dispatch, jak C ++.

Czy definiowanie klasy Swift bez klasy bazowej Cocoa / NSObject jest normalne? Jeśli mnie to niepokoi, oznacza to rezygnację z dużej części dynamiki Objective-C, takiej jak przechwytywanie metod i introspekcja w czasie wykonywania.

Dynamiczne zachowanie w czasie wykonywania leży u podstaw takich funkcji, jak obserwatorzy właściwości, dane podstawowe, programowanie zorientowane na aspekty , przesyłanie wiadomości wyższego rzędu , struktury analityczne i rejestrowania itd.

Użycie stylu wywołania metody Objective-C dodaje około 20 operandów kodu maszynowego do wywołania metody, więc w pewnych sytuacjach ( wiele ciasnych wywołań metod z małymi treściami) wysyłanie statyczne i vtable w stylu C ++ może działać lepiej.

Ale biorąc pod uwagę ogólną zasadę 95-5 ( 95% wzrostu wydajności pochodzi z dostrojenia 5% kodu ), czy nie ma sensu zaczynać od potężnych funkcji dynamicznych i wzmacniać je tam, gdzie to konieczne?

Jasper Blues
źródło
Powiązane: Czy Swift obsługuje programowanie zorientowane aspektowo? stackoverflow.com/a/24137487/404201
Jasper Blues

Odpowiedzi:

109

Klasy Swift będące podklasami NSObject:

  • to same klasy Objective-C
  • używają objc_msgSend()do wywołań (większości) ich metod
  • dostarczają metadane środowiska wykonawczego Objective-C dla (większości) implementacji metod

Klasy Swift, które nie są podklasami NSObject:

  • są klasami Objective-C, ale implementują tylko kilka metod zapewniających zgodność z NSObject
  • nie używaj objc_msgSend()do wywołań ich metod (domyślnie)
  • nie dostarczają metadanych środowiska wykonawczego Objective-C dla ich implementacji metod (domyślnie)

Podklasa NSObject w Swift zapewnia elastyczność środowiska wykonawczego Objective-C, ale także wydajność Objective-C. Unikanie NSObject może poprawić wydajność, jeśli nie potrzebujesz elastyczności Objective-C.

Edytować:

W przypadku Xcode 6 beta 6 pojawia się atrybut dynamiczny. To pozwala nam poinstruować Swifta, że ​​metoda powinna używać dynamicznego wysyłania, a zatem będzie wspierać przechwytywanie.

public dynamic func foobar() -> AnyObject {
}
Greg Parker
źródło
1
Jakiego rodzaju wysyłki używa Swift zamiast objc_msgSend? Czy to jest statyczne?
Bill
12
Swift może używać wysyłki objc_msgSend, wirtualnego wysyłania tabeli, bezpośredniego wysyłania lub wstawiania.
Greg Parker
statyczny: mniej niż 1,1ns. vtable 1.1ns, msgSend 4.9ns. . (Oczywiście w zależności od sprzętu). . Wydaje się, że `` czysty '' Swift jest świetnym językiem do programowania na poziomie systemu, ale w przypadku aplikacji nie chciałbym rezygnować z funkcji dynamicznych, chociaż byłbym szczęśliwy, gdyby przeniósł się do kompilatora tak, jak w Garbage Collection vs ARC. . . Słyszałem, że wysyłanie statyczne pozwala na lepsze przewidywanie gałęzi (a tym samym wydajności) w systemach wielordzeniowych. Prawdziwe?
Jasper Blues
„Swift Classes, które nie są podklasami NSObject, są klasami Objective-C” - czy możesz podać łącze do miejsca, w którym zostało to określone?
Matt S.
1
Podsumowując, powinienem podklasować NSObject w Swift tylko wtedy, gdy muszę komunikować się z kodem Objective C?
MobileMon
14

Odkryłem również, że opierając klasę Swift na NSObject, zauważyłem nieoczekiwane zachowanie w czasie wykonywania, które mogło ukryć błędy w kodowaniu. Oto przykład.

W tym przykładzie, gdy nie opieramy się na NSObject, kompilator poprawnie wykrywa błąd w testIncorrect_CompilerShouldSpot, zgłaszając „... 'MyClass' nie można zamienić na 'MirrorDisposition'”

class MyClass {
  let mString = "Test"

  func getAsString() -> String {
    return mString
  }

  func testIncorrect_CompilerShouldSpot() {
    var myString = "Compare to me"
      var myObject = MyClass()
      if (myObject == myString) {
        // Do something
      }
  }

  func testCorrect_CorrectlyWritten() {
    var myString = "Compare to me"
      var myObject = MyClass()
      if (myObject.getAsString() == myString) {
        // Do something
      }
  }
}

W tym przykładzie, gdy opieramy się na NSObject , kompilator nie dostrzega błędu w testIncorrect_CompilerShouldSpot:

class myClass : NSObject {
  let mString = "Test"

  func getAsString() -> String {
    return mString
  }

  func testIncorrect_CompilerShouldSpot() {
    var myString = "Compare to me"
      var myObject = MyClass()
      if (myObject == myString) {
        // Do something
      }
  }

  func testCorrect_CorrectlyWritten() {
    var myString = "Compare to me"
      var myObject = MyClass()
      if (myObject.getAsString() == myString) {
        // Do something
      }
  }
}

Wydaje mi się, że morał jest taki, że bazuj tylko na NSObject, gdzie naprawdę musisz!

Pete
źródło
1
Dzięki za informację.
Jasper Blues
13

Zgodnie z odniesieniem do języka, nie ma wymogu, aby klasy tworzyły podklasy dowolnej standardowej klasy głównej, więc w razie potrzeby można dołączyć lub pominąć nadklasę.

Zauważ, że pominięcie nadklasy w deklaracji klasy nie przypisuje żadnej domyślnej nadklasy bazowej. Definiuje klasę bazową, która w efekcie stanie się korzeniem niezależnej hierarchii klas.

Z odniesienia językowego:

Klasy Swift nie dziedziczą po uniwersalnej klasie bazowej. Klasy zdefiniowane bez określania nadklasy automatycznie stają się klasami podstawowymi, na których można budować.

Próba odniesienia superz klasy bez superklasy (tj. Klasy bazowej) spowoduje błąd czasu kompilacji

'super' members cannot be referenced in a root class
Cezar
źródło
1
Tak. Jeśli spróbujesz utworzyć plik źródłowy Cocoa lub Cocoa Touch w kontekście projektu, forma, w której wstawiasz podklasę, faktycznie uniemożliwia utworzenie klasy, pozostawiając ją pustą. Możesz usunąć nadklasę później, po jej utworzeniu.
Cezar,
1
Dzięki. Używanie wysyłania statycznego i vtable ma sens w przypadku programowania systemów lub dostrajania wydajności. Aby zachować spójność z rodzimym kakao, myślę, że dynamiczne powinno być domyślne. (Chyba że ktoś może mnie przekonać inaczej, stąd to pytanie).
Jasper Blues
1

Uważam, że zdecydowana większość danych Swift nie będzie objc . Tylko te części, które muszą komunikować się z infrastrukturą Celu C, zostaną wyraźnie oznaczone jako takie.

Nie wiem, w jakim stopniu introspekcja środowiska uruchomieniowego zostanie dodana do języka. Przechwytywanie metody będzie prawdopodobnie możliwe tylko wtedy, gdy metoda wyraźnie na to pozwoli. Zgaduję, ale tylko projektanci języków w Apple naprawdę wiedzą, dokąd zmierzają.

Plik analogowy
źródło
Dla mnie sensowne jest ustawienie dynamizmu jako domyślnego (poprzez rozszerzenie NSObject, użycie dekoracji „@objc” lub inne podejście). Wygląda na to, że gdy nie ma klasy bazowej, Swift faworyzuje wysyłanie statyczne / vtable, które działa lepiej, ale w większości przypadków nie będzie to konieczne. . (90% zysków pochodzi ze strojenia 10% kodu). Mam nadzieję, że spójność z dynamizmem Objective-C to raczej rezygnacja niż opt-in.
Jasper Blues
Sposób zaprojektowania języka jest opcjonalną opcją. O ile nam wiadomo, może się zdarzyć, że w przyszłości zostaną dodane systemowe API, które nie są natywnie objc. W perspektywie średnio- i długoterminowej mogą nawet migrować istniejące biblioteki do kodu innego niż objc z cienką warstwą kompatybilności w objc, która wywołuje kod inny niż objc. Po prostu nie wiemy. Prawdopodobnie będziemy w stanie to zrobić educated guessesza kilka lat. Ale na razie to tylko duży znak zapytania.
Plik analogowy
@JasperBlues Twierdziłbym, że dynamizm nie jest potrzebny przez większość czasu i wolałbym mieć domyślną wydajność, a następnie włączyć dynamiczne zachowanie. Do każdego chyba jego :)
Lance
@ Lance Hmmmm. Może. Nadal martwię się o: obserwatorów, AOP, frameworki testowe, ramy analityczne itp. a wydajność polega na tym, że 90% zysków pochodzi z dostrojenia 10%. . więc to jest moje uzasadnienie - zdecyduj się na te 10% przypadków. Myślę, że AOP będzie wielkim problemem dla aplikacji korporacyjnych iOS, ale można to zrobić za pomocą narzędzia kompilatora, tak jak w C ++. . z pewnością twórcy gier, obliczeń naukowych, grafiki itp. z radością powitają większą wydajność :)
Jasper Blues
1

Poniższy tekst jest skopiowany z Apple's Swift-eBook i zawiera odpowiednią odpowiedź na Twoje pytanie:

Definiowanie klasy bazowej

Każda klasa, która nie dziedziczy z innej klasy, jest nazywana klasą bazową.

Klasy Swift nie dziedziczą po uniwersalnej klasie bazowej. Klasy zdefiniowane bez określania nadklasy automatycznie stają się klasami podstawowymi, na których można budować.


Odniesienie

https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Inheritance.html#//apple_ref/doc/uid/TP40014097-CH17-XID_251

wottpal
źródło
-6

To normalne. Spójrz na cele projektowe Swift: celem jest wyeliminowanie ogromnych klas problemów programistycznych. Swizzling metod prawdopodobnie nie jest jedną z rzeczy, które chcesz robić w Swift.

gnasher729
źródło
3
Swizzling metod to sposób na osiągnięcie wzorca przecięcia w czasie wykonywania. Jednym z zastosowań tego jest zastosowanie zagadnień przekrojowych zgodnie z zasadami programowania zorientowanego na aspekty. Własne powiadomienia Apple, obserwatorzy nieruchomości (wbudowani w Swift), podstawowe dane - wszystkie używają swizzlingu z dobrym skutkiem. . Jedną z rzeczy, które zakochały ludzi w Objective-C pomimo jego niezgrabnej składni, był ten dynamizm, który ułatwia dostarczanie eleganckich rozwiązań zawsze, gdy wymagany jest wzorzec przechwytywania. . w rzeczywistości nie było formalnych ram AOP dla Objc, ponieważ surowce były tak dobre.
Jasper Blues
Fakt, że tak to się teraz robi, nie oznacza, że ​​tak będzie jutro. Jak mówisz, powiadomienia, obserwatorzy nieruchomości, delegaci i tym podobne są lub mogą być dostarczane natywnie. Język będzie ewoluował, a my nie wiemy, w jakim kierunku. Wiele zyskał pod względem obsługi programowania funkcjonalnego. Ale robi to w niecodzienny sposób, więc nie mam pojęcia, ile brakuje. Jednak o ile wiemy, mogą zmierzać w kierunku zniechęcania do mutacji i zachęcania do czysto funkcjonalnego programowania. A jeśli przyszłość interfejsu użytkownika jest reaktywna? Nie ma jeszcze sposobu, żeby wiedzieć.
Plik analogowy
1
Tak, ale wszystkie te rzeczy zależą od przechwytywania, które można wykonać na dwa sposoby: w czasie kompilacji (C ++) lub w czasie wykonywania (Ruby, Objective-C, Java (przez asm, cglib, ApsectJ itp.)). Współczesne języki robią to w czasie wykonywania. Ludzie kochali Objective-C, ponieważ zachowywał się jak nowoczesny język, mimo że był starym prymusem. Jak mówisz, im więcej tego jest wypieczone w języku, tym lepiej (pod warunkiem, że jest dobrze zrobione!). . ale na razie możemy połączyć się ze spuścizną dostarczoną przez Objc / Foundation, dlatego mam nadzieję, że rozszerzanie NSObject stanie się standardową praktyką dla codziennych aplikacji w ciągu najbliższych kilku lat.
Jasper Blues