Ответ 1
Свойство sourceViewController
UIStoryboardSegue
вводится как AnyObject
, а в качестве функции совместимости Objective-C, как только вы import Foundation
, ситуация становится немного странной с AnyObject
.
Вместо поиска методов типа AnyObject
Swift ищет вместо селекторов Objective-C точно так же, как Objective-C делает с типом id
. Любой селектор из любого класса является честной игрой: если вы захотите, вы можете попытаться вызвать activeProcessorCount
на свой объект, даже если это селектор NSProcessInfo
, и компилятор позволит вам это сделать. (По очевидным причинам он потерпит неудачу во время выполнения.) Это называется динамической отправкой, в отличие от статической отправки (обычный механизм вызова в Swift).
Одна вещь о динамической диспетчеризации заключается в том, что она всегда добавляет слой неявной упаковки. Если у вас есть свойство Objective-C, которое возвращает String
, динамическая отправка приведет к возврату String!
.
Вещи становятся волосатыми, когда несколько классов объявляют селекторы с тем же именем, но разные типы возвращаемых значений (или с разными параметрами, но здесь нас здесь не интересует). Я не знаю, как компилятор выбирает какой селектор из многих одинаково названных им, о которых он знает. В любом случае, он выбирает один, и вы застряли в нем, если не набросаете объект на более точный тип, и в этом случае компилятор Swift позволит вам использовать статическую отправку.
Используя аргумент swiftc
-dump-ast
, мы можем увидеть представление lisp, как компилятор проанализировал выражение:
(pattern_binding_decl
(pattern_named type='UIView?!' 'sourceView')
(dynamic_member_ref_expr type='UIView?!' location=MySegue.swift:15:46 range=[MySegue.swift:15:25 - line:15:46] decl=UIKit.(file).UIGestureRecognizer.view
(member_ref_expr type='AnyObject' location=MySegue.swift:15:25 range=[MySegue.swift:15:25 - line:15:25] decl=UIKit.(file).UIStoryboardSegue.sourceViewController
(derived_to_base_expr implicit type='UIStoryboardSegue' location=MySegue.swift:15:20 range=[MySegue.swift:15:20 - line:15:20]
(declref_expr type='Segue' location=MySegue.swift:15:20 range=[MySegue.swift:15:20 - line:15:20] decl=xxx.(file).Segue.func [email protected]:14:7 specialized=no)))))
Там много крутизны, но вы можете видеть, что он создал dynamic_member_ref_expr
вместо member_ref_expr
. Если вы прокрутите весь путь вправо до атрибута decl
", вы увидите, что оно использует свойство UIGestureRecognizer
view
( объявлено как UIView?
), так что выражение возвращает a UIView?!
.
Напротив, свойство UIViewController
view
объявляется как UIView!
. Если бы этот компилятор выбрал этот селектор, вы бы закончили с UIView!!
, и вам понадобится только один уровень явного разворота.
Вы можете (и должны, в таких случаях, как этот) явно разворачивать неявно-разворачиваемые значения. При sourceView
как UIView?!
первый !
разворачивает UIView?!
в UIView?
, а второй разворачивает UIView?
в окончательно пригодный UIView
. Вот почему вам нужны два восклицательных знака.
Класс, объявляющий селектор, используемый для динамической отправки, не имеет значения, если целевой объект реализует его, принимает совместимые аргументы и возвращает совместимый тип. UIView?
и UIView!
совместимы на двоичном уровне, поэтому в конце ваша программа все еще работает. Однако, если вы как-то ожидали String
или другой несвязанный тип, вы могли бы стать неожиданностью. На мой взгляд, вы должны избегать динамической отправки, сколько сможете.
tl; dr: если вы отбрасываете sourceViewController
в UIViewController
, вы получаете правильное определение свойства view
и не должны его вообще разворачивать.