Swift wishlist: change private access control
Sorry Doug.
There are few aspects of Swift more flame-inducing than its notion of access control (PATs are the undisputed king).
It has been debated and debated and changed multiple times. So much that Doug Gregor famously quipped after the last accepted change:
And with this change, let us never speak of access control again.
Ehm…
I like that change, brought in Swift 4, where private
now means that you can use it across both original scope and its extensions. From SE-0169 proposal:
This proposal recommends extending private access control so that members defined in an extension of a type have the same access as members defined on the type itself, so long as the type and extension are in the same source file.
What I don’t like is the last part, which I highlighted. This unnecessarily limits the developer to organize files as they see proper.
When dealing with big-ish classes, I tend to extend them over many different files. Sometimes the class handles stuff which share some common functionality but then have methods upon methods which don’t have any connections between them.
Thus I split them up into various files: core class declaration in one file, then multiple other files – with illustrative names – which house extensions. This simplifies maintenance and improves re-usability over various targets (say my iOS app and today extension targets can include only the files they really need).
In order to access that core functionality, I need to make all the properties in it internal
. Which means any other type in the module can see them too.
Say an API wrapper, where all the endpoint calls use the same prefix – this would be in a file called Service.swift
. :
final class Service {
private let basePath: String = "https://..."
private baseURL: URL { return URL(string: basePath) }
}
Then in another file I would group all user-related endpoints, Service-UserEndpoints.swift
extension Service {
func userFetchMessages() {
// I need to access _basePath_ here
let endpoint = baseURL.appendingPathComponent("messages")
}
}
(and so on, for Swift-PaymentEndpoints.swift etc)
This can’t work, unless basePath
is made internal
. Which means that any other type in the module can see it even though it’s only useful inside particular type.
Proposal
Add new access control: protected
which will act similar to private
does now but across extensions anywhere in the module. Inside the file, it will be treated as fileprivate
and outside the file would only be accessible inside extensions of given type.
It thus fits between fileprivate
and internal
.
Alternatives
Obvious counter-point is that something like Service could be a framework. This is not always feasible, as protected
is useful even in type with something like 200 or 500 or 1000 total lines of code. Building and maintaining it like a framework is an overkill. Plus…can you imagine build times of a project with 50+ frameworks?
Another alternative is to modularize the Service, so it has UserService and PaymentService and so on. Configuration would then be injected into each specific module. Again – this is good approach for larger use cases but falls apart for many smaller ones as it can add rather too much maintenance overhead.