How to: class private variable only accessible by categories located in different files

Posting this here because maybe I am missing something. I have a few huge classes (I didn't originally write this code!), and because of the size the original devs created a few category files. Great - its more readable.

But mutable properties have to exposed to the world! InObjective-C, you could create a private interface extension that exposed such priorities and by convention was not used outside the one class. I cannot think of anyway to do such a thing with a Swift class.

Suggestions?

Thanks,

David

In ObjC, your “private” extension is visible to anyone who #imports the header that contains it. Since Swift doesn’t have header files, this way of “hiding” declarations is not available. In Swift, you can only restrict declarations to the enclosing scope (private) or to the module (internal).

Edit: Actually, I think I misunderstood your problem. You’re probably looking for public internal(set) var myProperty.

Kyle, thanks for responding! I tried your solution but no go:

In Foo.swift:

final class Fud {
    public internal(set) var msg = ""
    var msg2 = ""
}

In Foo+Stuff.swift:

extension Fud {
    func test() {
        msg = "123"
        msg2 = "456"
    }
}

In ViewController.swift

let f = Fud()
f.msg = "Howdie"

No warning.

In all fairness, I don't believe there is a solution now to this. If not I plan to post elsewhere and ask for some keywords, as this is not a problem I alone face. When a property appears public, any developer should believe they can access it too.

A less desirable solutions is to prefix every such property with an underscore or say "private_xxx".

Removing header files from the language means there’s no direct notion of “who can access this” except the ones the compiler can enforce: public, internal, fileprivate, private. I don’t think you’re looking for the oft-proposed “typeprivate” because not every extension should get access to the setter, correct? Therefore we would have to invent something new to get the same effect in Swift, some lock-and-key mechanism that says “anyone [in the module] can access this but only if they agree that they need to” (like an explicit #import "VC+Private.h") or “these specific users can access this in addition to the current scope” (kind of like C++ friend).

Both of these feel niche to me because ultimately you are protecting against “yourself”. When you’re within a module and you want access to something, you can just give yourself access no matter how it was set up. So the workaround of using a computed wrapper around a clearly-internal property doesn’t actually seem terrible to me. (You could probably make a property wrapper for this purpose as well, if that provided enough of a spelling restriction to be satisfactory.)

Well, I would prefer that any extension can access that variable over every developer on a project assume that this variable is far game for them to modify. Comments could help here but in reality using an underscore prefix seems to me to be the best solution now - it at least alerts future devs that this property is special.

The old Objective-C private header was intended for just this purpose - devs look at Foo.h and can see the contract it provides to the outside word, and even if they knew Foo_Private.h existed, they'd ignore it.. In the old days if one knew an ivar existed you should use setValue:forKey to change or access it - and probably got what you deserved when that code changed.

I'm not a gambling man, but I would imagine the odds of getting a new keyword for this is 0.01%, but hey, its only going to cost me an hour to pitch it.

Again, thanks for the response.

PS: In the past 5 years I've done contract or full-time work in all-Swift codebases, and unfortunately devs have continues to expand classes with categories located in separate files, instead of using "helper" objects to handle specific duties. In every single company this issue of having public settable variables has been a real problem in trying to create a "public interface" for the class.

1 Like

I’ve never been convinced by this argument, honestly. By this logic, there’s no reason for private, since you can always replace it with internal and “discipline”.

Software development is a group activity, and every group boundary is not necessarily reflected by a module boundary. Hence things like @_spi().

2 Likes

*shrug* File boundaries mean something as long as we use files. Module boundaries mean something because that’s how we structure compilation. I agree that sometimes we want to express more complicated things and we don’t have the tools to do it; note that @_spi is still underscored. Header files are one mechanism, but not a very satisfying one. Typeprivate is another (that I personally dislike because it encourages grouping operations on types for access reasons rather than clarity-at-point-of-use). Some form of “submodules” is yet another. Any of these would need design and a full pitch.

1 Like

Any pitch in this area gets so much pushback from the Swift team that it quickly gets disheartening. Much of the team seems to share the opinion that files are just another form of lexical scoping, when in fact files are a way for humans to organize code. Requests like this thread are about one-off instances of privileged information sharing, but Swift wants you to organize your code at the top level based on those exceptions or else forsake access enforcement.

I appreciate that purely lexical scoping is simpler for the compiler. I wonder how a hybrid of @_spi and friend would be received:

/// ClassA.swift
public class ClassA {
  public internal(set, ClassAHelpers) var someProperty: Int
}

/// ClassB.swift
@internal(ClassAHelpers)
public class ClassB {
  func doStuff(with aInstance: ClassA) {
    aInstance.someProperty = 42 // ok
  }
}

extension ClassB {
  func doMoreStuff(with aInstance: ClassA) {
    aInstance.someProperty = 100
    // error: `someProperty` setter is not accessible from this scope
    // note: add `@internal(ClassAHelpers)` to enclosing `extension` to make `someProperty` setter accessible
  }
}
2 Likes

Access hasn't been lexically scoped since SE-0169, so "easier for the compiler" isn't really relevant here. It's really the other way around: files are a way for humans to organize code, and if you don't have separate files for "interface" and "implementation", then a very common place where you'll find code related to a particular declaration is in the same file. That's why fileprivate exists, and has existed since Swift 1.*

Moving forward from there, users made the argument that an even more common place to find uses is in the same scope (SE-0025). That was later widened to "the same type, in the same file" (SE-0169). That's private. Some people further argued that "in the same type but not in the same file" is also a useful grouping, and I disagree for the reason stated above.

So yeah, Swift's current access control is not flexible enough to do what you want, because there are two access levels that the language has to have, public and non-public, and we've split the latter into three groups we think are useful: internal, fileprivate, and private. They're not the only possible useful groups, just the ones most likely to be useful. Adding new levels, whether it's based on types, or file grouping, or declaration grouping like your example, increases the complexity of the language. Like any proposal, though, it might increase it in a way that's worth it.

And yeah, access control changes get a lot of pushback:

  • There have been two changes to what private means since Swift 1, and they were both controversial.
  • Many people were not happy about open being split from public.
  • File grouping is tied up with "submodules", which can mean a lot of different things. Proposing any of those features means thinking about whether the model can or should be expanded to include other features in the future, or whether they can coexist, and ugh.
  • To my knowledge, no other mainstream language implements something like C++ friend. Which doesn't mean Swift can't, but does indicate that no one else has felt the need for it.

So the core team has a higher bar for access control changes, because they will take a lot of energy from the proposal author, from the core team themselves, and from the community.


This all comes back to header files. Header files offered no control over who used an API beyond public/non-public, but they did signal intent; clients had to opt in to seeing certain declarations. And since Swift doesn't have header files, that particular mechanism for opting in and signalling intent is lost as well.

P.S. I get that the problem is also exacerbated by certain declarations needing to be in the original type rather than in extensions. Maybe that's an angle that can be investigated as well.

* Full disclosure: I was a primary designer of Swift's original three-level access control, and still think that separating fileprivate from private was a mistake.

6 Likes

Jordan,

First thank you so much for your thoughtful and informative response! I too don't like filePrivate!

I was pondering next steps.

The problem with expending effort on this is that it in effect rewards bad behavior. Devs shouldn't be creating massive classes with lots of categories located in external files. While a solution for sure would be welcome by anyone supporting such code, I'm sure there are other areas that are more deserving of time.

So really I'm back to thinking of using a property prefix of say "classPrivate_countOfFoo". I know its possible to use Unicode symbols in names (I haven't done this) - so maybe the poison symbol or equivalent.

I'm thinking of this as a good technique too:

class Foo {
    // CONTENTS ARE PRIVATE TO Foo
    struct ClassPrivate {
        var countOfFoo = 0
    }
    var cp = ClassPrivate()
    var countOfFoo: Int { return cp.countOfFoo }

All categories use "cp." and if you need to make something public readable, you can with a little bit of effort.

Again, thanks!

David