[swift-users] Property access for subclasses only

Brent Royal-Gordon brent at architechies.com
Fri Mar 17 16:19:25 CDT 2017


> On Mar 17, 2017, at 11:54 AM, James Dempsey via swift-users <swift-users at swift.org> wrote:
> 
> I have a class in a framework that is expected to be subclassed in modules outside of the framework. I’d like to do the following:
> 
> 1. I want some properties of that class to be accessible by subclasses outside of the framework.
> 2. I don’t want these properties of that class to be accessible by any piece of code outside of the framework because I don’t want dependencies on these properties to propagate beyond the subclass.
> 
> Am I correct in thinking that there is no way to achieve both #1 and #2 in Swift?
> 
> The only way I know to achieve #1 is to make the property public or open. And that makes the property public to everyone which makes #2 impossible.

No, there is no way to do this.

This reflects a judgement on the part of Swift's designers that `protected` access is not very useful, because you can always get around it by creating an extension on the type and exposing the API under a different name. At the same time, it prevents developers from structuring their subclass code freely; code which would be better moved to free functions or helper types instead has to remain in the subclass solely to access protected symbols.

This judgement is not uncontroversial, and gets rehashed on swift-evolution at least once a year.

> It seems like the best I can do is put a big comment on each property that says something like:
> // NOTE: FOR SUBCLASSERS ONLY! DO NOT USE EXCEPT IN A SUBCLASS!


You can do a little bit better by putting the methods in an ancillary "sharps drawer" type:

	open class MyClass {
		open func publicThing() { … }
		
		public struct SubclassOnly {
			fileprivate var instance: MyClass
			
			public func protectedThing() { … }
		}
		
		var subclassOnly: SubclassOnly { get { SubclassOnly(instance: self) } }
	}

This is still sort of the "honor system", but it at least makes it impossible to accidentally access something you shouldn't.

If you want the protected methods to only be used from inside certain override points, you can actually enforce that by creating a type which can only be constructed internally, requiring that type be passed to the methods you want to protect, and only passing it to your override points. For example:

	open class MyClass {
		public struct ProtectionToken {
			fileprivate init() {}
		}
		
		// Instead of overriding this...
		public func publicThing() {
			overridablePublicThing(protected: ProtectionToken())
		}
		
		// Subclasses override this.
		open func overridablePublicThing(protected: ProtectionToken) { … }
		
		// You can only call this if you have a protection token, and you can 
		// only get a protection token if the class gives it to you.
		public func protectedThing(protected: ProtectionToken) { … }
	}

This is a heavyweight solution, but if you really care strongly about who's allowed to call your methods, it'll do the trick. And it allows the kind of restructuring that `protected` forbids, since once you have a protection token, you can pass it to another method or type to delegate your access. You can also have several different kinds of protection tokens and do various other clever things.

-- 
Brent Royal-Gordon
Architechies



More information about the swift-users mailing list