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

Reply via email to