Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is declaring self not required in structures where it's required in classes?

Tags:

swift

Why is declaring self not required in structures where it's required in classes? I don't know if there are other examples where this is the case but with escaping closures, it is. If the closure is non-optional (and thus non-escaping), there is no requirement to declare self in either of the two.

class SomeClass {
    let someProperty = 1
    
    func someMethod(completion: (() -> Void)?) {}
    
    func anotherMethod() {
        someMethod {
            print(self.someProperty) // declaring self is required
        }
    }
}

struct SomeStruct {
    let someProperty = 1
    
    func someMethod(completion: (() -> Void)?) {}
    
    func anotherMethod() {
        someMethod {
            print(someProperty) // declaring self is not required
        }
    }
}
like image 814
kidcoder SAVE UKRAINE Avatar asked Oct 06 '20 20:10

kidcoder SAVE UKRAINE


People also ask

When should I use a struct instead of a class?

Class instances each have an identity and are passed by reference, while structs are handled and mutated as values. Basically, if we want all of the changes that are made to a given object to be applied the same instance, then we should use a class — otherwise a struct will most likely be a more appropriate choice.

What is difference between class and structure in Swift?

In Swift, structs are value types whereas classes are reference types. When you copy a struct, you end up with two unique copies of the data. When you copy a class, you end up with two references to one instance of the data. It's a crucial difference, and it affects your choice between classes or structs.

Why struct is faster than class Swift?

Structures and classes in Swift have many things in common. The major difference between structs and classes is that they live in different places in memory. Structs live on the Stack(that's why structs are fast) and Classes live on Heap in RAM.

When should you use classes over structs Swift?

Use Classes When You Need to Control Identity Classes in Swift come with a built-in notion of identity because they're reference types. This means that when two different class instances have the same value for each of their stored properties, they're still considered to be different by the identity operator ( === ).


2 Answers

The purpose of including self when using properties inside an escaping closure (whether optional closure or one explicitly marked as @escaping) with reference types is to make the capture semantics explicit. As the compiler warns us if we remove self reference:

Reference to property 'someProperty' in closure requires explicit use of 'self' to make capture semantics explicit.

But there are no ambiguous capture semantics with structs. You are always dealing with a copy inside the escaping closure. It is only ambiguous with reference types, where you need self to make clear where the strong reference cycle might be introduced, which instance you are referencing, etc.


By the way, with class types, referencing self in conjunction with the property is not the only way to make the capture semantics explicit. For example, you can make your intent explicit with a “capture list”, either:

  • Capture the property only:

     class SomeClass {
         var someProperty = 1
    
         func someMethod(completion: @escaping () -> Void) { ... }
    
         func anotherMethod() {
             someMethod { [someProperty] in    // this captures the property, but not `self`
                 print(someProperty)
             }
         }
     }
    
  • Or capture self:

     class SomeClass {
         var someProperty = 1
    
         func someMethod(completion: @escaping () -> Void) { ... }
    
         func anotherMethod() {
             someMethod { [self] in            // this explicitly captures `self`
                 print(someProperty)
             }
         }
     }
    

Both of these approaches also make it explicit what you are capturing.

like image 198
Rob Avatar answered Sep 27 '22 20:09

Rob


For classes, closures provide a mechanism to increment a reference count, thus "keeping the object alive".

Maybe you're okay with just capturing someProperty. Maybe not! The compiler doesn't know if you're using a closure in order to increment the reference, so it makes you be explicit about your intentions.

Not only is that a non-issue with structs, but so is the possibility of mutation, which is strictly disallowed.

Let's say you wanted anotherMethod to allow mutation of any kind, in a struct. You could start by marking it as mutating…

struct SomeStruct {
  func someMethod(completion: (() -> Void)?) {}

  mutating func anotherMethod() {
    someMethod {
      self
    }
  }
}

…but no, that's an error:

Escaping closure captures mutating 'self' parameter

Capture self, though…

  mutating func anotherMethod() {
    someMethod { [self] in
      self
    }
  }

…and that's fine.

And it's also the only option Swift allows. When you use an escaping closure from within a struct, you can only use an immutable capture of an instance. i.e. [self] in is implicit, for nonmutating methods.

This can yield unexpected results. Be careful.

struct SomeStruct {
  var someProperty = 1

  func anotherMethod() {
    DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
      print(someProperty)
    }
  }
}

var s = SomeStruct()
s.anotherMethod() // 1, even though it's printed after the following reassignment 
s.someProperty = 2
s.anotherMethod() // 2

I think it helps to think about what method syntax is shorthand for.

s.anotherMethod()

is really

SomeStruct.anotherMethod(s)()

You can visualize the immutability there, because there's no &.

like image 26
Jessy Avatar answered Sep 27 '22 21:09

Jessy