Как вы можете предоставить реализации по умолчанию для UIPageViewControllerDataSource?

Я предполагаю, что ответ на этот вопрос будет касаться проблем с протоколами Objective-C в целом, но это первая проблема такого типа, с которой я столкнулся.

Я ожидаю, что эти методы будут использоваться при реализации UIPageViewControllerDataSourceWithConnections.

import UIKit

protocol UIPageViewControllerDataSourceWithConnections: UIPageViewControllerDataSource {
    var connectedViewControllers: [UIViewController] {get}
}

extension UIPageViewControllerDataSourceWithConnections {
    func pageViewController(pageViewController: UIPageViewController,
        viewControllerBeforeViewController viewController: UIViewController
    ) -> UIViewController? {return connectedViewController(
        current: viewController,
        adjustIndex: -
    )}

    func pageViewController(pageViewController: UIPageViewController,
        viewControllerAfterViewController viewController: UIViewController
    ) -> UIViewController? {return connectedViewController(
        current: viewController,
        adjustIndex: +
    )}

    private func connectedViewController(
        current viewController: UIViewController,
        adjustIndex: (Int, Int) -> Int
    ) -> UIViewController? {
        let requestedIndex = adjustIndex(connectedViewControllers.indexOf(viewController)!, 1)
        return connectedViewControllers.indices.contains(requestedIndex) ?
            connectedViewControllers[requestedIndex] : nil
    }

    func presentationCountForPageViewController(pageViewController: UIPageViewController)
    -> Int {return connectedViewControllers.count}

    func presentationIndexForPageViewController(pageViewController: UIPageViewController)
    -> Int {
        return connectedViewControllers.indexOf(pageViewController.viewControllers!.first!)!
    }
}

Однако это не скомпилируется. Я должен реализовать эту чушь, чтобы все заработало. Ты можешь сказать мне, почему? Доступно ли решение для облегчения кода?

// connectedViewControllers is defined elsewhere in InstructionsPageViewController.
extension InstructionsPageViewController: UIPageViewControllerDataSourceWithConnections {

    // (self as UIPageViewControllerDataSourceWithConnections) doesn't work.
    // Workaround: use a different method name in the protocol

    func pageViewController(pageViewController: UIPageViewController,
        viewControllerBeforeViewController viewController: UIViewController
    ) -> UIViewController? {
        return pageViewController(pageViewController,
            viewControllerBeforeViewController: viewController
        )
    }

    func pageViewController(pageViewController: UIPageViewController,
        viewControllerAfterViewController viewController: UIViewController
    ) -> UIViewController? {
        return pageViewController(pageViewController,
            viewControllerAfterViewController: viewController
        )
    }


    // (self as UIPageViewControllerDataSourceWithConnections)
    // works for the optional methods.

    func presentationCountForPageViewController(pageViewController: UIPageViewController)
    -> Int {
        return (self as UIPageViewControllerDataSourceWithConnections)
            .presentationCountForPageViewController(pageViewController)
    }

    func presentationIndexForPageViewController(pageViewController: UIPageViewController)
    -> Int {
        return (self as UIPageViewControllerDataSourceWithConnections)
            .presentationIndexForPageViewController(pageViewController)
    }
}

1 ответ

Когда у вас возникает такая проблема, когда вы задаетесь вопросом об ограничениях самого языка Swift, это помогает уменьшить его до более простой версии проблемы.

Во-первых, давайте спросим: возможно ли расширить протокол, принимающий протокол, как способ внедрения реализаций по умолчанию требований этого протокола в окончательный класс принятия? Да, это; этот код является законным:

protocol Speaker {
    func speak()
}
protocol DefaultSpeaker : Speaker {
}
extension DefaultSpeaker {
    func speak() {
        print("howdy")
    }
}
class Adopter : DefaultSpeaker {

}

Итак, что еще делает ваш код? Ну, это также вводит дополнительное требование (переменная экземпляра). Это законно? Да, это. Этот код также является законным:

protocol Speaker {
    func speak()
}
protocol DefaultSpeaker : Speaker {
    var whatToSay : String {get}
}
extension DefaultSpeaker {
    func speak() {
        print(self.whatToSay)
    }
}
class Adopter : DefaultSpeaker {
    var whatToSay = "howdy"
}

Так чем же не нравится этот Swift? Что мы не сделали здесь, что делает ваш код? Это факт, что оригинальный протокол @objc, Если мы изменим protocol Speaker в @objc protocol Speaker (и внесите все другие необходимые изменения), код перестает компилироваться:

@objc protocol Speaker {
    func speak()
}
@objc protocol DefaultSpeaker : Speaker {
    var whatToSay : String {get}
}
extension DefaultSpeaker {
    func speak() {
        print(self.whatToSay)
    }
}
class Adopter : NSObject, DefaultSpeaker { // ERROR
    var whatToSay = "howdy"
}

Я собираюсь догадаться, что это потому, что Objective-C ничего не знает о расширениях протокола. Поскольку наша реализация требуемых методов протокола зависит от расширения протокола, мы не можем принять протокол таким образом, чтобы он удовлетворял компилятору, чтобы требование было выполнено с точки зрения Objective-C. Мы должны реализовать требования прямо в классе, где Objective-C может увидеть нашу реализацию (именно это и делает ваше решение):

@objc protocol Speaker {
    func speak()
}
@objc protocol DefaultSpeaker : Speaker {
    var whatToSay : String {get}
}
extension DefaultSpeaker {
    func speak2() {
        print(self.whatToSay)
    }
}
class Adopter : NSObject, DefaultSpeaker {
    var whatToSay = "howdy"
    func speak() {
        self.speak2()
    }
}

Итак, я заключаю, что ваше решение так же хорошо, как оно есть.

То, что вы делаете, на самом деле больше похоже на это, где мы используем расширение для класса приемника, чтобы внедрить методы "ловушки":

@objc protocol Speaker {
    func speak()
}
@objc protocol DefaultSpeaker : Speaker {
    var whatToSay : String {get}
}
extension DefaultSpeaker {
    func speak2() {
        print(self.whatToSay)
    }
}
class Adopter : NSObject {
}
extension Adopter : DefaultSpeaker {
    var whatToSay : String { return "howdy" }
    func speak() {
        self.speak2()
    }
}

Это работает, потому что это в прошлом extension это то, что может видеть Objective-C: расширение класса Objective-C фактически является категорией, которую понимает Objective-C.

Другие вопросы по тегам