This has been discussed in the past, with no particular conclusion, as far as I can tell. But I think the issue is ill-defined.
Imagine that we got a typeprivate
access modifier that could allow for something like this:
// in file Foo1.swift
struct Foo {
typeprivate var data: Data
}
extension Foo {
mutating func frobulate() {
// modify the `data` property
}
}
// in file Foo2.swift
extension Foo {
mutating func frobnicate() {
// modify the `data` property is allowed even if I'm in a separate file, because the access is `typeprivate`
}
}
This will effectively make the data
property internal
, even if it doesn't look like it. That's because in any file within the module I could write something like this:
// in file SomewhereElseFarAwayInTheCodebase.swift
extension Foo {
var internalDataProxy: Data {
get {
self.data
}
set {
// this is fine because I'm in an extension of `Foo` and `data` is `typeprivate`
self.data = newValue
}
}
}
struct DataBypassCaller {
func mutateDataOn(foo: inout Foo) {
let someNewData: Data = ...
// the effect of this is like modifying the `data` property directly
foo.internalDataProxy = someNewData
}
}
Due to the fact that we can extend types in Swift, the file-level access control becomes very important.
Please note that, in Swift, this issue cannot even solved by classes or OOP, not even with a protected
kind of access control: as long as you can access some private
member in some way in some other file, you will always be able to create "proxy" members that break the encapsulation.
My suggestion is to simply put everything in the same file, and use organization tools like // MARK: -
comments to highlight the specific sections. Alternatively, just use internal
and make sure to have practices in place in your team to spot code that breaks invariants and/or encapsulation in code reviews. Finally, you can instead create a module (a SPM library target) that contains the files that share the data
property, that would be declared as internal
to the module: this way the property would not be public
, thus, not visible from outside, but still accessible by the other files within the module.
Another thing that could help is a new access modifier that's not currently in Swift, but it could be introduced in the future, given that some people in the forums (myself included) have expressed interest in it, that is, folderprivate
. If we could limit the visibility of a declaration to the files in a particular folder, we could break down large files into smaller ones, as long as they are in the same folder.
So, the example would change like the following:
// in file Foo1.swift
struct Foo {
folderprivate var data: Data
}
extension Foo {
mutating func frobulate() {
// modify the `data` property
}
}
// in file Foo2.swift, in the same folder
extension Foo {
mutating func frobnicate() {
// modify the `data` property is allowed even if I'm in a separate file, because the access is `folderprivate`
}
}
// in file SomewhereElseFarAwayInTheCodebase.swift, not in the same folder
extension Foo {
var internalDataProxy: Data {
get {
self.data
}
set {
// this will not compile, because the file is not in the same folder
self.data = newValue
}
}
}
Even in this case, it would technically be possible to break encapsulation by sneaking in a new file in the folder, but this could be much more easily controlled, for example having specific owners defined for the files in that particular folder, that would be asked to approve any change in its files.