Utility functions in Swift
Bài đăng này đã không được cập nhật trong 5 năm
-
Đôi lúc cách duy nhất để tác động đến
statechung củacode basetạo ra những thay đổi lớn chẳng hạn như thay đổi kiến trúc code, thay đổi cách truyền data hoặc áp dụng cácframeworkmới. -
Ở bài viết này chúng ta cùng xem xét cách viết các
utility functionsnhỏ thực hiện các tác vụ phổ biến dễ dàng hơn với cácpatterngiản hơn để sử dụng.
1/ Configuration closures:
-
Một phần code trong các dự án được dành để xác định một số
objectnhất định để sử dụng . Đặc biệt là khi sử dụngobject orientUI như UIKit. Giống như chúng ta đã xem qua Encapsulating configuration code in Swift thì việc tìm ra những cách gọn gàng để viết riêng biệt những đoạn code đó có thể cải thiện sự minh bạch của logic cũng như phạm vi mà đoạn code được sử dụng. -
Ví dụ, chúng ta đang sử dụng
patternphổ biến bằng cách sử dụng self-executing closures như sau:
class HeaderView: UIView {
let imageView: UIImageView = {
let view = UIImageView()
view.translatesAutoresizingMaskIntoConstraints = false
view.contentMode = .scaleAspectFit
view.image = .placeholder
return view
}()
let label: UILabel = {
let view = UILabel()
view.translatesAutoresizingMaskIntoConstraints = false
view.numberOfLines = 2
view.font = .preferredFont(forTextStyle: .headline)
return view
}()
...
}
-
Cách code trên không có gì sai nhưng sẽ tốt hơn nếu chúng ta có thể giảm số lượng code được liên kết với từng
property. Có một số cách tiếp cận khác nhau mà chúng ta có thể thực hiện ở đây chẳng hạn như sử dụng các factory method thay vì tựself-executing closureshãy xem liệu chúng ta có thể viết mộtsimple utility funtionđể giúp chúng ta làmcodetrên gọn hơn trong khi vẫn sử dụng cùngpattern. -
Giới thiệu với các bạn chức năng gọi là
configure, nó sẽ lấy một giá trị mà chúng ta muốnconfigurevàclosuređể thực hiện công việcconfiguređó. Chúng ta có thể gói gọn tất cảconfigurecode của mình cũng như sẽ truyền tham số từ xa với từ khóainoutcho phép chức năng mới của chúng ta được sử dụng với các loại giá trị:
func configure<T>(
_ value: T,
using closure: (inout T) throws -> Void
) rethrows -> T {
var value = value
try closure(&value)
return value
}
- Giờ đây chúng ta có thể quay lại
HeaderViewcủa mình và làm choconfigurecodesubviewdễ đọc hơn:
class HeaderView: UIView {
let imageView = configure(UIImageView()) {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.contentMode = .scaleAspectFit
$0.image = .placeholder
}
let label = configure(UILabel()) {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.numberOfLines = 2
$0.font = .preferredFont(forTextStyle: .headline)
}
...
}
-
Một lợi thế của chức năng
configurecode của chúng ta là nó hoàn toàn có thể được sử dụng với bất kỳtypenào từUIViewđến bất kỳ loạiobject. -
Ví dụ, chúng tai sử dụng cùng một
patternnhư trên khi thiết lập giá trịURLRequestđược sử dụng để đồng bộ hóa dữ liệu khi người dùng kết nối WiFi:
struct SyncNetworkTask {
var request = configure(URLRequest(url: .syncEndpoint)) {
$0.httpMethod = "PATCH"
$0.addValue("application/json",
forHTTPHeaderField: "Content-Type"
)
$0.allowsCellularAccess = false
}
...
}
2/ Rethrowing functions:
- Một chi tiết của chức năng trên có thể dễ bị bỏ lỡ là nó được đánh dấu bằng từ khóa
rethrow. Những gì từ khóa đó làm là bảo trình biên dịchSwiftchỉ coi chức năng đó làthrownếuclosuređược truyền cho nó cũngthrow.
let webView = try configure(WKWebView()) {
let html = try loadBundledHTML()
try $0.loadHTMLString(html, baseURL: nil)
...
}
- Bất cứ khi nào chúng ta thiết kế bất kỳ
API nào chấp nhận cácsynchronous closuresthì chắc chắn nên xem xét việc đánh dấu các chức năng của chúng ta bằng các lần truy cập lại cho phép chúng tôithrow errorkhi cần.
3/ Reducing boilerplate:
- Bên cạnh việc chúng ta quy định các
code conventioncácutility functioncũng có thể giúp chúng ta tránh các lỗi phổ biến như thực hiện các tác vụ cụ thể hơn, chẳng hạn như xác địnhlayoutvàcolor.
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: view.topAnchor),
label.leadingAnchor.constraint(equalTo: view.leadingAnchor),
...
])
- Chúng ta có thể chọn cách đơn giản như
extension UIViewtự động chuẩn bị chế độ xem đã cho để sử dụng vớiauto layoutsau đó kích hoạt một loạt cácconstraint:
extension UIView {
func layout(using constraints: [NSLayoutConstraint]) {
translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate(constraints)
}
}
- Chỉ với
extensionnhỏ bé đó, chúng ta thực sự có thể làm chosource codecủa mình dễ đọc hơn:
let label = UILabel()
view.addSubview(label)
label.layout(using: [
label.topAnchor.constraint(equalTo: view.topAnchor),
label.leadingAnchor.constraint(equalTo: view.leadingAnchor),
...
])
- Hãy cùng xem một ví dụ khác mà chúng ta hướng đến để dễ dàng xác định
dynamic coloethích ứng với việc thiết bị của người dùng hiện đang chạy ởlight modehaydark mode:
let backgroundColor = UIColor { traitCollection in
switch traitCollection.userInterfaceStyle {
case .dark:
return UIColor(white: 0.15, alpha: 1)
case .light, .unspecified:
return UIColor(white: 0.85, alpha: 1)
}
}
- Chúng ta đang tìm cách xác định các cặp màu để sử dụng ở
light modehaydark mode. Đặt tên cho chức năng mớicolorPaircủa chúng ta và để nó chấp nhận mộtUIColorđể sử dụng cho chế độ slight modehaydark mode. Sau đó, chúng tôi sẽ gọi APIUIColortrả về màu thích hợp cho từngUIUserInterfaceStyle:
func colorPair(light: UIColor, dark: UIColor) -> UIColor {
UIColor { traitCollection -> UIColor in
switch traitCollection.userInterfaceStyle {
case .dark:
return dark
case .light, .unspecified:
return light
}
}
}
- Chúng ta khai báo
backgroundColorở trên của chúng ta như sau:
let backgroundColor = colorPair(
light: UIColor(white: 0.85, alpha: 1),
dark: UIColor(white: 0.15, alpha: 1)
)
- Kết hợp tính năng trên với một
utility functionkhác cho phép chúng ta xác định bất kỳUIColornào bằng cách sử dụng cú pháp dấu chấm :
extension UIColor {
static func grayScale(_ white: CGFloat,
alpha: CGFloat = 1) -> UIColor {
UIColor(white: white, alpha: alpha)
}
}
let backgroundColor = colorPair(
light: .grayScale(0.85),
dark: .grayScale(0.15)
)
- Trong khi viết các
utility functionnhư các function trên chúng ta có thể tự hỏi mình những câu hỏi như tại sao cácAPImặc định được thiết kế theo cách này? Giống như với hầu hết mọi thứ trong lập trình- các API tuyệt vời thường là về việc cân bằng một tập hợp các sự đánh đổi nhất định.
All rights reserved