1. Core Principle of Method Chaining: Returning self

1. Why does returning self enable method chaining?

  • Continuity of Method Calls: In Swift, the return type of a method determines what can be called next. If a method returns the current instance (self), the following method call can act on that same instance.
  • Syntax Structure: Swift allows consecutive dot syntax, e.g., a().b().c(). Each method must return a type compatible with the next method’s calling context (typically the same type).

2. Example Code Explained:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Builder {
var result: String = ""

@discardableResult
func addText(_ text: String) -> Builder {
result += text + " "
return self
}

func build() -> String {
return result.trimmingCharacters(in: .whitespaces)
}
}

let builtString = Builder()
.addText("Hello")
.addText("World")
.build()

Key Points:

  • addText returns self of type Builder, so .addText("World") acts on the same instance.
  • build() returns a String, ending the chain.

2. Method Chaining with Closures

1. Returning self in Closures

  • Closure as a Parameter: A method can take a closure and still return self to enable chaining.

Example Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Adder {
var currentValue: Double = 0

func add(value: Double, then action: (Adder) -> Void) -> Adder {
currentValue += value
action(self)
return self
}

func getResult() -> String {
return String(format: "%.2f", currentValue)
}
}

let result = Adder()
.add(value: 1.111) { _ in }
.add(value: 2.222) { _ in }
.getResult()

Key Points:

  • The closure receives self, enabling internal state modification.
  • Returning self allows the next add call to proceed.

3. Optional Chaining Explained

1. Syntax

  • Syntax: optional?.propertyOrMethod()
  • Purpose: If the optional is nil, the chain safely returns nil without crashing.

Example Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Person {
var residence: Residence?
}

class Residence {
var address: Address?
}

class Address {
var street: String?
}

let person = Person()
person.residence = Residence()
person.residence?.address = Address()
person.residence?.address?.street = "Main St"

if let street = person.residence?.address?.street {
print(street)
} else {
print("Address not available")

Key Points:

  • If residence is nil, further access returns nil safely.
  • Each ? checks for nil before proceeding.

4. Method Chaining via Extensions

1. Syntax

  • Syntax: extension TypeName { ... }
  • Purpose: Add chainable methods to existing types.

Example Code:

1
2
3
4
5
6
7
8
9
10
11
12
extension String {
@discardableResult
func appendIfNotEmpty(_ text: String) -> String {
if !text.isEmpty {
return self + text
}
return self
}
}

let message = "Hello".appendIfNotEmpty(" World").appendIfNotEmpty("")
print(message) // "Hello World"

Key Points:

  • @discardableResult avoids compiler warnings if return value is unused.
  • The method returns a String, enabling further chaining.

5. Role and Importance of @discardableResult

1. What is @discardableResult?

  • Purpose: Marks that a method’s return value can be ignored.
  • Necessity: In chaining, intermediate returns (usually self) are often not explicitly used.

Code Comparison:

Without @discardableResult:

1
2
3
4
func addText(_ text: String) -> Builder {
// ...
}
_ = Builder().addText("Hello") // Required to suppress warning

With @discardableResult:

1
2
3
4
5
@discardableResult
func addText(_ text: String) -> Builder {
// ...
}
Builder().addText("Hello") // No warning

6. Real-World Use Cases

1. UI Building

1
2
3
4
5
let button = UIButton()
.setTitle("Login", for: .normal)
.setTitleColor(.blue, for: .normal)
.setBackgroundColor(.lightGray)
.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)

Key Points:

  • Each method returns UIButton to support chaining.
  • Methods like addTarget may need to be extended to support chaining.

2. Data Processing

1
2
3
4
let result = [1, 2, 3, 4, 5]
.filter { $0 % 2 == 0 }
.map { $0 * 2 }
.reduce(0, +)

Key Points:

  • Each high-order function returns a value that supports further chaining.
  • filter and map return arrays; reduce returns a final result.

7. Performance and Considerations

1. Performance Impact

  • Memory Usage: Intermediate objects (like arrays/strings) may increase memory usage.
  • Closure Captures: Avoid strong reference cycles by using [weak self] or [unowned self] inside closures.

2. Best Practices

  • Avoid Over-Chaining: Excessive chaining may hurt readability. Break complex logic into parts.
  • Error Handling: If methods can throw errors, use do-catch to handle gracefully.

8. Summary: Key Elements and Techniques

Element Explanation
return self Enables continuous method calls on the same instance.
@discardableResult Allows ignoring intermediate return values.
Optional Chaining ? Safely accesses optional properties/methods.
Extensions Adds chaining support to existing types.
Closure Chaining Allows flexible logic within chaining context.
Performance/Readability Balance code elegance and maintainability.