2016-04-25 11 views
9

Этот вопрос был вдохновлен mz2's answer по вопросу Check for object type fails with "is not a type" error.Как AnyObject соответствует NSObjectProtocol?

Рассмотрим пустой Swift класс:

class MyClass { } 

Попытка вызвать любые NSObjectProtocol методы экземпляра этого класса приведет к ошибке времени компиляции:

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

Однако, если я бросаю экземпляр как AnyObject, мой объект теперь соответствует NSObjectProtocol, и я могу назвать методы экземпляра, определенные протоколом:

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

Мой объект не наследует от NSObject, но соответствует NSObjectProtocol. Как AnyObject соответствует NSObjectProtocol?

ответ

5

В мире Cocoa/Objective-C AnyObject - id. Отбросив этот объект в AnyObject, вы можете отправить ему какое-либо известное сообщение Objective-C, например isKindOfClass или conformsToProtocol. Теперь, когда вы говорите isKindOfClass или conformsToProtocol, вы больше не находитесь в мире Swift; вы разговариваете с Cocoa с Objective-C. Поэтому подумайте о том, как Objective-C видит этот объект. Все классы в мире Objective-C происходят из какого-то базового класса; необоснованный класс, такой как MyClass, невозможен. И каждый базовый класс в мире Objective-C соответствует протоколу NSObject (который Swift вызывает NSObjectProtocol); это то, чем он должен быть (или спускаться) с базового класса! Поэтому, чтобы попасть в мир Objective-C, Swift представляет MyClass как нисходящий из специального базового класса SwiftObject, который действительно соответствует NSObjectProtocol (как вы можете видеть здесь: https://github.com/apple/swift/blob/master/stdlib/public/runtime/SwiftObject.mm).

+0

Но 'AnyObject' определяется как' @objc public protocol AnyObject {} 'Означает ли это, что все протоколы' @ objc' соответствуют 'NSObjectProtocol'? Как насчет 'Любые'? – JAL

+0

Это совсем не значит. Я говорю то, что я говорю, а не что-то другое. – matt

+0

Вы говорите, что «необоснованный класс, такой как MyClass, невозможно». Так вы говорите, что «MyClass» наследует «SwiftObject»? Почему только листинг в 'AnyObject' выставляет соответствие NSObjectProtocol? – JAL

5

Если я понять это правильно на основе matt's ответ, это работает, когда Swift/Objective-C Interop доступен, потому что на самом деле Swift типов классов в конечном счете наследует от SwiftObject, который когда Objective-C Interop составляется в самом деле включает в себя Класс Objective-C (SwiftObject реализован в SwiftObject.mm, который скомпилирован как Objective-C++ при использовании взаимодействия Objective-C). Таким образом, листинг класса типа Swift как типа AnyObject «утечки» этой информации.

Заглянув в некоторых соответствующих битов в реализации от Swift source code, файл 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 

Как и предсказывали это, с Swift 3 в Linux (там, где нет Objective-C во время выполнения доступны как часть Swift выполнения & реализации Foundation, насколько я понимаю) пример кода из этого вопроса и the earlier question & answer, который вдохновил этот вопрос не удается из-за ошибки компиляции ошибка:

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

В дополнение к matt's ответ, который я считаю правильным:

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?

Нет, isKindOfClass посылается как динамически вызываемым методом потому сам класс является видимый тип Objective-C и делает использование отправка сообщений на основе собственных методов.

Он делает это из-за @objc в @objc public protocol AnyObject {}

Если вы Cmd кнопкой мыши на AnyObject в XCode вы увидите это в сгенерированных заголовках

/// 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`. 

И в документации на 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.

(мой упор)

Принятие протокола с тегом @objc означает, что ваш класс является классом @objc и является ObjC, соединенным через механизм взаимодействия, указанным в mz2 в ответе выше.

+0

Правильно, но удивительной вещью, которой все это документированная информация по-прежнему является то, что это работает для классов Swift, которые не помечены @objc и которые не наследуются (документированным образом) из класса Objective-C. И действительно, они не всегда (AnyObject существует на языке также без Objective-C interop). – mz2

+0

Каждый раз, когда вы используете AnyObject, вы вызываете objC interop, он говорит так прямо там, в документах, как указано выше. –

+0

Конечно, но 'AnyObject' также существует, когда в вашей копии Swift нет компиляции ObjC. Например, 'class A {}; print (A() как AnyObject) 'отлично работает в Swift 3 на Linux. Я полагаю, что мы просто получаем одно и то же: это делает на совместимых платформах ObjC, потому что недокументированный базовый класс с ObjC взаимодействует с классом Objective-C, совместимым с NSObjectProtocol, и эффектом (что методы и свойства доступны как неявно разворачиваемые варианты), как вы указали. – mz2

2

Добавление дополнительной информации к уже отличным ответам.

Я создал три программы и посмотрел на сгенерированной сборки из каждого:

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) 

T различия между obj1 и obj2 тривиальны. Любые инструкции, которые включают тип объекта имеют разные значения:

movq %rax, __Tv3obj3objCS_7MyClass(%rip) 

# ... 

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

# ... 

.no_dead_strip __Tv3obj3objCS_7MyClass 

против

movq %rax, __Tv3obj3objPs9AnyObject_(%rip) 

# ... 

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

# ... 

.no_dead_strip __Tv3obj3objPs9AnyObject_ 

Полный дифференциал here.

Это было интересно для меня. Если единственными различиями между этими двумя файлами являются имена типа объекта, почему объект, объявленный как AnyObject, выполняет селектор Objective-C?

obj3 показывает, как срабатывает селектор isKindOfClass::

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:)" 

Разница между obj2 и obj3 here.

isKindOfClass выслан как динамически отправленный метод, см. _objc_msgSend. Оба объекта подвергаются Objective-C как SwiftObject (.quad _OBJC_METACLASS_$_SwiftObject), объявляя тип объекта как AnyObject, завершает мост до NSObjectProtocol.

+0

Что-то, о чем я сейчас начинаю задавать, поскольку совершенно отдельный вопрос, основанный на этом, заключается в том, как эффект 'import Foundation' имеет эффект: листинг класса типа Swift для AnyObject вызывает ошибку компилятора, если вы не импортируете Foundation'. Используется ли объединение Objective-C во всех этих модулях в зависимости от того, используется ли Foundation? – mz2