问题
I assume that the answer to this question will address issues with Objective-C protocols in general, but this is the first problem of this type that I've come across.
I expect for these methods to be used, when implementing 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!)!
}
}
However, that won't compile. I have to implement this nonsense to make things work. Can you tell me why? Is a code-lighter solution available?
// 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:
When you have a problem like this, where you're wondering about the limits of the Swift language itself, it helps to reduce it to a simpler version of the problem.
First, let's ask: is it possible to extend a protocol-adopting protocol as a way of injecting default implementations of that protocol's requirements into an ultimate adopting class? Yes, it is; this code is legal:
protocol Speaker {
func speak()
}
protocol DefaultSpeaker : Speaker {
}
extension DefaultSpeaker {
func speak() {
print("howdy")
}
}
class Adopter : DefaultSpeaker {
}
Okay, so what else does your code do? Well, it also injects an additional requirement (the instance variable). Is that legal? Yes, it is. This code is legal too:
protocol Speaker {
func speak()
}
protocol DefaultSpeaker : Speaker {
var whatToSay : String {get}
}
extension DefaultSpeaker {
func speak() {
print(self.whatToSay)
}
}
class Adopter : DefaultSpeaker {
var whatToSay = "howdy"
}
So what is that Swift doesn't like? What haven't we done here, that your code does? It's the fact that the original protocol is @objc
. If we change protocol Speaker
to @objc protocol Speaker
(and make all other necessary changes), the code stops compiling:
@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"
}
I'm going to guess that this is because Objective-C knows nothing about protocol extensions. Since our implementation of the required protocol methods depends upon the protocol extension, we cannot adopt the protocol in a way that satisfies the compiler that the requirement has been satisfied from Objective-C's point of view. We have to implement the requirements right there in the class, where Objective-C can see our implementation (which is exactly what your solution does):
@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()
}
}
So, I conclude that your solution is as good as it gets.
What you're doing is actually more like this, where we use an extension on the adopter class to inject the "hook" methods:
@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()
}
}
That works because that last extension
is something Objective-C can see: an extension on an Objective-C class is effectively a category, which Objective-C understands.
来源:https://stackoverflow.com/questions/31663560/how-can-you-provide-default-implementations-for-uipageviewcontrollerdatasource