如何为UIPageViewControllerDataSource提供默认实现?

Jes*_*ssy 3 protocols default-implementation uipageviewcontroller swift

我认为这个问题的答案一般会解决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!)!
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,这不会编译.我必须实现这个废话以使事情有效.你能告诉我为什么吗?是否可以使用代码更轻的解决方案?

// 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)
    }
}
Run Code Online (Sandbox Code Playgroud)

mat*_*att 5

当你遇到这样的问题时,你想知道Swift语言本身的局限性,它有助于将它简化为更简单的问题版本.

首先,让我们问:是否有可能扩展采用协议的协议,作为将该协议的需求的默认实现注入最终采用类的方法?是的; 这段代码是合法的:

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

}
Run Code Online (Sandbox Code Playgroud)

好的,那你的代码还有什么用呢?好吧,它还注入了额外的要求(实例变量).这合法吗?是的.这段代码也是合法的:

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

那么斯威夫特不喜欢什么?我们在这里做了什么,你的代码呢?这是原始协议的事实@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"
}
Run Code Online (Sandbox Code Playgroud)

我猜这是因为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()
    }
}
Run Code Online (Sandbox Code Playgroud)

所以,我的结论是你的解决方案一样好.

你正在做的事实上更像是这样,我们在使用者类上使用扩展来注入"钩子"方法:

@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()
    }
}
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为最后extension是Objective-C 可以看到的东西:Objective-C类的扩展实际上是Objective-C理解的类别.

  • 我同意你的雷达,并建议这样做.你有一个很好的用例,在我看来,无论将Swift API暴露给Objective-C是什么,都可以通过协议扩展告诉Objective-C关于进入类的东西的更多信息. .但是,我怀疑他们可以解决这个问题,因为这里涉及奇怪的调度规则.不过,我们总能希望! (2认同)