Structures and classes

Structs are value types and classes are reference types.

Value type and reference type

  • The essence of value types: Assignment means copying by value. That is, each value type variable holds a value that is independent. A type that has this behavior is called value semantics.
  • The essence of reference types: Variables do not contain the “thing” itself, but hold a reference to the “thing”. Other variables can also contain references to the same instance and can be modified by any variable pointing to it. Types that have these properties are called reference Semantics.

variability

  • Understand the use of attributes and variablesletvarThe key to all the different combinations of is to remember two things:
    • The value of a variable of type class, which is a reference to an instance; The value of a variable of type struct is the struct instance itself.
    • Modifying a structure’s properties, even if it is a multi-layer nested property, is equivalent to assigning a new structure instance to a variable.

Variable method

  • It’s used on structuresfuncThe normal method defined by the keyword cannot modify any properties of the structure. That’s because it was passed in implicitlyselfParameter, which is immutable by default. We must use it explicitlymutating funcKeyword to create a mutable method.

Inout parameters

  • although&The notation might remind you ofCObjective-CIn theThe addressing operator, or theC++The reference passing operator in, but inSwiftIn, its role is not the same. Just like with normal parameters,SwiftThe inout arguments passed in are still copied, but when the function returns, the values of those arguments are overwritten with the original values. That is, even if multiple changes are made to an inout parameter in a function, only one change is noticed to the caller, when a new value overwrites the old one. Similarly, the caller will notice a change (both willSet and didSet observer methods will be called) even if the function does not modify the inout argument at all.

The life cycle

  • Structures do not have multiple owners. The life cycle of a structure is bound to the life cycle of the variable that contains the instance of the structure. When a variable leaves scope, its memory is freed and the structure instance is destroyed.
  • Swift usedAutomatic Reference Counting (ARC)To track the reference count of an instance. When the reference count drops to zero (for example, when all variables containing references are out of scope or set to nil), Swift at runtime calls the object’s deinit method and frees memory. Therefore, classes can be used to implement shared objects that need to be cleaned up when they are eventually released, such as file handles (the underlying file descriptor must be closed at some point), orview controller(You may need to do various cleanup tasks, such as unlogging observers).

A circular reference

  • Circular references occur when two or more objects have strong references to each other, and they cannot be released (unless the developer explicitly breaks the loop). This can cause memory leaks and make potential cleanup tasks impossible to perform.
  • Circular references can occur in a variety of ways: from two objects strongly referencing each other, to complex loops made up of many objects, and to trapping objects in closures.

A weak reference

  • To break the circular reference, we need to make one of the references weak orunownedReferences. Assigning an object to a weak reference variable does not change the instance reference count. In Swift, the weak reference variable isZero (zeroing)Once the object to which it points is destroyed, the variable is automatically set to nil. This is why weak reference variables must be optional values.
  • Weak references are very useful when using proxies, and are common in Cocoa. A proxy object (for example, a table view) needs a reference to its proxy, but it should not own the proxy, or a circular reference might be generated. Therefore, references to the proxy are usually weak references, and another object (for example, a View Controller) is responsible for ensuring that the proxy object actually exists when needed.

Unowned reference

  • Sometimes we want a reference to be a weak reference, but not an optional value. In this case, you can useunownedThe keyword.
  • forunownedQuote, it is our responsibility to ensure that the life cycle of the quote is longer than that of the quote.
  • In the object, the Swift runtime uses another reference count to traceunownedReferences. When an object does not have any strong references, all resources are released (for example, references to other objects). However, as long as there are objectsunownedIf a reference exists, its own memory will not be reclaimed. This memory is marked as invalid, sometimes referred to as zombie memory. Once it’s marked as zombie memory, as soon as we try to access thisunownedReference, a runtime error occurs.

Closures and circular references

  • Classes are not the only reference type in Swift. Functions (including closures) are also reference types. If a closure captures a variable of reference type, a strong reference to that variable is held in the closure.
  • You can use a capture list to explicitly control how values are captured (such as weak or unowned) in a closure

Choose between an unowned reference and a weak reference

  • If objects have an independent lifetime (that is, you can’t guarantee that one object will live longer than the other), then weak references are the only safe choice.
  • On the other hand, an unowned reference is usually more convenient if you can guarantee that an object with a non-strong reference has the same or even longer lifetime as the object that holds the reference. Because its type does not require an optional value and can be declared as a let, a weak reference must be an optional value declared with var. It is common to have the same life cycle, especially if the two objects have a parent-child relationship. When the parent uses strong references to control the life cycle of its children, and we can be sure that no other object knows about the existence of the child, the child’s reference to the parent can be unowned.
  • An unowned reference is also less expensive than a weak reference, and it is slightly faster to access properties or call method pairs. However, this advantage should only be considered in code paths that are very efficient.
  • The disadvantages of using unowned references are also obvious. If you misjudge the life cycle of an object, your program is likely to crash. Personally, we often find ourselves preferring weak references even when we can use unowned.

Make a choice between a structure and a class

  • To share ownership of an instance, we must use classes. Otherwise, we can use structures.

Classes with value semantics

  • First we declare all the properties as lets, making them immutable. Second, to avoid reintroducing any mutable behavior because of subclasses, we disable subclassing by marking the class final:

    final class ScoreClass {
      let home: Int
      let guest: Int
      init(home: Int.guest: Int) {
        self.home = home
        self.guest = guest
      }
    }
    Copy the code

A structure with reference semantics

  • Be careful when storing references in a structure, because doing so often results in unexpected behavior. But in some cases, it’s intentional, and it’s exactly what you want.

Copy-on-write optimization

  • Copy-on-write means that data in a structure is initially shared between variables: a copy of the data occurs only when one of the variables modifies its data.

The tradeoff of copy at write time

  • One advantage of value types is that they do not incur reference-counting overhead. However, because the structure copied in advance depends on a reference stored internally, each copy of the structure increases the reference count for that internal reference. In effect, we are giving up the advantage that value types do not require reference counting in order to mitigate the potential cost of the replication semantics of value types.

Implement copy-on-write

struct HTTPRequest {
  fileprivate class Storage {
    var path: String
    var headers: [String: String]
    init(path: String.headers: [String: String]) {
      self.path = path
      self.headers = headers
    }
  }
  
  private var storage: Storage
  
  init(path: String.headers: [String: String]) {
    storage = Storage(path: path, headers: headers)
  }
}

extension HTTPRequest.Storage {
  func copy(a) -> HTTPRequest.Storage {
    print("Making a copy...")  // Debug statements
    return HTTPRequest.Storage(path: path, headers: headers)
  }
}

extension HTTPRequest {
  private var storageForWriting: HTTPRequest.Storage {
    mutating get {
      if !isKnownUniquelyReferenced(&storage) {
        self.storage = storage.copy()
      }
      return storage
    }
  }
  
  var path: String {
    get { return storage.path }
    set{}}var headers: [String: String] {
    get { return storage.headers }
    set{}}}Copy the code
var req = HTTPRequest(path: "/home", headers: [:])
var copy = req
for x in 0.5 {
  req.headers["X-RequestId"] = "\(x)"
} // Making a copy
Copy the code