【问题标题】:How can you provide default implementations for UIPageViewControllerDataSource?如何为 UIPageViewControllerDataSource 提供默认实现?
【发布时间】:2023-03-15 18:37:01
【问题描述】:

我认为这个问题的答案将解决 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)
    }
}

【问题讨论】:

  • 我的问题其实是页面顶部可以阅读的问题。这是具体的,尽管正如我所说,答案可能适用于更多的问题。我相信我很清楚什么可以编译,什么不可以编译。
  • 我在“第一部分”前面加上了“实施时”。这意味着,“当在类型名称后加一个冒号表示它符合协议时”。对于 UIPageViewControllerDataSource,编译器会告诉您没有发生一致性。回答这个问题需要有人已经知道,或者通过将我的代码复制到项目中并使用它来发现。

标签: swift protocols uipageviewcontroller default-implementation


【解决方案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 可以理解。

【讨论】:

  • 我提交了一个带有视频教程的雷达。虽然你不能直接在 Objetive-C 中使用我要使用的 ?,但它可以编译,我认为这是最清晰的语法。当我收到 Apple 的回复时,我要么将此标记为答案,要么创建一个新答案。
  • 我同意你的看法,并打算这样做。你有一个很好的用例,在我看来,无论是什么将 Swift API 暴露给 Objective-C 都可以更努力地告诉 Objective-C 通过协议扩展进入类的东西.但是,我怀疑他们能否解决这个问题,因为这里涉及到奇怪的调度规则。不过,我们总有希望!
猜你喜欢
  • 2011-05-18
  • 2014-07-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多