[swift-evolution] [Proposal] Protected Access Level

Rod Brown rodney.brown6 at icloud.com
Sun May 29 06:36:27 CDT 2016


Brent,

I personally was also thinking of the UIGestureRecognizerSubclass example, and I think it’s the best example of ‘protected’ use case, but I think I came to a slightly different position you did.

You come to this from the perspective of “can we do something with the same limitations of UIGestureRecognizerSubclass”? I come at this from the exact opposite view, I think UIGestureRecognizer shows the problem with the Obj-C solution. It’s a workaround for a painful limitation of Obj-C. This is something we should be looking to provide a better solution for, not “as good as”. I also think your solution is quite convoluted simply to avoid a “protected” solution.

I really liked your idea for “private(call)” but I think that the UIGestureRecognizer example still could not be implemented without a “protected(call)”. Internal state needs to be updated by the gesture recognizer subclass, outside the initial module (UIKit), but these setters and accessors should never be called externally of a subclass. That, in my mind, is the definition of “protected”.

You seem to be producing a convoluted solution to create a “sharps drawer” but Swift is supposed to be safe. You shouldn’t access the sharps drawer there and there are better ways for us to stop you than to simply “trust” the developer - it’s to do the right thing, and provide a protection level that stops the access where it’s not needed: “protected”.

Protected would not fix some of the issues with calling methods like “layoutSubviews()” but we can address them otherwise as they’re not really in the scope “protected” addresses.

- Rod



> On 29 May 2016, at 8:30 PM, Brent Royal-Gordon via swift-evolution <swift-evolution at swift.org> 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.
> 
> That's a little bit of a different use case than the `layoutSubviews()` case discussed in the proposal.
> 
> My answer is this: There is nothing magical about being a subclass that ought to grant access to those methods. For instance, if your subclass grows very complicated and you extract a helper object, it's perfectly reasonable for that helper object to want to access the "subclass-only" API. Contrarily, simple subclasses might not need that API, and exposing it to them would be an unnecessary risk. And there are things which you don't subclass at all which could benefit from being hidden away—think of the Objective-C runtime, which has some parts which every app needs (like the definition of `BOOL`) and other parts which are extraordinarily dangerous and should only be available to code which needs it (like `method_exchangeImplementations`).
> 
> The Objective-C solution—using a separate header file—actually acknowledges this fact. Even though the header is called "UIGestureRecognizerSubclass.h", it is not really limited to subclasses; any code can import and use that API. It's just sectioned off *by default*, like keeping all the kitchen knives in a sharps drawer. And the Objective-C runtime, which doesn't contain (many) classes, can use this approach too: <objc/objc.h> is implicitly available, while <objc/runtime.h> is something you have to ask for explicitly.
> 
> There are a few ways you could bring this same "sharps drawer" approach to Swift. For instance—without adding any language features—you could create an ancillary struct which merely serves to segregate all the dangerous APIs:
> 
> 	public class UIGestureRecognizer {
> 		public private(set) var state: UIGestureRecognizerState {...}
> 		
> 		private func ignoreTouch(touch: UITouch, forEvent event: UIEvent) {...}
> 		private func reset() {...}
> 		
> 		// etc. for the other APIs
> 		
> 		/// Contains methods and properties which directly affect the state of the gesture recognizer.
> 		///
> 		/// -Warning: Only use the state engine when implementing a custom gesture recognizer yourself.
> 		/// 		The state engine is delicate and modifying behind a gesture recognizer's back is likely to 
> 		///		break it.
> 		public var stateEngine: StateEngine { return StateEngine(gestureRecognizer: self) }
> 		
> 		public struct StateEngine {
> 			private var gestureRecognizer: UIGestureRecognizer
> 			
> 			public var state: UIGestureRecognizerState {
> 				get { return gestureRecognizer.state }
> 				nonmutating set { gestureRecognizer.state = newValue }
> 			}
> 		
> 			public func ignoreTouch(touch: UITouch, forEvent event: UIEvent) {
> 				gestureRecognizer.ignoreTouch(touch, forEvent: event)
> 			}
> 			
> 			public func reset() {
> 				gestureRecognizer.reset()
> 			}
> 
> 			/// etc. for the other APIs
> 		}
> 	}
> 
> Now ordinary clients of UIGestureRecognizer won't see a bunch of random methods strewn around with doc comments warning not to use them; they'll see *one* property with an intimidating name and a scary comment. You could even give the property a name like `internals` to make it clearer that you shouldn't be touching this unless you know what you're doing. On the other hand, any code that needs to *can* access these features, whether or not that code happens to be located in a subclass of the class in question.
> 
> Obviously, this approach could benefit from formalization; there are a number of ways that might be done. For instance, you could create a sort of namespace within a class which functions the same way as the `StateEngine` struct and `stateEngine` property in the last example, but without the boilerplate:
> 
> 	public class UIGestureRecognizer {
> 		/// Contains methods and properties which directly affect the state of the gesture recognizer.
> 		///
> 		/// -Warning: Only use the state engine when implementing a custom gesture recognizer yourself.
> 		/// 		The state engine is delicate and modifying behind a gesture recognizer's back is likely to 
> 		///		break it.
> 		namespace stateEngine {
> 			// Note that `self` here is still UIGestureRecognizer.
> 			
> 			public var state: UIGestureRecognizerState {...}
> 			
> 			public func ignoreTouch(touch: UITouch, forEvent event: UIEvent) {...}
> 			public func reset() {...}
> 		}
> 		
> 		public var state: UIGestureRecognizerState {
> 			get { return stateEngine.state }
> 		}
> 	}
> 
> You could tag particular methods and properties such that files have to ask for access to that subset:
> 
> 	public class UIGestureRecognizer {
> 		public(@restricted(set: StateEngine)) var state: UIGestureRecognizerState {...}
> 		
> 		public(@restricted(StateEngine))  func ignoreTouch(touch: UITouch, forEvent event: UIEvent) {...}
> 		public(@restricted(StateEngine)) func reset() {...}
> 	}
> 
> 	// In some other file...
> 	import UIKit
> 	use UIGestureRecognizer.StateEngine
> 
> Or you could move the dangerous members into a submodule and thus require a separate import to see them. I will not speculate on a good submodule syntax, but usage would end up looking like this:
> 
> 	import UIKit
> 	import UIKit.UIGestureRecognizerStateEngine
> 
> All of these approaches share the virtues of the Objective-C approach:
> 
> * Subclasses which don't need the dangerous stuff don't have access to it.
> * Code which is not technically a subclass but still requires access *does* have access to it.
> * They'll work with types which *don't* get subclassed but similarly have some rare-but-dangerous APIs.
> 
> So, just as with controlling override point callers, I think that `protected` is at best a rough approximation of the feature you actually want.
> 
> -- 
> Brent Royal-Gordon
> Architechies
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution



More information about the swift-evolution mailing list