Three subtle details of Swift extensions

Three subtle details of Swift extensions

Whenever I first look at a document, I skim through it quickly, nodding and muttering to myself, "Okay! I see, that's it!" But later when I really want to apply the knowledge points that I thought I understood, I find that the actual situation is often different from what I thought. Every time this happens, I get confused and think, "Wow... What's going on? This is completely different from what I thought! Does the document mention this?"

A few recent discussions have made me ask myself if I really understand extensions in Swift. I've read the documentation on extensions, and I "thought" I understood them pretty well. But these discussions, plus my own private verification through code, have made me discover several subtle details that I hadn't noticed before.

Update: As soon as this post was published, the Swift community stepped up and helped me understand my fundamental confusion. I wrote another post “Access Control in Swift Clarified” to further clarify my previous misunderstanding. To avoid making the same mistake I did, I recommend you read it.

Three subtle details about extensions

Thinking about the following three details seriously challenged my previous understanding of Swift extensions:

Swift extensions have visibility into the types they extend. For example, can an extension access private properties?

Does the location where an extension is defined affect the visibility of the extension? For example, if I have a type and I want to write an extension, is there any difference between writing the extension in the same source file and writing it in another file?

The default access modifiers for members in an extension and the effect of adding modifiers to them on the public interface of the extension as a type.

Before I start, let's assume I have a public struct Person. This struct has some private properties, name, gender, and age. Gender is encapsulated in an enumeration. The struct looks like this:

  1. public struct Person {
  2. private var name: String
  3. private var gender: Gender
  4. private var age: Int
  5.    
  6. public init(name: String, gender: Gender, age: Int) {
  7. self.name = name
  8. self.gender = gender
  9. self.age = age
  10. }
  11.    
  12. public func howOldArdYou() -> String {
  13. return formattedAge()
  14. }
  15.    
  16. // Private method, used for the following analysis of the extended `visibility`...  
  17. private func formattedAge() -> String {
  18. switch self.gender {
  19. case .Male:
  20. return   "I'm \(self.age)."  
  21. case .Female:
  22. return   "Not telling."  
  23. }
  24. }
  25.    
  26. public   enum Gender {
  27. case Male
  28. case Female
  29. }
  30. }

Now, let's write an extension for Person and clarify the three small details just mentioned through practice...

Extending access to types

When I proposed the first detail, about the ability of extensions to access the extended types, I asked a question: "Can extensions access the contents modified by private?" The answer was unexpected at first: Yes... the extension can access it.

However, there is a second detail to consider here: where you define the extension definitely matters.

Defined in the same file

If the extension and the type are written in the same source file, the extension can access the contents modified by privilege in the type.

For example, defining an extension of Person in Person.swift will allow the extension to access variables and methods modified by private

  1. extension Person {
  2. func getAge() -> Int {
  3. return age // Compiles successfully even though age is --private--  
  4. }
  5.    
  6. func getFormattedAge() -> String {
  7. return formattedAge() // Compiles successfully even though formattedAge is --private--  
  8. }
  9. }

As for why this happens when the extension is written in the same source file, my own reasoning is that when writing the type, the implementation of the extension can be written as part of the type, and the final effect will be the same. "Who knows?! What?? Why?" I didn't understand it at the time...

I am in the source file of the type I want to "extend", so whether I write the new functionality as an extension of this type or define the functionality I originally intended to write in the extension right inside this type makes no difference, the effect is the same.

So, from the compiler's perspective, the compiler might say, "Okay, I see an extension here, but it's really not necessary because the extension and the type are in the same source file... so the developer can just write the code from the extension directly inside the type... so he/she can access the private-modified code segment."

Update: The reasoning I wrote above shows that I didn't understand Swift's access control mechanism at all. So I suggest you read my later article "Explaining Swift's Access Control Mechanism" for more details.

Defined in different files

If you write the extension in another file, the extension cannot access the contents of the type that are modified by private.

According to my own reasoning above, thinking the other way around, it also makes sense to me that private properties cannot be accessed if they are defined in different files.

Most of the time, you will be extending types that you don't have the source code for, in which case the extension can only access those contents that are modified by public.

Extended access control by default

Verifying the last detail also gave me a deeper understanding. Apple's official documentation said it, but it was not until I verified it myself that I realized the subtle impact of the default access control modifier on the extension.

Default access when no access modifiers are explicitly declared

In short, when you declare an extension without specifying an access modifier (by default), the default access level of the extension depends on the access level of the type being extended.
* If the type is public or internal, then the "members" of the extended implementation are internal by default. What I didn't expect here is that unless you specifically declare it, the member variables of the extension of the public type are also internal by default.
* If the type is private, then by default the "members" in the extended implementation are also private

Here’s what happens when we don’t explicitly declare what access modifiers to add (I defined this extension in Person.swift to access private properties and methods):

  1. public struct Person {
  2. // ...  
  3.    
  4. // ...  
  5. }
  6.    
  7. extension Person {
  8. func getAge() -> Int {
  9. return age
  10. }
  11.    
  12. func getFormattedAge() -> String {
  13. return formattedAge()
  14. }
  15. }

The same module, like the code above, uses the default access modifier to allow instances in the same module to access the API in the extension. However, if the instance of the extended type is in another module (such as a test module), it cannot access any new public API in the extension.
Same module

Different modules (tests)

For some reason, I always thought that if I added an extension to a public type, the members in the extension should also be public. I don't know why I thought this, but fortunately my verification made it clear.

#p#

Declare the extension normally, but add the public modifier to the extension's implementation

Adding public access control modifiers to the members of the extended implementation will make these members accessible both in the same module and in different modules (test targets).

As long as the members are public, it doesn’t matter whether you declare the extension in the same source file or in another file... However, as mentioned earlier, only extensions declared in the same source file can access type member variables that are private.

Extensions declared in different (left) and the same (right) source files

Public extension member variables can also be accessed in different modules

Please note that when I wrote extension Person {...}, I did not add any modifications to this extension, I just added public to the members of this extension. Even so, the newly added methods can still be accessed in different modules.

In other words, there is no need to write public extension Person {...}. Because Person is already public, extensions based on Person naturally inherit the access level of the type itself.

Summarize

For me, the three details about Swift extensions mentioned in this article are enough for me to play around with the code to verify. I hope the analysis here can clear some obstacles for friends who are trying to understand Swift extensions.

<<:  Response to AFNetworking security bug

>>:  Apple opens App Analytics to all developers for free

Recommend

Science in the Week: Will Tying Your Hair Often Make Your Hairline Higher?

2022, Week 7, Issue 5, Total Issue 21 It’s almost...

Implanting AI engine Amap's future goal is to create a living map

At the Yunqi Conference in August this year, Alib...

How do you evaluate Jiang Xiaobai’s marketing style?

The heart-wrenching copywriting creativity only s...

Improve conversion rate: How to make App [preview video]?

[Preview video] is a new feature provided by Appl...

Cloud Native for Big Companies 2021

Course Catalog 01. What is cloud computing? 02. C...

Tik Tok practical operation notes!

Tik Tok is a new world, and every world has its o...

Do you know these 4 points about Tencent social advertising?

Do you know all this about Tencent social adverti...

How to place Amazon CPC ads? How to do Amazon CPC ads?

The performance of CPC directly affects the sales...