2016-04-25 12 views
9

To pytanie zostało zainspirowane przez mz2's answer na pytanie Check for object type fails with "is not a type" error.Jak AnyObject jest zgodny z NSObjectProtocol?

Rozważmy pustą Swift Klasa:

class MyClass { } 

Próba wywołać żadnych NSObjectProtocol metod na wystąpienie tej klasy spowoduje błąd kompilacji:

let obj = MyClass() 
obj.isKindOfClass(MyClass.self) // Error: Value of type 'MyClass' has no member 'isKindOfClass' 

Jednak jeśli rzucam instancja jako AnyObject, mój obiekt jest teraz zgodny z NSObjectProtocol i mogę wywołać metody instancji zdefiniowane przez protokół:

let obj: AnyObject = MyClass() 
obj.isKindOfClass(MyClass.self) // true 
obj.conformsToProtocol(NSObjectProtocol) // true 
obj.isKindOfClass(NSObject.self) // false 

Mój obiekt nie dziedziczy po NSObject, ale nadal jest zgodny z NSObjectProtocol. W jaki sposób AnyObject jest zgodny z NSObjectProtocol?

Odpowiedz

5

W świecie Cocoa/Objective-C AnyObject to id. Po rzutowaniu tego obiektu na obiekt AnyObject można wysłać do niego dowolny znany komunikat Objective-C, taki jak isKindOfClass lub conformsToProtocol. Teraz, kiedy mówisz isKindOfClass lub conformsToProtocol, nie jesteś już w świecie Swift; rozmawiasz z Cocoa z Objective-C. Pomyśl o tym, jak Objective-C widzi ten obiekt. Wszystkie klasy w świecie Objective-C pochodzą z pewnej klasy bazowej; klasa pozbawiona podstaw, taka jak MyClass, jest niemożliwa. I każda klasa bazowa w świecie Objective-C jest zgodna z protokołem NSObject (który Swift wywołuje NSObjectProtocol); to właśnie to jest być (lub zejść) z klasy bazowej! Dlatego też, aby przenieść go do świata Objective-C, Swift przedstawia MyClass jako opadającą ze specjalnej klasy bazowej SwiftObject, która rzeczywiście jest zgodna z NSObjectProtocol (jak widać tutaj: https://github.com/apple/swift/blob/master/stdlib/public/runtime/SwiftObject.mm).

+0

Ale "AnyObject" jest zdefiniowany jako '@kjc public protocol AnyObject {}' Czy to oznacza, że ​​wszystkie protokoły '@ objc' są zgodne z' NSObjectProtocol'? A co z "Any"? – JAL

+0

To wcale tak nie znaczy. Mówię to, co mówię, a nie jakaś inna rzecz. – matt

+0

Mówisz, że "klasa pozbawiona podstaw, taka jak MyClass, jest niemożliwa". Więc mówisz, że 'MyClass' dziedziczy po' SwiftObject'? Dlaczego tylko rzutowanie na "AnyObject" ujawnia zgodność z 'NSObjectProtocol'? – JAL

5

Jeśli mam zrozumienia tego poprawnie na podstawie matt's odpowiedź, to działa, kiedy Swift/Objective-C współdziałanie jest dostępna, ponieważ w rzeczywistości ostatecznie Swift typy klas, które dziedziczą z SwiftObject gdy Objective-C jest współdziałanie sporządzoną, rzeczywiście wiąże Klasa Objective-C (SwiftObject jest zaimplementowany w SwiftObject.mm, który jest skompilowany jako Objective-C++, gdy używany jest interop Celive-C). Tak więc, rzucając obiekt klasy Swift jako obiekt AnyObject "przecieka" tę informację.

zerkanie w niektórych odpowiednich bitów w realizacji od Swift source code, sygn swift/stdlib/public/runtime/SwiftObject.mm:

#if SWIFT_OBJC_INTEROP 

// … 

@interface SwiftObject<NSObject> { 
    SwiftObject_s header; 
} 

// … 

@implementation SwiftObject 

// … 

- (BOOL)isKindOfClass:(Class)someClass { 
    for (auto isa = _swift_getClassOfAllocated(self); isa != nullptr; 
     isa = _swift_getSuperclass(isa)) 
    if (isa == (const ClassMetadata*) someClass) 
     return YES; 

    return NO; 
} 

// … 

// #endif 

Zgodnie z przewidywaniami przez to, ze Swift 3 w Linux (gdzie nie ma Objective-C Runtime dostępny jako część Swift Runtime & realizacja Fundacja ile mi zrozumieć) przykład kodu z tym pytaniem i the earlier question & answer który zainspirował kwestia ta nie powiedzie się z powodu następującego błędu: błąd kompilatora?

ERROR […] value of type 'AnyObject' has no member 'isKindOfClass' 
1

Oprócz matt's odpowiedź, która moim zdaniem jest poprawne:

Is isKindOfClass in this case actually sent as a dynamically dispatched message, even though the class itself is not an Objective-C visible type and does not use messaging based dispatch for its own methods?

Nie, isKindOfClass wysyłany jest jako dynamicznie wywoływane metody ponieważ klasa sama jest widoczny typ Objective-C i dokłada korzystaj z wysyłek opartych na wiadomościach dla własnych metod.

Czyni to ze względu na @objc w @objc public protocol AnyObject {}

Jeśli CMD-kliknij AnyObject w Xcode zobaczysz tego w generowanych nagłówków

/// When used as a concrete type, all known `@objc` methods and 
/// properties are available, as implicitly-unwrapped-optional methods 
/// and properties respectively, on each instance of `AnyObject`. 

A w docs w https://developer.apple.com/library/ios/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html

To be accessible and usable in Objective-C, a Swift class must be a descendant of an Objective-C class or it must be marked @objc.

(podkreślenie moje)

Przyjęcie protokołu oznaczonego @objc oznacza, że ​​twoja klasa jest klasą @objc i jest mostkiem ObjC przez mechanizm interop wskazany przez mz2 w powyższej odpowiedzi.

+0

Dobrze, ale zaskakującą rzeczą, zważywszy na wszystkie udokumentowane informacje, jest to, że działa to dla klas Swift, które nie są oznaczone jako @objc i które nie dziedziczą (w udokumentowany sposób) z klasy Objective-C. I rzeczywiście robią _nie_ zawsze (AnyObject istnieje w języku również bez interakcji Objective-C). – mz2

+0

Za każdym razem, gdy korzystasz z AnyObject, powołujesz się na interakcję ObjC, tak mówi to w dokumentach cytowanych powyżej. –

+0

Oczywiście, ale 'AnyObject' jest tam również, gdy nie ma interakcji ObjC, nawet wkompilowanych w twoją kopię Swift. Na przykład 'klasa A {}; print (A() jako AnyObject) "działa dobrze w Swift 3 na Linuksie. Przypuszczam, że właśnie zaczynamy w tym samym punkcie: robi to na platformach interoperacyjnych ObjC, ponieważ nieudokumentowana klasa bazowa jest z obiektami ObjC zgodnymi z NSObjectProtocol zgodnymi z klasą Objective-C, a efekt (że metody i właściwości są dostępne jako niejawnie nieopakowanych opcji) jest udokumentowana, jak zauważyłeś. – mz2

2

Dodanie dodatkowych informacji do już świetnych odpowiedzi.

stworzyłem trzy programy i spojrzał na wygenerowanym montażu od siebie:

obj1.swift

import Foundation 
class MyClass { } 
let obj = MyClass() 

obj2.swift

import Foundation 
class MyClass { } 
let obj: AnyObject = MyClass() 

obj3.swift

import Foundation 
class MyClass { } 
let obj: AnyObject = MyClass() 
obj.isKindOfClass(MyClass.self) 

różnice między obj1 i obj2 są trywialne. Wszelkie instrukcje, które obejmują typ obiektu mają różne wartości:

movq %rax, __Tv3obj3objCS_7MyClass(%rip) 

# ... 

globl __Tv3obj3objCS_7MyClass   .globl __Tv3obj3objPs9AnyObject_ 
.zerofill __DATA,__common,__Tv3obj3objCS_7MyClass,8,3 

# ... 

.no_dead_strip __Tv3obj3objCS_7MyClass 

vs

movq %rax, __Tv3obj3objPs9AnyObject_(%rip) 

# ... 

.globl __Tv3obj3objPs9AnyObject_ 
.zerofill __DATA,__common,__Tv3obj3objPs9AnyObject_,8,3 

# ... 

.no_dead_strip __Tv3obj3objPs9AnyObject_ 

Pełna edycja here.

To było interesujące dla mnie. Jeśli jedynymi różnicami między tymi dwoma plikami są nazwy typu obiektu, dlaczego obiekt zadeklarowany jako AnyObject może wykonywać selektor Objective-C?

obj3 pokazuje jak selektor isKindOfClass: wypala:

LBB0_2: 
    # ... 
    movq __Tv3obj3objPs9AnyObject_(%rip), %rax 
    movq %rax, -32(%rbp) 
    callq _swift_getObjectType 
    movq %rax, -8(%rbp) 
    movq -32(%rbp), %rdi 
    callq _swift_unknownRetain 
    movq -24(%rbp), %rax 
    cmpq $14, (%rax) 
    movq %rax, -40(%rbp) 
    jne LBB0_4 
    movq -24(%rbp), %rax 
    movq 8(%rax), %rcx 
    movq %rcx, -40(%rbp) 
LBB0_4: 
    movq -40(%rbp), %rax 
    movq "L_selector(isKindOfClass:)"(%rip), %rsi 
    movq -32(%rbp), %rcx 
    movq %rcx, %rdi 
    movq %rax, %rdx 
    callq _objc_msgSend 
    movzbl %al, %edi 
    callq __TF10ObjectiveC22_convertObjCBoolToBoolFVS_8ObjCBoolSb 
    movq -32(%rbp), %rdi 
    movb %al, -41(%rbp) 
    callq _swift_unknownRelease 
    xorl %eax, %eax 
    addq $48, %rsp 

# ... 

LBB6_3: 
    .section __TEXT,__objc_methname,cstring_literals 
"L_selector_data(isKindOfClass:)": 
    .asciz "isKindOfClass:" 

    .section __DATA,__objc_selrefs,literal_pointers,no_dead_strip 
    .align 3 
"L_selector(isKindOfClass:)": 
    .quad "L_selector_data(isKindOfClass:)" 

Diff między obj2 i obj3 here.

isKindOfClass jest wysyłany jako metoda dynamicznie wysyłana, tak jak w przypadku _objc_msgSend. Oba obiekty są narażone na Objective-C jako SwiftObject (.quad _OBJC_METACLASS_$_SwiftObject), deklarując typ obiektu jako AnyObject uzupełniający most do NSObjectProtocol.

+0

Coś, z czym zaczynam się teraz zastanawiać, jako całkowicie odrębne pytanie również oparte na tym, jak to się dzieje, że 'import Foundation' ma pewien efekt: rzucenie obiektu klasy Swift na obiekt AnyObject powoduje zgłoszenie błędu kompilatora, jeśli nie" importujesz " Fundacja ". Czy użycie współrzędnych Objective-C we wszystkich tych modulacjach zależy od tego, czy użyto Fundacji? – mz2

Powiązane problemy