27

I was wondering if I could do a hidden adoption of a protocol in swift.

In Objective-C, I would use a private header in the m.-file to hide protocols that I don't want to be exposed to the outside. Is something like that possible in swift? Putting a "private" before the protocol does not work, apparently :)

kutschenator
  • 902
  • 10
  • 26

2 Answers2

10

Edit: Found a simple way to hide it. At least from the xcode quick help information: Just put the adoption and implementation in an extension of you class and it will not show up there.

Original Answer:

I came up with this example, inspired by inner classes in Java. Here MyVC does not expose that it implements UICollectionViewDelegatefor internal purposes, while the delegate implementation has access to MyVC's private variables.

public class MyVC: UIViewController {

    private let a = 4 as Int
    private var hiddenDelegate: HiddenDelegateImpl?

    public override func viewDidLoad() {
        super.viewDidLoad()
        hiddenDelegate = HiddenDelegateImpl(outer: self)
        innerCollectionView.delegate = hiddenDelegate
    }
}

private class HiddenDelegateImpl: NSObject, UICollectionViewDelegate {

    private weak var outer: MyVC?

    init(outer: MyVC) {
        self.outer = outer
        super.init()
    }

    private func doStuff() -> Int {
        // can access private variables of outer class
        return outer?.a
    }

    // implement delegate methods here
}

Note that HiddenDelegateImpl could also be an inner class of MyVC, I chose to put it outside for readability.

In contrast to Java instances of inner classes need an instance of the outer class to exists. Since this is not the case with Swift we need to have the outer workaround.

There is also this nice example which focuses on delegate implementation.

Edit: Made the delegate an instance variable in the outer class to retain it and the reference to the outer class weak to avoid retain cycles.

Community
  • 1
  • 1
kutschenator
  • 902
  • 10
  • 26
  • 2
    Good question. There is a real need for this. Outside users of your class don't need to know what protocols it needs internally. Shame the solution is so inelegant (a problem of the language, you have given the best answer, I think.) – TJez Feb 01 '15 at 10:35
  • Thanks for the appreciation :) the question was downvoted at first so I thought I was being stupid. But you are totally right, I dont want the user of my class to know that it implements 10 different protocols internally to do its work – kutschenator Feb 02 '15 at 22:39
  • I would guess one reason is that they want to move away from delegates all together and want you to use closures. But with all the old APIs floating around that is just not possible – kutschenator Feb 02 '15 at 22:41
  • Your edited solution using an extension **does not hide** the protocol implementation. The protocol implementation is always visible to everybody. See also: https://forums.developer.apple.com/thread/13103 – Lars Blumberg Dec 30 '15 at 07:57
  • Like I said: it hides it from the QuickHelp in xcode which is good enough for me in most cases. – kutschenator Dec 30 '15 at 17:17
  • This is a way to do it, but isn't it just composition? It's not really private protocol conformance. But we all agree that that's not possible as per language "limitation(?)". :) – Nuno Gonçalves Jan 30 '17 at 22:47
-1

Maybe the language has changed since the post, but it works for me. This is an example of where I wanted to hide an initializer to control the lifetime of an object and perform post processing. In this case, I wanted to track and send analytics, based on the caller's configuration.

private protocol Reportable {
    init()
    var people:[String:AnyObject] { get }
    var track:[String:AnyObject] { get }
}


public class Analytics {

    public final class Alpha: Reportable {

        var thingOne: String?
        var thingTwo: String?

        private init() {}
        private var people:[String:AnyObject] { return [:] }
        private var track:[String:AnyObject] { return [:] }
    }

    public final class Bravo: Reportable {

        var thingOne: String?
        var thingTwo: String?

        private init() {}
        private var people:[String:AnyObject] { return [:] }
        private var track:[String:AnyObject] { return [:] }
    }


    public static func alpha(configure:Alpha -> ()) {
        return report(configure)
    }

    public static func bravo(configure:Bravo -> ()) {
        return report(configure)
    }


    private static func report<T:Reportable>(configure:T -> ()) {
        let event = T()
        configure(event)
        Analytics.doSomething()
    }


    static func doSomething() {

    }
}

// separate file
func clientCode() {

    Analytics.alpha { event in

        event.track // error

        event.thingOne = "foo"
        event.thingTwo = "bar"  }

    Analytics.bravo { event in
        event.thingOne = "foo"
        event.thingTwo = "bar"  }

}
Chris Conover
  • 8,889
  • 5
  • 52
  • 68