Ditto - I would love to be able to disallow non-subclasses accessing/modifying some variables.
Though, I'm not sure what would be the stand on this from the core team - according to Apple's blog they've already considered protected access level: https://developer.apple.com/swift/blog/?id=11 Charlie > On May 29, 2016, at 7:56 AM, Riley Testut via swift-evolution > <[email protected]> wrote: > > Unless I'm missing something Brent, your suggestions still wouldn't allow the > developer to provide a public class in a module designed to be subclassed by > clients in another module and access these "private" details, which is a real > problem I'm having in my current project. > > I have a framework which provides an abstract model object designed to be > subclassed (and yes it has to be a class and not a struct for a multitude of > reasons ๐) by clients of the framework. There are several convenience methods > + properties I have exposed for subclasses to use, but they should really be > implementation details; a client using these model objects should not have to > know about them. Even worse, several of the properties are mutable so the > subclasses can modify them, but they certainly should *not* be modified by > anything else. > > Right now, I'm limited to simply commenting something akin to "DO NOT CALL" > next to these methods/properties, which definitely goes against Swift's > safety focus. For these reasons, I'm 100% in support of a protected access > control modifier. > >> On May 28, 2016, at 8:11 PM, Brent Royal-Gordon via swift-evolution >> <[email protected]> wrote: >> >> To begin with, I'm not a fan of `protected` access. But even leaving that >> aside, I have a few questions and critiques. >> >>> A common case is the UIView from UIKit. Many developers are tempted to make >>> this call: >>> >>> view.layoutSubviews() >>> The documentation says: "You should not call this method directly. If you >>> want to force a layout update, call the setNeedsLayoutmethod instead to do >>> so prior to the next drawing update. If you want to update the layout of >>> your views immediately, call the layoutIfNeeded method." >> >> This example is illuminating in several ways. >> >> * The rule is not simply that "only the class should call >> `layoutSubviews()`"; it is effectively "*you* should never call >> `layoutSubviews()` except when `super`ing up from your override". Calling >> `layoutSubviews()` from `insertRows(at:)` is just as much a mistake if >> `insertRows(at:)` is part of the class as if it is not. So isn't `protected` >> insufficiently strict to properly serve this use case? >> >> * At the same time, something outside `layoutSubviews()` has to be able to >> call `layoutSubviews()`. In the case of UIKit, though, that "something" is >> always within UIKit itself, never outside it. So should `protected` have a >> "bottom", a level below which calls are unrestricted? For instance, in >> UIKit's case you might have `protected fileprivate`, meaning "anything up to >> `fileprivate` has unrestricted use; anything above that can override and >> `super` up from its override, but not use it any other way". >> >> protected fileprivate func layoutSubviews() >> >> * `layoutSubviews()` is also something you should probably always `super` up >> to. Have you considered addressing `super` requirements at all? >> >> In short, is a traditional `protected` really the feature you want to handle >> this use case, or would a very different design actually suit it a lot >> better? >> >>> When declarated by a class the protected member will be visible to the >>> class itself and all the derived classes. >> >> In what scope? The same as the class? >> >> Is there not room for, for instance, "usable without restriction in this >> file, override-only in the rest of this module, invisible outside it"? For >> instance, `internal(protected) fileprivate`, or perhaps `internal(override) >> fileprivate`? `layoutSubviews()` might then be `public(override) >> fileprivate`โthe ability to override is public, the ability to use it >> unrestricted is filewide. >> >> public(override) fileprivate func layoutSubviews() >> internal(override) fileprivate func privateSubclassingHook() >> >>> public protected(set) var x = 20 >> >> Of course, that might be difficult to combine with the `(set)` syntax. >> `public(set: override)`, maybe? With, for instance, `public internal(set: >> override) private(set)` if you want the property's getter public and its >> setter overridable internally and callable in private scope. >> >> public(override) fileprivate func layoutSubviews() >> internal(override) fileprivate func privateSubclassingHook() >> public(get, set: override) internal(set) var x = 20 >> >> But there's something about this that's starting to seem a little rotten. I >> think the problem is that we're not really trying to widen the ability to >> override, we're trying to restrict the ability to call. Let's try >> restructuring along those lines: >> >> public fileprivate(call) func layoutSubviews() >> internal fileprivate(call) func privateSubclassingHook() >> public internal(set: call) var x = 20 >> >> That seems much cleaner to me. >> >>> If the member is declared as final then it will be visible but not can be >>> overrided by the derived classes. Just like it works with other access >>> levels. >> >> With the "overridable but otherwise unusable" conception I'm suggesting, >> this would not be the case, of course. >> >>> Protocols >>> >>> Protocols do not declare access level for their members. So the protected >>> access level is not applicable here. >> >> But `protected` is quite different from other access levels; it does not >> limit the visibility of the symbols, but rather their use. And protocols >> face the same sort of problem as classes, where certain members are >> essentially override hooks and shouldn't be called directly outside a >> particular scope. >> >> So I think we ought to allow `accesslevel(call)`, but not a plain >> `accesslevel`: >> >> public fileprivate(call) func layoutSubviews() >> internal fileprivate(call) func privateSubclassingHook() >> public internal(set: call) var x = 20 >> internal(call) func protocolConformanceHook() >> fileprivate(set: call) var onlyProtocolSetsThis: Int { get set } >> >>> Extensions >>> >>> Extensions will not be able do be protected nor their members. >> >> This is very vague. There are several things extensions might try to do with >> protected members: >> >> * Declare new ones >> * Override existing ones >> * Call existing ones >> >> Which of these, if any, are permitted? Why? >> >> In my conception, I would permit extensions to behave as the type they >> extended did. Extensions could declare new members with restricted calling >> and override existing ones. They would not be able to call, except when >> supering from an override, unless they were within scope of the `call` >> access control. In other words, they'd behave just like any other code at >> that location. That's how we want extensions to work. >> >>> But nested declarations will be allowed, so this code will compile: >>> >>> // We can declare a protected class (or struct, enum, etc.) if >>> // and only if they are nested inside other type. >>> public class MyPublicClass { >>> protected >>> class MyProtectedClass { >> >> What does it mean to "use" a protected class, though? Clearly you can call >> its methods, if only through AnyObject or a non-protected superclass or a >> protocol it conforms to. Does it mean you can't instantiate it? Does it mean >> you can't subclass it? Does it mean you can't call methods that aren't on >> its supertypes? All of the above? None? >> >> One more thing that didn't come up: Testability. I believe that importing a >> module with `@testable` should disable its call restrictions, even ones >> inherited from outside that module. Thus, even if *you* cannot call your >> `layoutSubviews()`, your test suite can. >> >> So, in short, my counter-proposal is: >> >> public fileprivate(call) func layoutSubviews() >> internal fileprivate(call) func privateSubclassingHook() >> public internal(set: call) var x = 20 >> internal(call) func protocolConformanceHook() >> fileprivate(set: call) var onlyProtocolSetsThis: Int { get set } >> >> In other words: >> >> * There is a new aspect of the member, `call`, which controls the ability to >> actually call the member, as opposed to overriding it. No `call`, no calling >> (except when `super`ing up from an override). >> >> * `call` is used in combination with one of the existing access modifiers: >> `public(call)` `internal(call)` `fileprivate(call)` `private(call)`. >> `call`'s visibility is always less or equal to the member itself. >> >> * To control the callability of a setter independently from both the getter >> and the overridability of the setter, use `set: call`. >> >> * Extensions behave just like type definitions at the same location with >> regards to `call`. >> >> * Protocols can use access modifiers with `call` to prevent unauthorized >> code from calling a member. The access control level to implement a member >> continues to be as wide as the access control level of the protocol itself. >> >> * `@testable` disables `call` restrictions on the types it imports, so the >> test suite can call any visible member, even ones inherited from other >> modules. >> >> * There should probably also be some sort of "super required" warning/error, >> but this is an orthogonal feature and can be left for a separate proposal. >> >> I think that feature will be closer to the one you actually *want*, as >> opposed to the one that other languages have cargo-culted from SIMULA-67. >> >> -- >> Brent Royal-Gordon >> Architechies >> >> _______________________________________________ >> swift-evolution mailing list >> [email protected] >> https://lists.swift.org/mailman/listinfo/swift-evolution > _______________________________________________ > swift-evolution mailing list > [email protected] > https://lists.swift.org/mailman/listinfo/swift-evolution _______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
