Подтвердить что ты не робот

Xcode Forcing Swift Опционально Unwraps Twice (!!)

Я подклассифицирую UIStoryboardSegue, и каждый раз, когда я пытаюсь использовать один из двух UIViews, Xcode делает мне добавление двух необязательных разворот (!!), таких как:

let sourceView = self.sourceViewController.view
sourceView!!.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight

или

let sourceView = self.sourceViewController.view!
sourceView!.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight

или

self.sourceViewController.view!!.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight

Мне интересно, может ли кто-нибудь объяснить, почему это так.

4b9b3361

Ответ 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 и не должны его вообще разворачивать.

Ответ 2

self.sourceViewController.view - это редкий случай "двойного обертывания":

  • один раз, поскольку UIStoryboardSegue sourceViewController вводится как AnyObject, к которому может быть отправлено сообщение view, потому что проверка типов подавлена, но за счет возврата Необязательного (поскольку я объясняю мой книга: http://www.apeth.com/swiftBook/ch04.html#SECsuppressing)

  • и снова, потому что UIViewController может иметь или не иметь представление - действительно, UIViewController view изначально nil, пока не будет загружено представление и viewDidLoad, поэтому свойство view сам набрал как необязательный

Таким образом, мы завершаем опцию, завернутую в Необязательный. Что я делаю в этой ситуации, пишу это так в Swift 1.1 или раньше:

let sourceView = (self.sourceViewController as UIViewController).view

Или с as! в Swift 1.2:

let sourceView = (self.sourceViewController as! UIViewController).view

Уныние решает все сомнения сразу. Теперь настройка sourceView.frame просто работает.

Ответ 3

Потому что

self.sourceViewController.view

возвращает вид UIView?! Вы должны разоружиться дважды, чтобы получить UIView. Первый неодобритель !, затем unarp ?

    let test:UIView?! = nil
    let firstunwrap = test! //Here kind of firstwrap is UIView?,so need second wrap
    let seconunwrap = firstunwrap!

Если у вас есть UIView!?,

вам нужно только разоружить один раз. Потому что ! есть implicitly unwrapped optional

    let test:UIView!? = nil
    let firstunwrap = test!//Here firstwrap is UIView!,do not need to wrap anymore