Search code examples
swiftprotocols

Implement a public method that rely on a private property in Swift using protocols


I'm building an API that uses ResultBuilder with structs as components and chaining methods as modifiers. Sometimes different components have the same modifier, e.g.:

var resultBuilderContent = {
    Component1()
       .modifier(x: 1)
    Component2()
       .modifier(x: 2)
}

I'd like to implement 'modifier' method in a protocol to avoid duplicating the code. But the method relies on internal properties and if I implement it like this:

protocol SameModifierProtocol{
    var x: Int { get set }
}
extension SameModifierProtocol{
    public func modifier(x: Int)->SameModifierProtocol{
        var s = self
        s.x = x
        return s
    }
}
public struct Component: SameModifierProtocol{
    var x: Int = 0
    public init(){}
}
// in another module
let c = Component().modifier(x: 1)

I get the error: "'modifier' is inaccessible due to 'internal' protection level".

If I try to differentiate access levels between two protocols like this:

protocol SameModifierProtocol{
    var x: Int { get set }
}

public protocol ReceivingSameModifier{
}
extension ReceivingSameModifier where Self: SameModifierProtocol{
    public func modifier(x: Int)->ReceivingSameModifier{
        var s = self
        s.x = x
        return s
    }
}

I get the following error: "Cannot declare a public instance method in an extension with internal requirements".

This is where I stuck. What are my options?


Solution

  • Following this discussion from Swift forum, there are two ways to solve the issue.

    First one is straightforward:

    /// The publically visible capabilities.
    public protocol SameModifierProtocol {
      func modifier(x: Int) -> SameModifierProtocol
    }
    /// The internal requirements on which the default implementation relies.
    internal protocol SynthesizedSameModifierProtocolConformance:
      SameModifierProtocol {
        var x: Int { get set }
    }
    /// The default implementation.
    extension SynthesizedSameModifierProtocolConformance {
        public func modifier(x: Int) -> SameModifierProtocol{
            var s = self
            s.x = x
            return s
        }
    }
    
    /// Conforms to the public protocol
    /// and requests the default implementation from the internal one.
    /// Clients can only see the public protocol.
    public struct Component: SynthesizedSameModifierProtocolConformance {
        internal var x: Int = 0
        public init() {}
    }
    

    The second involves an unofficial feature @_spi, that allows to make implementation details unexposed in a public protocol:

    public protocol SameModifierProtocol {
        @_spi(SameModifier) var x: Int { get set }
    }
    
    extension SameModifierProtocol {
        public func modifier(x: Int) -> Self {
            var s = self
            s.x = x
            return s
        }
    }
    
    public struct Component: SameModifierProtocol {
        @_spi(SameModifier) public var x: Int = 0
        public init() {}
    }