Tuỳ chỉnh pattern trong Swift
Bài đăng này đã không được cập nhật trong 6 năm
- Một trong những đặc điểm nổi bật của Swift là có nhiều tính năng gốc được implement bằng chính Swift thay vì phải tuỳ chỉnh cứng trong complier. Về lý thuyết thì điều đó rất tiện lợi và cho chúng ta linh hoạt trong tuỳ chỉnh cách hoạt động có hiệu quả cao cho công việc.
- Ở bài viết này chúng ta sẽ cùng nghiên cứu sâu hơn về việc lựa chọn và tuỳ chỉnh pattern phù hợp trong Swift để thấy chúng ta có thể xây dựng các custom pattern hoàn chỉnh và một số kỹ thuật thú vị chúng ta có thể sử dụng sau này.
1/ Ví dụ đơn giản:
Pattern matchinglà công việc khớp giá trị với cácpatternđược xác định trước đó thường để xác định ra nhánh code nào sẽ thực hiện công việc. VD: mỗi khi chúng taswitchdựa trên các giá trị có sẵn:
func items(in section: Section) -> [Item] {
switch section {
case .featured:
return dataSource.featuredItems
case .recent:
return dataSource.recentItems
}
}
- Bên trên chúng ta sử dụng enum
Sectionđể xác định 2patternđược tạo trước đó (featuredvàrecent), đây là một cách sử dụng thông thường về việcpattern matchingtrong swift, nó tí khi gây ra những ảnh hưởng không đáng có trong khi code. - Để nói rõ hơn, chúng ta định nghĩa
Patternstruct, chúng ta sẽ sử dụng nó để định nghĩaclosurecủa chúng ta.Closurenày sẽ lấy giá trị phù hợp và trả về kết quảBool:
struct Pattern<Value> {
let closure: (Value) -> Bool
}
structbên trên có vẻ đơn giản nhưng nó hoàn toàn cho phép chúng ta có thể định nghĩa các sắp xếp tuỳ chỉnhpatternbằng cách sử dụnggeneric type constaintsđể thêm cácstatic factory methodsđể tạo cácpatterncủa chúng ta.
extension Pattern where Value: Hashable {
static func any(of candidates: Set<Value>) -> Pattern {
Pattern { candidates.contains($0) }
}
}
- Tất cả các
pattern matchingtrongSwiftđều rất mạnh mẽ, với việc sử dụng operator~=để dùng làm đối số bên trái và các giá trị phù hợp bên phải.
func ~=<T>(lhs: Pattern<T>, rhs: T) -> Bool {
lhs.closure(rhs)
}
2/ Pha trộn và tuỳ chỉnh phù hợp:
- Nếu chúng ta đang làm việc trong app như mạng xã hội, khi sử dụng
LoggedInUserstruct để theo dõi date của user đã log in như id, friendId:
struct LoggedInUser {
let id: Identifier<User>
var friendIDs: Set<Identifier<User>>
...
}
- Bây giờ chúng ta muốn build 1 view controller để hiển thị bất kỳ số người sử dụng như một danh sách mà chúng ta muốn render các icon khác nhau phụ thuộc vào user thuộc loại nào. Quyết định đó được đưa ra bởi
switch statement:
private extension UserListViewController {
func resolveIcon(for userID: Identifier<User>) -> Icon {
switch userID {
case loggedInUser.id:
return .currentUser
case .any(of: loggedInUser.friendIDs):
return .friend
default:
return .anyUser
}
}
}
3/ Pattern so sánh:
- Tiếp tục mở rộng
Patternbằng cách thêm vào các chức năng. Chúng ta sẽ viết các extension sử dụng protocolComparablebao gồm 2method:
extension Pattern where Value: Comparable {
static func lessThan(_ value: Value) -> Pattern {
Pattern { $0 < value }
}
static func greaterThan(_ value: Value) -> Pattern {
Pattern { $0 > value }
}
}
- Đoạn code trên sẽ trở nên hữu dụng khi chúng ta muốn so sánh các giá trị hơn hoặc kém. Ở ví dụ này chúng ta chúng có thể kiểm tra user có vượt ngưỡng điểm của trò chơi không bằng cách sử dụng
switch:
func levelFinished(withScore score: Int) {
switch score {
case .lessThan(50):
showGameOverScreen()
case .greaterThan(highscore):
showNewHighscore(score)
default:
goToNextLevel()
}
}
4/ Covert key path trong các pattern:
- Một các sử dụng khác của việc hình thành các mẫu cực kì hữu dụng là sử dụng
key-path:
func ~=<T>(lhs: KeyPath<T, Bool>, rhs: T?) -> Bool {
rhs?[keyPath: lhs] ?? false
}
- Với đoạn code trên chúng ta có thể pha trộn
key-pathvới các kiểupatterncho phép chúng ta có thể giải quyết các đoạn code logic phức tạp chỉ với việc sử dụngswitch statement. - Ởđây chúng ta đã quyết định cách phân tích một dòng văn bản trong một danh sách phụ thuộc vào first character bằng cách sử dụng
Character typeđể tạo mẫu key-path, kết hợp với với các pattern phù hợp với enumKind
struct ListItemParser {
enum Kind {
case numbered
case unordered
}
let kind: Kind
func parseLine(_ line: String) throws -> ListItem {
// Here we're switching on an optional Character, which is
// the type of values that Swift strings are made up of:
switch line.first {
case .none:
throw Error.emptyLine
case \.isNewline:
return .empty
case \.isNumber where kind == .numbered:
return parseLineAsNumberedItem(line)
case "-" where kind == .unordered:
return parseLineAsUnorderedItem(line)
case .some(let character):
throw Error.invalidFirstCharacter(character)
}
}
}
- Để đoạn code trên hoạt động trơn tru, hãy định nghĩa 1 operator khác
==sẽ trả vềPatternkết hợpKeyPathvà một hằng số như sau:
func ==<T, V: Equatable>(lhs: KeyPath<T, V>, rhs: V) -> Pattern<T> {
return Pattern { $0[keyPath: lhs] == rhs }
}
- Chúng ta đã kết hợp
key-pathvới giá trị được tạo từpatternchúng ta chỉ cần đơn giản implement cách chúng ta tính toán level của giá trị giao hàng vớiDestinationnhư sau:
struct Destination {
var address: String
var city: String
var country: Country
}
extension Destination {
var shippingCost: ShippingCost {
switch self {
// Combining a key path with a constant value:
case \.city == "Paris":
return .free
// Using a nested key path as a pattern:
case \.country.isInEurope:
return .reduced
default:
return .normal
}
}
}
All rights reserved