This article is concluded after reading the original chapter. Due to the need for me to carry out a certain summary refining, if there is improper place welcome readers to correct. If you have any questions about the content, welcome to discuss together.

In addition to the normal method, there are two special methods: computed properties and subscript scripts. Computed properties look similar to normal properties, but instead of taking up memory to store something, they are evaluated dynamically each time they are accessed. Subscript scripts are essentially methods, but they are defined and invoked differently than normal methods. For example, here is a structure representing a file that has a method to calculate the file size:

struct File {
let path: String  // File path

func computeSize(a) -> Int {
var size = 0
// There are some complex I/O and calculations
return size
}
}
Copy the code

ComputeSize can be a very time-consuming method because it has the potential to recursively call subdirectory methods. At this point we can consider caching the results, that is, storing the return value of the computeSize with a private attribute. Because you want to modify the properties of a structure in its methods, you need to mark the method mutating:

private var cachedSize: Int?

mutating func computeSize(a) -> Int {
guard cachedSize == nil else { return cachedSize! }
// If the cache does not exist, run the following code again
var size = 0
// There are some complex I/O and calculations
cachedSize = size  // Add to the cache
return size
}
Copy the code

This lazy loading + data caching pattern is very common in Swift, and a better solution is to use the lazy keyword:

lazy var size: Int? = {
var size: Int? = 0
// There are some complex I/O and calculations
return size
}()
Copy the code

The attribute marked lazy is mutable, so it must be declared as var. It is defined as a closure that executes immediately when the property is accessed for the first time (which is why the closure ends with a pair of parentheses), and the return value of the closure is assigned to the property.

However, both approaches have a drawback: the structure variable must be defined as var. Because it has mutating methods or lazy members inside it.

If we don’t want to cache and want to get the file size from a property instead of a function, we can define this property as a calculated property so that the value is recalculated every time it is accessed:

var size: Int? {
var size: Int? = 0
// There are some complex I/O and calculations
return size
}
Copy the code

Here we implement the get method, if we want to implement its set method, we need to separate it from the get method implementation:

var data: NSData? {
get {
return nil
}
set {
data = newValue
}
}
Copy the code

We can implement the didSet and willSet callbacks for both ordinary and computed properties. These callbacks are called before and after the set method is called, respectively. A common scenario is to do some initialization after an IBOutlet is connected:

class ViewController: UIViewController {
@IBOutlet weak var label: UILabel? {
didSet{ label? .textColor = .blackColor() } } }Copy the code

Reload the subscript script

You’ve probably seen subscripts in action: dictionaries look up elements with subscripts. Subscript scripts are also functions, but the syntax is weird. Subscript scripts can be read-only (using GET) or readable and writable (using GET and set). Like normal functions, subscript scripts can be overloaded and can have different types of arguments, as we saw when we used array slicing:

let fibs = [0.1.1.2.3.5]
let first = fibs[0] // The subscript argument is of type Int
let nums = fibs[1..<3]  // Subscript parameters are of type Range
      
print(nums)  // result: [1,1]
Copy the code

In Swift, the Range type represents a bounded Range: each Range variable has a start position and an end position. We can also extend the collection type so that its subscripts support only a certain semi-bounded interval between the start and end positions. First we define two new structures:

struct RangeStart<I: ForwardIndexType> {
let start: I
}

struct RangeEnd<I: ForwardIndexType> {
let end: I
}
Copy the code

We can define two convenience operators to represent semi-deterministic intervals. They are unary operators, a prefix operator and a suffix operator. In this way, the semi-deterministic interval where only the starting position is known can be expressed as x.. <, only the semi-deterministic interval of the end position can be expressed as.. The < x:

postfix operator. The < {}postfix func ..<<I: ForwardIndexType>(lhs: I) -> RangeStart<I> {
return RangeStart(start: lhs)
}

prefix operator. The < {}prefix func ..<<I: ForwardIndexType>(rhs: I) -> RangeEnd<I> {
return RangeEnd(end: rhs)
}
Copy the code

With that done, we can override the subscript for collection types:

extension CollectionType {
subscript(r: RangeStart<Self.Index- > >)SubSequence {
return self[r.start..<endIndex]
}

subscript(r: RangeEnd<Self.Index- > >)SubSequence {
return self[startIndex..<r.end]
}
}
Copy the code

Let’s test it out:

let fibs = [0.1.1.2.3.5]
print(fibs[2..<])  [1, 2, 3, 5]
print(fibs[..<4])  [0, 1, 1, 2]
Copy the code

In addition, semi-deterministic intervals can be used to implement a search function for pattern matching that looks for the first occurrence of a subset in a set:

extension CollectionType
where Generator.Element: Equatable.SubSequence.Generator.Element= =Generator.Element {
func search<S: SequenceType where S.Generator.Element == Generator.Element>
(pattern: S) -> Self.Index? {
return self.indices.indexOf {
// Each $0 here is a subscript of the set, so self[$0..<] is a progressively shorter string
self[$0..<].startsWith(pattern)  // If true is returned, the position at which pattern first appears is $0}}}Copy the code

We use the search method to find the position where a string first appears in a string, and based on that position we get all characters from the beginning of the string to that position:

let greeting = "Hello, world"
if let index = greeting.characters.search(",".characters) {
print(String(greeting.characters[..<index]))
}
Copy the code

This approach can be understood as an inefficient, simple version of the KMP algorithm.

Subscript advanced

Subscript scripts can not only be overloaded (taking different types of arguments), they can also take multiple arguments (which is exactly the same as functions). We can extend the dictionary type so that its subscript has a default return value, which is returned if key is not found. This default is not needed in the set method of the subscript, because newValue cannot be of an optional type:

extension Dictionary {
subscript(key: Key, or or: Value) - >Value {
get {
return self[key] ?? or  // Use the null conjunction operator to return or if self[key] is nil
}

set {
self[key] = newValue
}
}
}
Copy the code

Such subscript scripts can simplify a lot of code. For example, we implement a function that counts the frequency of occurrences of all elements in an array. The result is expressed in a dictionary, where the key is the element and the value is the number of occurrences of the element:

extension SequenceType where Generator.Element: Hashable {
func frequencies(a)- > [Generator.Element:Int] {
var result: [Generator.Element:Int] = [:]
for x in self {
result[x, or: 0] + +}return result
}
}
Copy the code

If you think that result[x, or: 0]++ is equivalent to 0++ when result[x, or: 0] = 0, you will see that this does not add a key-value pair to the dictionary at all. In fact, its implementation is as follows:

result[x, or: 0] = result[x, or: 0] + 1
Copy the code

That is, we call the set and get subscripts first. You can use breakpoints to see for yourself.

Test it out:

var array = [100.200.100.300.200.200]
print(array.frequencies())   // Output result: [100:2, 200:3, 300:1]
Copy the code