Вы (Мэтт), вероятно, уже знаете хотя бы кое-что из этого, но вот некоторые факты для других читателей:
Swift выводит типы по одному целому выражению за раз, но не между операторами.
Swift позволяет выводу типа автоматически выдвигать объект типа T
для ввода Optional<T>
, если необходимо выполнить проверку типа оператора.
Swift также позволяет выводу типа автоматически продвигать замыкание типа (A) -> B
для типа (A) -> B?
. Другими словами, это составляет:
let a: (Data) -> UIImage? = { UIImage(data: $0) }
let b: (Data) -> UIImage?? = a
Это стало для меня неожиданностью. Я обнаружил это во время исследования вашей проблемы.
Теперь давайте рассмотрим использование assign
:
let p0 = Just(Data())
.compactMap { UIImage(data: $0) }
.receive(on: DispatchQueue.main)
.assign(to: \.image, on: self.iv)
Swift-проверяет весь этот оператор одновременно. Поскольку тип \UIImageView.image
Value
равен UIImage?
, а тип self.iv
равен UIImageView!
, Swift должен сделать две «автоматические» вещи для проверки этого оператора:
Он должен способствовать закрытию { UIImage(data: $0) }
от типа (Data) -> UIImage?
до типа (Data) -> UIImage??
, чтобы compactMap
мог убрать один уровень Optional
и сделать тип Output
равным UIImage?
.
Должно быть неявно развернуто iv
, поскольку Optional<UIImage>
не имеет свойства с именем image
, но UIImage
не имеет.
Эти два действия позволяют Swift успешно проверить тип оператора.
Теперь предположим, что мы разбили его на три оператора:
let p1 = Just(Data())
.compactMap { UIImage(data: $0) }
.receive(on: DispatchQueue.main)
let a1 = Subscribers.Assign(object: self.iv, keyPath: \.image)
p1.subscribe(a1)
Swift сначала проверяет тип оператора let p1
. Ему не нужно продвигать тип замыкания, поэтому он может вывести тип Output
типа UIImage
.
Затем Swift проверяет тип оператора let a1
. Он должен неявно развернуть iv
, но нет никакой необходимости в Optional
повышении. Он выводит тип Input
как UIImage?
, потому что это тип Value
пути к ключу.
Наконец, Swift пытается проверить тип оператора subscribe
. Тип Output
p1
равен UIImage
, а тип Input
a1
равен UIImage?
. Они разные, поэтому Swift не может успешно проверить оператор. Swift не поддерживает продвижение Optional
обобщенных параметров типа c, таких как Input
и Output
. Так что это не компилируется.
Мы можем выполнить эту проверку типов, установив для Output
тип p1
значение UIImage?
:
let p1: AnyPublisher<UIImage?, Never> = Just(Data())
.compactMap { UIImage(data: $0) }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
let a1 = Subscribers.Assign(object: self.iv, keyPath: \.image)
p1.subscribe(a1)
Здесь мы принудительно Быстро продвигать тип закрытия. Я использовал eraseToAnyPublisher
, потому что иначе тип p1
слишком уродлив, чтобы его можно было разобрать.
Поскольку Subscribers.Assign.init
опубликован c, мы также можем использовать его напрямую, чтобы Swift выводил все типы:
let p2 = Just(Data())
.compactMap { UIImage(data: $0) }
.receive(on: DispatchQueue.main)
.subscribe(Subscribers.Assign(object: self.iv, keyPath: \.image))
Swift проверяет тип успешно. По сути, это то же самое, что и оператор, который использовал .assign
ранее. Обратите внимание, что он выводит тип ()
для p2
, потому что это то, что .subscribe
возвращает здесь.
Теперь вернемся к назначению на основе ключа:
class Thing {
var iv: UIImageView! = UIImageView()
func test() {
let im = UIImage()
let kp = \UIImageView.image
self.iv[keyPath: kp] = im
}
}
This не компилируется, с ошибкой value of optional type 'UIImage?' must be unwrapped to a value of type 'UIImage'
. Я не знаю, почему Свифт не может скомпилировать это. Он компилируется, если мы явно преобразуем im
в UIImage?
:
class Thing {
var iv: UIImageView! = UIImageView()
func test() {
let im = UIImage()
let kp = \UIImageView.image
self.iv[keyPath: kp] = .some(im)
}
}
Он также компилируется, если мы изменим тип iv
на UIImageView?
и добавим необязательное присваивание:
class Thing {
var iv: UIImageView? = UIImageView()
func test() {
let im = UIImage()
let kp = \UIImageView.image
self.iv?[keyPath: kp] = im
}
}
Но он не компилируется, если мы просто принудительно распаковываем необязательно развернутый необязательный параметр:
class Thing {
var iv: UIImageView! = UIImageView()
func test() {
let im = UIImage()
let kp = \UIImageView.image
self.iv![keyPath: kp] = im
}
}
И он не компилируется, если мы просто опционально присваиваем присваивание:
class Thing {
var iv: UIImageView! = UIImageView()
func test() {
let im = UIImage()
let kp = \UIImageView.image
self.iv?[keyPath: kp] = im
}
}
Я думаю, что это может быть ошибка в компиляторе.