Joe's Blog

iOS 開發筆記

NSObject 鏈式 Setter

發佈於 2020-03-01
2026-06-13 更新

UIKit 開發久了,最煩的事情之一就是初始化一個 View:

1
2
3
4
5
let subView: UIView = .init(frame: .zero)
subView.translatesAutoresizingMaskIntoConstraints = false
subView.clipsToBounds = true
subView.layer.cornerRadius = 10
subView.backgroundColor = .black

五個屬性,五行,subView 重複出現五次。這還只是一個 View,整個畫面十幾個元件疊下來,viewDidLoad 就已經半條命了。


核心想法

Swift 的 WritableKeyPath 可以讓你用 \.propertyName 的語法指向某個屬性,配合 @discardableResult 讓方法回傳 self,就能串起來一路點下去。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
extension NSObjectProtocol {
    @discardableResult
    func setup<Value>(_ keypath: WritableKeyPath<Self, Value>, value: Value) -> Self {
        var result = self
        result[keyPath: keypath] = value
        return result
    }

    @discardableResult
    func setup<Value>(_ keypath: WritableKeyPath<Self, Value>, condition: () -> Value) -> Self {
        var result = self
        result[keyPath: keypath] = condition()
        return result
    }
}

兩個多載:


使用起來長這樣

1
2
3
4
5
6
7
let subView: UIView = .init(frame: .zero)
    .setup(\.translatesAutoresizingMaskIntoConstraints, value: false)
    .setup(\.clipsToBounds, value: true)
    .setup(\.layer.cornerRadius, value: 10)
    .setup(\.backgroundColor) {
        Bool.random() ? .black : .white
    }

subView 只出現一次,後面全部是屬性設定,視覺上很清楚這整塊都是在初始化同一個物件。


為什麼用 NSObjectProtocol 而不是 NSObject

因為 NSObjectProtocol 的覆蓋範圍更廣,UIViewCALayerUIBarButtonItem 這些都進得來。用 NSObject 的話有些類別不繼承它,反而漏掉。


限制

WritableKeyPath 要求屬性是 varlet 屬性無法用這個方式設定,這是語言層面的限制,繞不過去。


這個技巧在 Swift 4.x / 5.x 初期的 UIKit 專案裡很流行,寫起來比 Builder Pattern 輕,比裸寫乾淨。雖然 SwiftUI 時代用的機會少了,但只要還在寫 UIKit,這個 extension 就值得放在 toolbox 裡。


本文使用 Claude 共同完成