Missing a level of access control on class/struct

I am working on a large Swift framework that consist of several classes (actually structs). Several of these classes are very large, they are therefore implemented in several files by using extensions.

The user of the framework will get the access control required (by public access control), but what about the framework internally. The encapsulation benefits of OOP are not obtainable in this example. Protection of class members cannot be provided with private or fileprivate access control modifiers, when class implementation is distributed over several files.

It is a very complex systems so software quality is not only required on the module interface, but also internally of the module. I really misses protection of class members.

Maybe I have overlooked some other opportunities?

1 Like

From what I gather, it looks like you're searching for something like package-private in Java. There's no similar equivalent in Swift. However, I think the need for it can be mostly mitigated with a better set of types. It might not be applicable in your specific code base, but I'll mention it just in case it might help.

For example, you'd often see classes that conform to a ton of different protocols, each in a separate extension. And if those get extensions get long enough, people might break them out into separate files. That doesn't help reduce or organize the complexity, it just scatters it around. For example, you might see this with view controllers like:

class GodObjectViewControler: UIViewController,
    UITableViewDelegate,
    UITableViewDataSource,
    CLLocationDelegate,
    URLSessionDelegate,
    UISearchBarDelegate,
    UISearchControllerDelegate,
    UISearchResultsUpdating,
    // ... and so on

The solution there isn't to merely break this large class up at a text/syntax level (with extensions or multiple files), but to decompose the responsibilities into multiple, tiny classes, each of which stays focused on a single responsibility. These objects can be internal, and expose internal APIs to be called from your other code, while using private to hide their inner workings. They can easily be independently tested.

Objects of those classes can then be assembled into intermediate objects which coordinate actions between those multiple objects (e.g. use the user's current location from the location object, to ask the repository object to download data for restaurants near that location). This slims down the view controller, focusing it on controlling views, while the other stuff is handled by these composed objects.

Thanks for the answer. Due to performance issues (speed), an extra layer of complexity in the classes is not an option (it is a framework used for scientific calculations).

1 Like

Looks like it is in the works: SE-0386: `package` access modifier

1 Like

Unfortunately no.This proposal is about a new modifier that allows a module in a package to access another module in the same package without having to make the entities public.

What kind of performance issues, exactly?

If you're worried about more object allocations and indirection, then you could use structs, whose contents will be stored inline on the "parent" class, no different than if those fields were there directly

1 Like

I am using structs primarily. And these structs are having their data in structs. My concern about an extra layer was about how to efficiently share data between suggested internal sub-structs, and the extra call needed to go from top-level protocol based interface to the internal struct.

Otherwise, I like the idea of splitting a too large view-controller (for instance) up in that way.

I should also mention that the project is an attempt to move from a C++ based implementation to Swift. And that speed in C++ is obtained among other things by using inline functions a lot. I feel this proposal goes in the opposite directions.

Could you provide some pseudocode snippets to better illustrate the problem. I'm having hard time understanding what you're trying to protect and more importantly how. If you want to protect mutability but still have reference semantics, we now have actor as an option, but that would require you to potentially suspend on asynchronous access.

1 Like
public protocol: protocol1 {
...
}

public struct AAA: protocol1, protocol2 .... {
   var data: /* mostly struct based */
}

public struct BBB: protocol1, protocol2 .... {
   var data: /* mostly struct based */
}

...

/* In other files (hopefully), a large amounts of mutating functions operating on 'data' are implemented */

extension AAA {
   mutating func aaa() -> something {
      ...
   }
   mutating func bbb() -> something {
      ...
   }
}

/* If all functions were put in single file, the 'data' field in struct AAA would have been declared 'fileprivate'
   and in that way protected it to be accessed from other structs: BBB and CCC and so on. But there are
   nearly hundreds functions, and I don't like them to be in a single file. Therefore 'data' field must 'internal'
   and protection from other structs are lost.
 */

Tell me what I‘m still missing.

  • Are you trying to protect the data structure internally in the module itself?
  • Who and how will operate on these data structures?
  • Is the consumer a module user or a module maintainer / developer?

If it‘s a consumer then those mutating functions should be public and the internal becomes more or less irrelevant unless someone who maintains the module messes it up.

If you really want to protect data property internally then you‘re a bit out of luck as there‘s no such thing in Swift like ‘type private‘. I want it too, but this topic is very opinionated which is unfortunate.

1 Like

yes.

the 'data' field is declared internal (or private). It is not going to be public and known to the consumer.

I want this protection so a lot of other structs in the module can't access 'data' directly. It is good OOP programming style.

Or in other words, protection is for the sake of writing, reading and maintaining the code.

I'm having a hard time understanding the issue. Please tell me if this basic example reflects your problem:

// in file Foo1.swift

struct Foo {
  private var data: Data
}

extension Foo {
  mutating func frobulate() {
    // modify the `data` property 
  }
}

// in file Foo2.swift

extension Foo {
  mutating func frobnicate() {
    // try to modify the `data` property, but it wouldn't compile because it's in a separate file
  }
}

It this correct?

1 Like

Unfortunately I don‘t think I can propose any solution for this problem. Personally I stopped using most access modifiers from Swift besides the implicit internal, public and private but only for hiding inits for singleton types. All private type members are prefixed with an underscore to signal hands off but they remain internal due to the lack of type private access control.

Also a tiny nit pick. If you‘re using structs only, then there‘s no direct OOP here. You‘re not really acting on objects, unless your structs have internally reference semantics (+ possibly COW optimizations).

yes.

Thanks for advice. I am experimenting a bit with structs. Maybe, I have to fall-back on class implementation.

This has been discussed in the past, with no particular conclusion, as far as I can tell. But I think the issue is ill-defined.

Imagine that we got a typeprivate access modifier that could allow for something like this:

// in file Foo1.swift

struct Foo {
  typeprivate var data: Data
}

extension Foo {
  mutating func frobulate() {
    // modify the `data` property 
  }
}

// in file Foo2.swift

extension Foo {
  mutating func frobnicate() {
    // modify the `data` property is allowed even if I'm in a separate file, because the access is `typeprivate`
  }
}

This will effectively make the data property internal, even if it doesn't look like it. That's because in any file within the module I could write something like this:

// in file SomewhereElseFarAwayInTheCodebase.swift

extension Foo {
  var internalDataProxy: Data {
    get {
      self.data
    }
    set {
      // this is fine because I'm in an extension of `Foo` and `data` is `typeprivate`
      self.data = newValue
    }
  }
}

struct DataBypassCaller {
  func mutateDataOn(foo: inout Foo) {
    let someNewData: Data = ...

    // the effect of this is like modifying the `data` property directly
    foo.internalDataProxy = someNewData
  }
}

Due to the fact that we can extend types in Swift, the file-level access control becomes very important.

Please note that, in Swift, this issue cannot even solved by classes or OOP, not even with a protected kind of access control: as long as you can access some private member in some way in some other file, you will always be able to create "proxy" members that break the encapsulation.

My suggestion is to simply put everything in the same file, and use organization tools like // MARK: - comments to highlight the specific sections. Alternatively, just use internal and make sure to have practices in place in your team to spot code that breaks invariants and/or encapsulation in code reviews. Finally, you can instead create a module (a SPM library target) that contains the files that share the data property, that would be declared as internal to the module: this way the property would not be public, thus, not visible from outside, but still accessible by the other files within the module.

Another thing that could help is a new access modifier that's not currently in Swift, but it could be introduced in the future, given that some people in the forums (myself included) have expressed interest in it, that is, folderprivate. If we could limit the visibility of a declaration to the files in a particular folder, we could break down large files into smaller ones, as long as they are in the same folder.

So, the example would change like the following:

// in file Foo1.swift

struct Foo {
  folderprivate var data: Data
}

extension Foo {
  mutating func frobulate() {
    // modify the `data` property 
  }
}

// in file Foo2.swift, in the same folder

extension Foo {
  mutating func frobnicate() {
    // modify the `data` property is allowed even if I'm in a separate file, because the access is `folderprivate`
  }
}

// in file SomewhereElseFarAwayInTheCodebase.swift, not in the same folder

extension Foo {
  var internalDataProxy: Data {
    get {
      self.data
    }
    set {
      // this will not compile, because the file is not in the same folder
      self.data = newValue
    }
  }
}

Even in this case, it would technically be possible to break encapsulation by sneaking in a new file in the folder, but this could be much more easily controlled, for example having specific owners defined for the files in that particular folder, that would be asked to approve any change in its files.

It means a lot to me to have the possibility of providing restricted access to data members whereever I want it. It makes code easier to understand and guards against mistakes and so on.

If there is a way by using force to overrule such restrictions is another discussion.

After having read the mentioned discussion, I give up for now, and will think about whether to live with very long files of no protection for data members throughout the module. :disappointed_relieved:

What about the option to create a Swift module that contains all the files in which you would break the large file into?

As mentioned in above post, I have given up on this, and decided to align with standard way to handle when a file gets large, which is to use pragma marks to navigate using Xcode's dropdown menu.

Another important factor is Xcode's code folding mechanism, which I recently discovered now works as expected (it did not some years ago).

1 Like