Чей стол свидетеля должен быть использован? - PullRequest
4 голосов
/ 04 апреля 2019

Я хочу иметь четкое представление о диспетчеризации методов в Swift.Я прочитал из этого популярного блога о трех типах рассылок, как показано ниже:

  1. Динамический
  2. Таблица (Таблица свидетелей в Swift)
  3. Сообщение

В этом блоге автор говорит, что подтипы NSObject поддерживают таблицу диспетчеризации (таблицу свидетелей), а также иерархию диспетчеризации сообщений.Ниже приведен фрагмент кода, представленный автором:

class Person: NSObject {
    func sayHi() {
        print("Hello")
    }
}
func greetings(person: Person) {
    person.sayHi()
}

greetings(person: Person()) // prints 'Hello'

class MisunderstoodPerson: Person {}
extension MisunderstoodPerson {
    override func sayHi() {
        print("No one gets me.")
    }
}
greetings(person: MisunderstoodPerson()) // prints 'Hello'

Я приведу аргументацию автора для вызова sayHi () в экземпляре Person :

Метод greetings (person :) использует диспетчеризацию таблиц для вызова sayHi ().Это решает, как и ожидалось, и «Hello» печатается.Здесь нет ничего захватывающего.Теперь давайте создадим подкласс Person

Автор продолжает объяснять вызов sayHi () для экземпляра типа MisunderstoodPerson, передаваемого Person :

Обратите внимание, что sayHi () объявлена ​​в расширении, означающем, что метод будет вызываться при отправке сообщения.Когда вызывается приветствие (person :), sayHi () отправляется объекту Person посредством отправки таблицы.Поскольку переопределение MisunderstoodPerson было добавлено посредством отправки сообщений, в таблице диспетчеризации MisunderstoodPerson по-прежнему имеется реализация Person в таблице диспетчеризации, и возникает путаница.

Я хочу знать, как автор пришел к выводу, что метод greetings (person :) использует диспетчеризацию таблицы для вызова sayHi ()

Одна вещь, о которой автор упоминает ранее в блоге, - это когда подкласс NSObject объявляет метод в первоначальном объявлении (то естьне в расширении) будет использоваться таблица отправки.

Поэтому я предполагаю, что в качестве параметра 'person' типом является Person для greetings (person:) , а вызываемый метод - sayHi (), который объявленв первоначальном объявлении Person class используется отправка таблицы, и sayHi () от Person называется . Можно с уверенностью сказать, что используется таблица свидетелей Person .

Как только мы получим подкласс MisunderstoodPerson и передадим этот экземпляр приветствиям (person :), этот экземпляр следует рассматривать как Человек .У меня тут путаница и у меня есть пара вопросов.

  • Экземпляр имеет тип MisunderstoododPerson , поэтому здесь будет использоваться таблица MisunderstoododPerson .
  • Или экземпляр был приведен к типу Person , поэтому здесь будет использоваться таблица свидетелей Person .

Автор не разъясняет в блоге, чьи таблицы свидетелей следует использовать,Даже в некоторых случаях мне кажется, что автор описывает даже компиляторы, создающие таблицы свидетелей для протоколов.Это видно из изображения, которое он публикует в блоге, как показано ниже.

enter image description here Я был бы очень признателен, если бы кто-нибудь мог это объяснить.

1 Ответ

4 голосов
/ 05 апреля 2019

Эта запись блога немного устарела, поскольку наследование от NSObject больше не меняет поведение диспетчеризации класса (в Swift 3 это приведет к неявному представлению членов Obj-C, что изменит поведение диспетчеризациидобавочные члены, , но это уже не так ).Приведенный ими пример также больше не компилируется в Swift 5, так как вы можете переопределить только элемент dynamic из расширения.

Чтобы отличить статическую диспетчеризацию от динамической диспетчеризации, давайте рассмотрим протоколы отдельно.Для протоколов динамическая диспетчеризация используется, если оба:

  • Элемент объявлен в основном теле протокола (это называется точкой требования или настройки).
  • Элементвызывается для значения типа протокола P, типа набора протокола P & X или типового значения заполнителя T : P, например:

    protocol P {
      func foo()
    }
    
    struct S : P {
      func foo() {}
    }
    
    func bar(_ x: S) {
      x.foo() // Statically dispatched.
    }
    
    func baz(_ x: P) { 
      x.foo() // Dynamically dispatched.
    }
    
    func qux<T : P>(_ x: T) { 
      x.foo() // Also dynamically dispatched.
    }
    

Если протоколis @objc, используется диспетчеризация сообщений, в противном случае используется диспетчеризация таблиц.

Для непротокольных участников вы можете задать вопрос: «Может ли это быть переопределено?».Если ответ отрицательный, вы смотрите на статическую рассылку (например, struct член или final член класса).Если это можно переопределить, вы смотрите на какую-то форму динамической отправки.Однако стоит отметить, что если оптимизатор может доказать, что он не переопределен (например, если он fileprivate и не переопределен в этом файле), то он может быть оптимизирован для использования статической диспетчеризации.

Для обычноговызов метода, модификатор dynamic - это то, что отличает две текущие формы динамической диспетчеризации, диспетчеризации таблиц и диспетчеризации сообщений Obj-C.Если член dynamic, Swift будет использовать отправку сообщений.Как уже говорилось, это правило кажется довольно простым, однако некоторые члены явно не отмечены dynamic - компилятор выводит его вместо этого.Сюда входят:

Менее известной формой вызова метода в Swift является динамический вызов метода, который выполняется путем доступа к члену @objc вAnyObject значение.Например:

import Foundation

class C {
  @objc func foo() {}
}

func bar(_ x: AnyObject) {
  // Message dispatch (crashing if the object doesn't respond to foo:).
  x.foo!()
}

Такие вызовы всегда используют рассылку сообщений.

И я думаю, что об этом суммирует текущие правила, вокруг которых используются механизмы диспетчеризации.


Как только мы получим подкласс MisunderstoodPerson и передадим этот экземпляр в greetings(person:), этот экземпляр должен рассматриваться как Person.У меня здесь путаница, и у меня есть пара вопросов:

  • Экземпляр имеет тип MisunderstoodPerson, поэтому здесь будет использоваться таблица MisunderstoodPerson.
  • Или экземплярбыл приведен по типу Person, поэтому здесь будет использоваться таблица-свидетель Person.

(небольшая терминология: для классов она называется vtable, а нетаблица-свидетель)

Это всегда vtable, который соответствует динамическому типу используемого экземпляра, поэтому в этом случае это будет vtable MisunderstoodPerson.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...