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)
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" )
With @discardableResult
:
1 2 3 4 5 @discardableResult func addText (_ text : String ) -> Builder { } Builder ().addText("Hello" )
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.
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.