Вопрос: Необязательное связывание завершается успешно, если оно не должно


Это то, что я опубликовал как возможное решение для Иерархия контроллера трассировки в Swift  (слегка измененный):

extension UIViewController {

    func traverseAndFindClass<T where T : UIViewController>(T.Type) -> T? {
        var currentVC = self
        while let parentVC = currentVC.parentViewController {
            println("comparing \(parentVC) to \(T.description())")
            if let result = parentVC as? T { // (XXX)
                return result
            }
            currentVC = parentVC
        }
        return nil
    }
}

Метод должен пройти иерархию контроллера родительского представления и вернуть первый экземпляр данного класса, или nil, если ни один не найден.

Но это не работает, и я не могу понять, почему. Дополнительная привязка отмечены (XXX)  всегда удается , так что возвращается первый родительский контроллер представления даже если это не случай T,

Это можно легко воспроизвести: Создайте проект из «Приложения с основными приложениями iOS», шаблон в Xcode 6 GM и добавьте следующий код в viewDidLoad() из MasterViewController класс:

if let vc = self.traverseAndFindClass(UICollectionViewController.self) {
    println("found: \(vc)")
} else {
    println("not found")
}

self это MasterViewController (подкласс UITableViewController), и это контроллер родительского представления - это UINavigationController, Здесь нет UICollectionViewController в родительском представлении иерархии контроллеров, поэтому я ожидаю, что метод возвращается nil и выход «не найден».

Но вот что происходит:

comparing <UINavigationController: 0x7fbc00c4de10> to UICollectionViewController
found: <UINavigationController: 0x7fbc00c4de10>

Это, очевидно, неправильно, потому что UINavigationController не является подклассом UICollectionViewController, Возможно, я сделал какую-то глупую ошибку, но я не мог ее найти.


Чтобы изолировать проблему, я также попытался воспроизвести ее со своим собственным классом иерархия, независимо от UIKit:

class BaseClass : NSObject {
    var parentViewController : BaseClass?
}

class FirstSubClass : BaseClass { }

class SecondSubClass : BaseClass { }

extension BaseClass {

    func traverseAndFindClass<T where T : BaseClass>(T.Type) -> T? {
        var currentVC = self
        while let parentVC = currentVC.parentViewController {
            println("comparing \(parentVC) to \(T.description())")
            if let result = parentVC as? T { // (XXX)
                return result
            }
            currentVC = parentVC
        }
        return nil
    }
}

let base = BaseClass()
base.parentViewController = FirstSubClass()

if let result = base.traverseAndFindClass(SecondSubClass.self) {
    println("found: \(result)")
} else {
    println("not found")
}

И угадай что? Теперь он работает так, как ожидалось! Выход

comparing <MyApp.FirstSubClass: 0x7fff38f78c40> to MyApp.SecondSubClass
not found

ОБНОВИТЬ:  

  • Удаление ограничения типа в общем методе

    func traverseAndFindClass<T>(T.Type) -> T?
    

    как предложено @POB в комментарии, заставляет его работать должным образом.

  • Замена необязательной привязки с помощью двухступенчатой ​​привязки

    if let result = parentVC as Any as? T { // (XXX)
    

    как предложил @vacawama в его ответе, также заставляет его работать, как ожидалось.

  • Изменение конфигурации сборки с «Отладки» до «Отпуска» также делает метод работает должным образом. (Я тестировал это только в iOS Simulator до сих пор.)

Последнее указывает на то, что это компилятор Swift или ошибка времени выполнения. И я все еще не может понять, почему проблема возникает с подклассами UIViewController, но нет с подклассами моего BaseClass, Поэтому я буду держать вопрос открытым для прежде чем принимать ответ.


ОБНОВЛЕНИЕ 2:  Это было исправлено с Xcode 7 ,

С окончательным Xcode 7 освободить проблему больше не происходит. Дополнительная привязка if let result = parentVC as? T в traverseAndFindClass() метод теперь работает (и не работает), как и ожидалось, как в конфигурации Release, так и Debug.


8


источник


Ответы:


Если вы попытаетесь условно отбросить объект типа UINavigationController к UICollectionViewController на детской площадке:

var nc = UINavigationController()

if let vc = nc as? UICollectionViewController {
    println("Yes")
} else {
    println("No")
}

Вы получите эту ошибку:

Выполнение игровой площадки не выполнено:: 33: 16: ошибка: «UICollectionViewController» не является подтипом «UINavigationController» если пусть vc = nc как? UICollectionViewController {

но если вы это сделаете:

var nc = UINavigationController()

if let vc = (nc as Any) as? UICollectionViewController {
    println("Yes")
} else {
    println("No")
}

он печатает «Нет».

Поэтому я предлагаю попробовать:

extension UIViewController {

    func traverseAndFindClass<T where T : UIViewController>(T.Type) -> T? {
        var currentVC = self
        while let parentVC = currentVC.parentViewController {
            println("comparing \(parentVC) to \(T.description())")
            if let result = (parentVC as Any) as? T { // (XXX)
                return result
            }
            currentVC = parentVC
        }
        return nil
    }
}

3