3

I've created an abstract base class-like structure in Swift, using protocol extensions, as per this answer. This is a simplified example:

protocol AbstractBase {
    var _constant: Int { get }
    func _operation(_ val: Int) -> Int
}

public class ConcreteSub: AbstractBase {
    let _constant: Int = 42
    func _operation(_ val: Int) -> Int {
        return val + 2
    }
}

extension AbstractBase {
    func mainOperation(_ val: Int) -> Int {
        return _operation(val + _constant)
    }
}

So basically, ConcreteSub provides the implementation details needed by AbstractBase, namely _constant and _operation.

I would like to hide those details from clients, and only expose mainOperation. However, Swift does not allow me to make the members fileprivate on the protocol -- if I do the following

protocol AbstractBase {
    fileprivate var _constant: Int { get }
    // etc

I get "error: 'fileprivate' modifier cannot be used in protocols".

Nor can I apply the modifier on the subclass -- when I try

public class ConcreteSub: AbstractBase {
    fileprivate let _constant: Int = 42
    // etc

I get "error: property '_constant' must be declared internal because it matches a requirement in internal protocol 'AbstractBase'".

Lastly, when I make the whole protocol fileprivate, I get no compile errors, but I consistently run into Linking errors, which I guess is because the protocol is private, but the subclass is public.

Is there another way I'm missing?

Community
  • 1
  • 1
Phlippie Bosman
  • 5,378
  • 3
  • 26
  • 29

2 Answers2

1

When I need an abstract base with some properties/functions hidden I use class with some additional fatalErrors and asserts to crash whenever someone is trying to use Base instead of implementation.

public class AbstractBase {
    init() {
        assert(type(of: self) != AbstractBase.self, "Abstract class")
    }

    fileprivate var _constant: Int {
        fatalError("Abstract class")
    }
    fileprivate func _operation(_ val: Int) -> Int {
        fatalError("Abstract class")
    }

    func mainOperation(_ val: Int) -> Int {
        return _operation(val + _constant)
    }
}

public class ConcreteSub: AbstractBase {

    fileprivate override var _constant: Int {
        return 42
    }
    fileprivate override func _operation(_ val: Int) -> Int {
        return val + 2
    }
}
Tomasz Bąk
  • 6,124
  • 3
  • 34
  • 48
  • 2
    This is good, but please also add `@available(*, unavailable)`. https://mokacoding.com/blog/swift-unavailable-how-to/ –  Mar 05 '21 at 20:59
0

I actually just ran into this issue. As of Swift 5.1, you can do this instead:

protocol MyProtocol {
    var someVisibleVar: String { get }
    func someVisibleFunc()
}

fileprivate extension MyProtocol {
    var someFilePrivateVar: String {
        "whatever"
    }

    func someFilePrivateFunc() {
        print("someFilePrivateFunc() was called with \(someVisibleVar)")
    }
}

class SomeClass: MyProtocol {
    var someVisibleVar: String { "whatever" }

    func someVisibleFunc() {
        if someFilePrivateVar == someVisibleVar {
            someFilePrivateFunc()
        }
    }
}

class SomeOtherClass: MyProtocol {
    var someVisibleVar: String { "something else" }

    func someVisibleFunc() {
        if someFilePrivateVar == someVisibleVar {
            someFilePrivateFunc()
        }
    }
}
Ryan
  • 4,425
  • 2
  • 23
  • 12