[swift-evolution] Protected access level / multiple class/struct/protocol APIs
Andrey Tarantsov
andrey at tarantsov.com
Tue Mar 29 03:10:46 CDT 2016
Now that the scoped access discussion is close(r) to being settled, let's tackle the next controversial topic: protected.
Expose
Objective-C allows multiple “APIs” to be exposed for a given class by virtue of writing additional header files. There are two common uses of that:
Use case 1: API for private clients
Foo.h
Foo-Private.h
Foo.m
Use case 2: API for subclasses, intended to be overridden
Foo.h
Foo-Subclasses.h // defines a method to override, e.g. layoutSubviews
Foo.m
Use case 3: API for subclasses, intended to be called
Foo.h
Foo-Subclasses.h // defines a dangerous helper method
Foo.m
Swift covers use case #1 with the internal (or perhaps soon-to-be moduleprivate) access modifier.
The problem
Swift core teams has prompted us to explore using internal and file-private access levels to cover use cases #2 and #3, and I've faithfully tried to do just that. It works for many cases inside the code of apps, but it doesn't work if you're writing a library package and want to expose the “special extenders club” API to the clients of the library.
Here's the use case in more detail:
1. You have a library that exposes a class called Foo.
2. There are two different ways to use that library: you can just use Foo, or you can extend the library, perhaps subclassing Foo.
3. Those who opt for the second way to use the library (i.e. extending it) need access to a broader set of APIs.
Why do extenders need access to a broader set of APIs?
Basically, for safety and documentation purposes.
Considering use case #2:
The library often exposes very similar APIs, some of which are intended to be called (layoutIfNeeded), others intended to be extended (layoutSubviews). These typically sound very similar, and are easy to mix up, so you definitely want to document this difference, and ideally you also want to statically prevent the first (non-extending) type of clients from calling them.
Note that accidentally calling layoutSubviews instead of layoutIfNeeded is one of the worst kinds of mistakes; it might seem to work, and it might not be found until production, and the bug it causes may be weird and difficult to reproduce.
Considering use case #3:
A library may expose methods that should not be called by normal clients, but are useful when extending it:
3.1) They may carry substantially different guarantees from normal methods (e.g. require to be called only on a certain dispatch queue),
3.2) or may only be callable while another overridable method is executing,
3.3) or may simply allow mutating the internal state of the class (e.g. the state of UIGestureRecognizer) that is not supposed to be mutated from outside.
Swift currently requires making all of this public. :-(
The problem has tons of prior art.
C family of languages use multiple header files.
Ada has special access rules for subpackages (nicely explained in http://blog.spacesocket.com/2012/07/31/ada-child-packages/).
OOP languages have protected access levels.
What does extending mean in Swift?
I'm not sure about this. It certainly needs to include subclassing a class, but we may find other useful meanings.
In particular, writing an extension for a struct/class/protocol may raise similar concerns and may need similar treatment.
What do I propose?
I'm mainly looking for ideas and discussion, but as a strawman, let me put this out:
Introduce protected access modifier that allows the member to be accessed by:
1. Subclasses of a class.
2. Extenders of a struct or protocol.
3. Implementors of a protocol.
package FooKit:
public class Foo {
public func amSafe() { ... }
protected func amDangerous() { ... }
}
public struct Boo {
public func amSafe() { ... }
protected func amDangerous() { ... }
}
public protocol Moo {
func amSafe()
}
public extension Moo {
protected func amDangerous() { ... }
}
App:
public class Bar: Foo {
public func bar() {
amDangerous()
}
}
public extension Foo {
public func boz() {
amDangerous()
}
}
public extension Boo {
public func boz() {
amDangerous()
}
}
public class Boz: Moo {
public func fubar() {
amDangerous()
}
}
So what do you think?
A.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160329/ab33725c/attachment.html>
More information about the swift-evolution
mailing list