== Перегрузка для настраиваемого classа не всегда называется

У меня есть пользовательский оператор, определяемый глобально, например:

func ==(lhs: Item!, rhs: Item!)->Bool { return lhs?.dateCreated == rhs?.dateCreated } 

И если я исполню этот код:

 let i1 = Item() let i2 = Item() let date = Date() i1.dateCreated = date i2.dateCreated = date let areEqual = i1 == i2 

areEqual является ложным. В этом случае я точно знаю, что мой пользовательский оператор не стреляет. Однако, если я добавлю этот код на площадку:

 //same function func ==(lhs: Item!, rhs: item!)->Bool { return lhs?.dateCreated == rhs?.dateCreated } //same code let i1 = Item() let i2 = Item() let date = Date() i1.dateCreated = date i2.dateCreated = date let areEqual = i1 == i2 

areEqual верно – я предполагаю, что мой пользовательский оператор уволен в этом случае.

У меня нет других настраиваемых операторов, которые могли бы вызвать конфликт в случае без игрового поля, а class Item в обоих случаях одинаковый, поэтому почему мой пользовательский оператор не вызывается за пределами игровой площадки?

Класс Item наследуется от classа Object предоставленного Realm , который в конечном итоге наследуется от NSObject . Я также заметил, что, если я определяю нестационарные входы для перегрузки, когда входы являются дополнительными, это не срабатывает.

Есть две основные проблемы с тем, что вы пытаетесь сделать здесь.

1. Разрешение перегрузки благоприятствует супертипам за дополнительную рекламу

Вы заявили о своей перегрузке для Item! параметры, а не параметры Item . Поступая таким образом, средство проверки типов взвешивает больше в пользу статической отправки на перегрузку NSObject для == , поскольку, как представляется, средство проверки типов поддерживает подclass для конверсий над суперclassом по сравнению с дополнительной рекламой (я не смог найти источник для подтверждения этого).

Как правило, вам не нужно определять собственную перегрузку для обработки опций. Equatable данный тип с Equatable , вы автоматически получите перегрузку == которая обрабатывает проверку равенства между необязательными экземплярами этого типа.

Более простой пример, демонстрирующий преимущество перегрузки суперclassа над дополнительной перегрузкой подclassа, будет:

 // custom operator just for testing. infix operator <===> class Foo {} class Bar : Foo {} func <===>(lhs: Foo, rhs: Foo) { print("Foo's overload") } func <===>(lhs: Bar?, rhs: Bar?) { print("Bar's overload") } let b = Bar() b <===> b // Foo's overload 

Если Bar? перегрузка изменяется на Bar – вместо этого будет вызвана перегрузка.

Поэтому вы должны изменить свою перегрузку, чтобы вместо этого использовать параметры Item . Теперь вы сможете использовать эту перегрузку для сравнения двух экземпляров Item для равенства. Однако это не будет полностью решить вашу проблему из-за следующей проблемы.

2. Подclassы не могут напрямую повторно выполнять требования протокола

Item не соответствует Equatable . Вместо этого он наследует от NSObject , который уже соответствует Equatable . Его реализация == только вперед на isEqual(_:) – который по умолчанию сравнивает адреса памяти (т. isEqual(_:) являются ли два экземпляра одним и тем же экземпляром).

Это означает, что если вы перегружаете == для Item , эта перегрузка не может быть динамически отправлена. Это связано с тем, что Item не получает свою собственную таблицу свидетельств протокола для соответствия Equatable – вместо этого он полагается на PWT NSObject, который отправит на свою == перегрузку, просто вызывая isEqual(_:) .

(Протоколы свидетельских показаний протокола – это механизм, используемый для обеспечения динамической отправки протоколов – см. Этот разговор WWDC о них для получения дополнительной информации.)

Поэтому это предотвратит вызов вашей перегрузки в общих контекстах, включая вышеупомянутую бесплатную перегрузку == для опций – объяснение, почему она не работает при попытке сравнить Item? экземпляров.

Это можно увидеть в следующем примере:

 class Foo : Equatable {} class Bar : Foo {} func ==(lhs: Foo, rhs: Foo) -> Bool { // gets added to Foo's protocol witness table. print("Foo's overload") // for conformance to Equatable. return true } func ==(lhs: Bar, rhs: Bar) -> Bool { // Bar doesn't have a PWT for conformance to print("Foo's overload") // Equatable (as Foo already has), so cannot return true // dynamically dispatch to this overload. } func areEqual(lhs: T, rhs: T) -> Bool { return lhs == rhs // dynamically dispatched via the protocol witness table. } let b = Bar() areEqual(lhs: b, rhs: b) // Foo's overload 

Таким образом, даже если вы должны изменить свою перегрузку таким образом, чтобы она вводила Item , если == был вызван из общего контекста экземпляра Item , ваша перегрузка не будет вызвана. Перегрузка NSObject будет.

Это поведение несколько неочевидно и было подано как ошибка – SR-1729 . Обоснование этого, как объяснил Джордан Роуз:

[…] Подclass не может предоставлять новые члены для соответствия требованиям. Это важно, потому что протокол может быть добавлен к базовому classу в одном модуле и подclassе, созданном в другом модуле.

Что имеет смысл, так как модуль, в котором находится подclass, должен быть перекомпилирован, чтобы позволить ему удовлетворять требованиям, что, вероятно, приведет к проблемному поведению.

Однако стоит отметить, что это ограничение действительно проблематично с требованиями оператора, так как другие требования протокола обычно могут быть переопределены подclassами. В таких случаях переопределяющие реализации добавляются к подclassу vtable, что позволяет проводить динамическую отправку, как ожидалось. Однако в настоящее время это невозможно сделать с операторами без использования вспомогательного метода (например, isEqual(_:) ).

Решение

Следовательно, решение состоит в том, чтобы переопределить метод isEqual(_:) и свойство hash а не перегрузку == (см. Это Q & A, как это сделать). Это гарантирует, что ваша реализация равенства всегда будет вызываться независимо от контекста, поскольку ваше переопределение будет добавлено в таблицу vtable classа, что позволит динамически отправлять сообщения.

Обоснование переопределения hash а также isEqual(_:) заключается в том, что вам нужно сохранить promise, что если два объекта сравниваются равными, их hashи должны быть одинаковыми. Всевозможные странности могут возникать иначе, если Item когда-либо hashируется.

Очевидно, что решение для не связанных с NSObject classов должно было бы определить ваш собственный isEqual(_:) , и подclassы переопределяют его (а затем просто имеют для него цепочку перегрузки == ).

  • Обновление пользовательского интерфейса с помощью Dispatch_Async в Swift
  • Запретить увольнение UIAlertController
  • UITableview с более чем одной пользовательской ячейкой с Swift
  • Есть ли способ установить связанные объекты в Swift?
  • Как импортировать файл Swift из другого файла Swift?
  • Что такое Swift-эквивалент ответаSoSelector?
  • Что означает восклицательный знак на языке Swift?
  • Swift Alamofire: как получить код статуса ответа HTTP
  • Есть ли способ для Interface Builder визуализировать представления IBDesignable, которые не переопределяют drawRect:
  • Вызов метода из строки в Swift
  • развертывание нескольких опций в выражении if
  • Давайте будем гением компьютера.