Generic — bản chất của kiểu tổng quát và tham số T

Tác giả: IT Sectr Đã đăng: 2026-06-18 Thời gian đọc: 10 phút

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 . Thay vì sao chép mã cho Int, String và các cấu trúc tùy chỉnh, nhà phát triển tạo một giải pháp tổng quát duy nhất hoạt động với mọi kiểu, đồng thời duy trì tính phân loại chặt chẽ. Trình biên dịch thay thế kiểu cụ thể tại điểm sử dụng, đảm bảo an toàn mà không mất hiệu suất. Theo Swift Book, 2025, tham số generic là một trong những tính năng chính của ngôn ngữ, là nền tảng của thư viện chuẩn và SwiftUI.

Các ý Chính

  • Generic — cơ chế lập trình tổng quát với tham số kiểu
  • Hàm với tham số generic hoạt động với mọi kiểu mà không sao chép mã
  • Ràng buộc where thu hẹp phạm vi generic vào các kiểu đáp ứng điều kiện
  • Associated Types trong giao thức cho phép giao thức trở nên tổng quát
  • Trình biên dịch tạo mã chuyên biệt cho từng kiểu cụ thể

Generic trong Swift là gì?

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à , nhưng có thể dùng bất kỳ tên nào. Generics là nền tảng của SwiftUI (View, some View), thư viện chuẩn (Array, Optional) và Combine.

Tại sao cần Generics

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ã.

swift
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 không có chi phí so với hàm viết tay cho Int. Swift không sử dụng xóa kiểu như Java — generics tồn tại cả lúc biên dịch lẫn thời gian chạy (mặc dù tối ưu có thể chuyên biệt hóa chúng).

Hàm Tổng quát với Tham số T

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: . Chúng có thể được sử dụng trong chữ ký: kiểu đối số, kiểu trả về và bên trong thân hàm.

Nhiều Tham số Kiểu

Một hàm có thể có nhiều tham số generic. Ví dụ, hàm findKey nhận một từ điển với khóa K và giá trị V. Mỗi tham số kiểu là duy nhất và có thể được sử dụng trong các ràng buộc.

swift
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.

Ràng buộc Kiểu qua where

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 (), where cho phép diễn đạt các điều kiện phức tạp hơn: tuân thủ nhiều giao thức, mối quan hệ giữa các tham số kiểu và tinh chỉnh associated types.

Cú pháp where

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).

swift
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 sẽ nhận được phương thức isSorted, trong khi Container thì không, vì Any không phải Comparable. Đây là một cơ chế mạnh mẽ cho chức năng có điều kiện.

Associated Types trong Giao thức

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ì , giao thức khai báo một associatedtype, được cụ thể hóa trong quá trình triển khai. Điều này đặc biệt quan trọng đối với các bộ sưu tập: giao thức Sequence không biết chuỗi sẽ chứa những phần tử nào.

Liên kết Associated Types qua where

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.

swift
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 với một giao thức generic, nhưng Swift sử dụng associated types để nhất quán với các cầu nối Objective-C và khả năng đọc tốt hơn.

Generic trong Mở rộng và Subscript

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.

Subscript 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.

swift
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 Thường Gặp khi Làm việc với Generic

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.

Tham số hóa Quá mức

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 . Điều này làm phức tạp mã mà không có lợi ích. Generic chỉ hợp lý khi kiểu thực sự thay đổi, không phải cho sự linh hoạt trừu tượng trong tương lai.

Thiếu Ràng buộc where

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. không thể gọi .count nếu không chỉ định T: Collection. Luôn thêm ràng buộc cho các tham số kiểu, nếu không trình biên dịch sẽ từ chối mã.

swift
// ❌ 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ó thể là bất kỳ kiểu nào, và trình biên dịch không biết liệu nó có count hay không. Ràng buộc Collection giải quyết vấn đề. Tương tự cho Equatable, Hashable, Numeric — luôn chỉ định giao thức nếu bạn dự định gọi phương thức hoặc toán tử trên tham số generic.

Câu hỏi Thường Gặp

Nói đơn giản thì Generic trong Swift là gì?

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ỗ được sử dụng và được thay thế bằng kiểu thực tế tại điểm sử dụng.

Generic khác Any như thế nào?

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?).

Làm thế nào để ràng buộc Generic với một giao thức?

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 trong giao thức là gì?

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.

Generic nhanh hơn hay chậm hơn kiểu cụ thể?

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

  • Generic — cơ chế lập trình tổng quát với tham số kiểu
  • Hàm và kiểu với tham số generic thay thế nhiều triển khai trùng lặp
  • Ràng buộc where thu hẹp phạm vi generic vào các điều kiện cần thiết
  • Associated Types cho phép giao thức tổng quát mà không cần cụ thể hóa tại khai báo
  • Mở rộng với where chỉ thêm chức năng khi điều kiện được đáp ứng
  • Trình biên dịch tạo mã chuyên biệt — generic không thêm chi phí
  • Tham số hóa quá mức là phản mẫu — chỉ dùng generic khi kiểu thực sự thay đổi

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.

Thảo luận dự án

Đọc thêm