I agree - both will need to be addressed in the future to make the language
complete. There are some valid points, however, in the blog post - would you
allow extensions to access protected variables and potentially expose them to
other classes? I.e:
/// Class in module A:
class View {
protected func layoutSubviews()
}
/// In module B:
extension View {
func doLayoutSubviews() {
self.layoutSubviews()
}
}
Or would you disallow access from extensions to prevent this kind of abuse?
> On May 29, 2016, at 11:19 AM, Goffredo Marocchi <[email protected]> wrote:
>
> Without wither abstract classes or a protected access modifier, the status
> quo, that kind of ExceptionThisMethodShouldBeOverridden are really ugly bad
> code there is no alternative to beyond a religious stop using classes and sub
> classing... did you not know about your pop saviour ;)?
>
> Sent from my iPhone
>
>> On 29 May 2016, at 08:38, Charlie Monroe via swift-evolution
>> <[email protected]> wrote:
>>
>> 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
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution