Generic (lập trình tổng quát) — là cơ chế của Swift cho phép viết các kiểu và hàm với kiểu được tham số hóa
Các ý Chính
Generic (kiểu tổng quát) — là cấu trúc cú pháp trong Swift cho phép viết mã linh hoạt, có thể tái sử dụng với tham số hóa kiểu. Thay vì một kiểu cụ thể, một phần giữ chỗ được viết trong dấu ngoặc nhọn — thường là
Không có generics, bạn phải viết các hàm riêng cho từng kiểu: swapInts, swapStrings, swapDates. Một hàm generic swap thay thế cả ba biến thể bằng một khai báo duy nhất. Điều này giảm trùng lặp, cải thiện khả năng đọc và giảm khả năng xảy ra lỗi khi sao chép mã.
func swapValues<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}
var x = 10
var y = 20
swapValues(&x, &y)
Trình biên dịch Swift tạo mã chuyên biệt cho mỗi lần sử dụng cụ thể của generic. Điều này có nghĩa là hàm generic swapValues
Hàm generic là các hàm chấp nhận một hoặc nhiều tham số kiểu. Tham số kiểu được chỉ định sau tên hàm trong dấu ngoặc nhọn:
Một hàm có thể có nhiều tham số generic. Ví dụ, hàm findKey
func findKey<K: Hashable, V>(
for value: V,
in dictionary: [K: V]
) -> K? where V: Equatable {
for (key, dictValue) in dictionary {
if dictValue == value {
return key
}
}
return nil
}
Các ràng buộc K: Hashable và where V: Equatable đảm bảo rằng findKey chỉ có thể được gọi với từ điển có khóa hashable và giá trị equatable. Các ràng buộc này không phải thủ tục mà là cần thiết: không có Hashable thì không thể đánh chỉ mục tìm kiếm khóa, không có Equatable thì không thể so sánh value với dictValue.
where — là từ khóa trong Swift áp đặt các ràng buộc bổ sung lên các tham số generic. Không giống như chỉ định một giao thức trong dấu ngoặc nhọn (
Mệnh đề where được viết sau chữ ký hàm hoặc kiểu, trước dấu ngoặc nhọn mở. Trong where, bạn có thể chỉ định rằng T: Comparable & Hashable (đồng thời tuân thủ hai giao thức), hoặc T.U == Int (cụ thể hóa một associated type).
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
}
extension Container where Item: Comparable {
func isSorted() -> Bool {
// Implementation available only if Item: Comparable
return true
}
}
Phần mở rộng với where thêm phương thức chỉ cho các kiểu thỏa mãn điều kiện. Container
Associated Types (kiểu liên kết) — là cách để làm cho một giao thức trở nên tổng quát mà không cần chỉ định kiểu cụ thể tại thời điểm khai báo. Thay vì
Sử dụng where, bạn có thể thiết lập mối quan hệ giữa các associated types của các giao thức khác nhau. Ví dụ, bạn có thể yêu cầu Item của một giao thức khớp với Iterator.Element của giao thức khác. Điều này đảm bảo tương thích kiểu ở cấp độ trình biên dịch.
protocol StackProtocol {
associatedtype Element
mutating func push(_ item: Element)
mutating func pop() -> Element?
}
struct IntStack: StackProtocol {
typealias Element = Int
private var items: [Int] = []
mutating func push(_ item: Int) { items.append(item) }
mutating func pop() -> Int? { items.popLast() }
}
IntStack cụ thể hóa Element là Int bằng typealias. Trình biên dịch kiểm tra rằng tất cả các yêu cầu của giao thức được đáp ứng với kiểu này. Không có associated types, bạn sẽ phải viết StackProtocol
Mở rộng generic cho phép thêm các phương thức vào một kiểu generic với các ràng buộc bổ sung. Đây là mô hình “tuân thủ có điều kiện”, trong đó một kiểu chỉ có được chức năng trong những điều kiện nhất định. Subscript cũng có thể là generic.
Một subscript có thể là generic: nó chấp nhận các tham số generic trong dấu ngoặc nhọn. Điều này hữu ích cho truy cập an toàn đến các bộ sưu tập với chỉ mục tùy ý, nơi chỉ mục có thể thuộc các kiểu khác nhau.
extension Array where Element: Numeric {
func sum() -> Element {
return reduce(0, +)
}
}
extension Array {
subscript<Indices: Sequence>(indices: Indices) -> [Element]
where Indices.Element == Int {
return indices.map { self[$0] }
}
}
Cấu trúc này cung cấp sự kết hợp mạnh mẽ: phương thức sum chỉ xuất hiện trên các mảng số, và subscript với chỉ mục tùy ý hoạt động với bất kỳ chuỗi chỉ mục số nguyên nào. Trình biên dịch quản lý tầm nhìn của các thành viên này dựa trên các điều kiện where.
Lỗi khi sử dụng generic thường liên quan đến các ràng buộc không chính xác hoặc cố gắng sử dụng generic ở nơi cần kiểu cụ thể. Hãy xem ba tình huống phổ biến mà các nhà phát triển gặp phải.
Thêm tham số generic vào nơi mà kiểu cụ thể đủ dùng là một phản mẫu. Nếu một hàm luôn làm việc với String, không cần biến nó thành
Gọi một phương thức trên tham số generic mà không chỉ định rằng phương thức đó tồn tại là một lỗi.
// ❌ Error: value has no count method
func countElements<T>>(value: T) -> Int { value.count }
// ✅ Works: T: Collection guarantees count exists
func countElements<T: Collection>(value: T) -> Int { value.count }
Lỗi rõ ràng: generic
Câu hỏi Thường Gặp
Generic là cách viết mã hoạt động với mọi kiểu, mà không sao chép cùng một logic cho Int, String và các kiểu khác. Thay vì kiểu cụ thể, một phần giữ chỗ
Generic giữ lại thông tin về kiểu cụ thể tại thời điểm biên dịch, cho phép trình biên dịch kiểm tra kiểu và tạo mã tối ưu. Any là xóa kiểu: bất kỳ giá trị nào cũng có thể được truyền vào, nhưng kiểu bị mất và cần ép kiểu (as?).
Sử dụng cú pháp T: SomeProtocol trong dấu ngoặc nhọn hoặc mệnh đề where riêng where T: SomeProtocol. Các ràng buộc đảm bảo tham số generic hỗ trợ các phương thức và thuộc tính cụ thể.
Associated Type (associatedtype) là một phần giữ chỗ cho một kiểu bên trong giao thức. Giao thức không chỉ định một kiểu cụ thể mà khai báo một associatedtype, được cụ thể hóa trong quá trình triển khai: Array có Element, Dictionary có Key và Value.
Swift biên dịch mã generic thành phiên bản chuyên biệt cho từng kiểu cụ thể. Trong thực tế, một hàm generic cho Int không chậm hơn hàm viết tay cho Int. Không có chi phí trừu tượng.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm