Swinject: nó là gì, nguyên tắc Dependency Injection và cách hoạt động

Tác giả: IT Sectr Đã đăng: 2026-05-04 Thời gian đọc: 8 phút

Swinject là một container DI cho Swift, triển khai mẫu Dependency Injection trong các ứng dụng iOS. Framework tự động hóa việc tạo và tiêm phụ thuộc, loại bỏ việc quản lý đối tượng và factory thủ công. Theo Swinject trên GitHub, thư viện hỗ trợ Constructor Injection, Property Injection và Method Injection với hệ thống scopes linh hoạt để quản lý vòng đời.

Những điểm chính

  • Swinject — một container DI cho Swift tự động hóa việc tiêm phụ thuộc trong các dự án iOS.
  • Dependency Injection — một mẫu thiết kế nơi đối tượng nhận phụ thuộc từ bên ngoài thay vì tạo chúng bên trong.
  • Container — thành phần trung tâm của Swinject lưu trữ sổ đăng ký các dịch vụ đã đăng ký và factory của chúng.
  • Service — một sự trừu tượng dưới dạng giao thức mà container lưu trữ một triển khai cụ thể.
  • ObjectScope — một cơ chế xác định vòng đời của instance: graph, container hoặc transient.

Swinject và Dependency Injection là gì

Swinject là một container DI mã nguồn mở cho ngôn ngữ Swift, được thiết kế để đơn giản hóa việc tiêm phụ thuộc trong các ứng dụng cho iOS, macOS và watchOS. Framework sử dụng cách tiếp cận Service Locator: các dịch vụ được đăng ký trong một container trung tâm và container tự động giải quyết đồ thị phụ thuộc khi một instance được yêu cầu.

Dependency Injection (DI) là một mẫu thiết kế nơi đối tượng nhận phụ thuộc từ bên ngoài thay vì tạo chúng bên trong. Điều này làm giảm sự kết dính giữa các thành phần, đơn giản hóa kiểm thử đơn vị và cho phép thay thế triển khai mà không sửa đổi mã người tiêu dùng.

Theo Martin Fowler (2004), DI là một trường hợp cụ thể của Inversion of Control và được triển khai thông qua tiêm qua constructor, property hoặc method. Swinject tự động hóa quy trình này, loại bỏ nhu cầu viết factory và service locator thủ công.

Sử dụng Swinject trong các dự án có ba dịch vụ trở lên với các phụ thuộc chéo, nơi việc xây dựng đối tượng thủ công dẫn đến mã khởi tạo phình to và giảm khả năng kiểm thử.

Swinject tích hợp chặt chẽ với hệ sinh thái Apple và hỗ trợ tất cả các phiên bản Swift từ 3.0 trở đi. Framework tương thích với Objective-C thông qua các cầu nối, cho phép đưa nó vào các dự án hiện có viết bằng ngôn ngữ hỗn hợp mà không cần di chuyển mã hoàn toàn. Điều này đặc biệt quan trọng đối với các ứng dụng lớn có lịch sử phát triển hơn năm năm.

Cách hoạt động của container Swinject

Container Swinject được triển khai bởi lớp Container, lưu trữ sổ đăng ký các dịch vụ đã đăng ký. Khi phương thức resolve được gọi, container tạo một đối tượng, giải quyết tất cả phụ thuộc của nó một cách đệ quy thông qua đồ thị đăng ký.

Container và Service

Container là đối tượng trung tâm nơi các ánh xạ giữa sự trừu tượng và triển khai của chúng được đăng ký. Service là một giao thức xác định hợp đồng, trong khi Component là một lớp triển khai giao thức này. Việc đăng ký được thực hiện bằng phương thức register, nhận loại dịch vụ và một factory.

swift
let container = Container()
container.register(Networking.self) { _ in
    NetworkService()
}
let service = container.resolve(Networking.self)

Phương thức resolve trả về một instance của triển khai cụ thể đã được đăng ký cho giao thức đã chỉ định. Nếu một phụ thuộc chưa được đăng ký, container ném ra một lỗi nghiêm trọng để phát hiện vấn đề nhanh chóng trong quá trình phát triển.

Đăng ký và dịch vụ có tên

Mỗi đăng ký tạo một mục nhập với một hàm factory và một phạm vi đã chọn. Một dịch vụ có thể có nhiều đăng ký với các tên khác nhau, cho phép chọn một triển khai cụ thể theo tên — hữu ích cho các môi trường khác nhau (phát triển, staging, sản xuất).

Quy trình giải quyết phụ thuộc hoạt động đệ quy: khi container tạo một instance Component, nó phân tích bộ khởi tạo của nó và cho mỗi tham số gọi resolve với loại tương ứng. Nếu một phụ thuộc cũng có phụ thuộc riêng, quy trình tiếp tục cho đến khi toàn bộ đồ thị được xây dựng hoàn chỉnh. Độ sâu lồng nhau chỉ bị giới hạn bởi bộ nhớ khả dụng, nhưng trong thực tế hiếm khi vượt quá năm cấp.

Các phương pháp tiêm phụ thuộc trong Swinject

Swinject hỗ trợ ba phương pháp tiêm phụ thuộc chính, mỗi phương pháp có thể áp dụng tùy theo ngữ cảnh kiến trúc.

Constructor Injection

Constructor Injection tiêm phụ thuộc thông qua các tham số bộ khởi tạo. Đây là phương pháp ưa thích, đảm bảo rằng một đối tượng luôn ở trạng thái hợp lệ ngay từ khi tạo. Swinject tự động giải quyết tất cả phụ thuộc được truyền vào constructor.

swift
class LoginViewModel {
    private let authService: AuthProtocol

    init(authService: AuthProtocol) {
        self.authService = authService
    }
}

container.register(AuthProtocol.self) { _ in
    AuthService()
}
container.register(LoginViewModel.self) { r in
    LoginViewModel(authService: r.resolve(AuthProtocol.self)!)
}

Property Injection

Property Injection tiêm phụ thuộc bằng cách thiết lập thuộc tính của đối tượng sau khi khởi tạo. Nó được sử dụng khi một phụ thuộc tùy chọn hoặc không thể truyền qua constructor, ví dụ, khi làm việc với Storyboard, nơi view controller được tạo tự động. Swinject hỗ trợ chú thích @Inject để tiêm thuộc tính tự động trong thời gian chạy mà không cần gọi resolve rõ ràng.

Khi sử dụng Property Injection, điều quan trọng là đảm bảo phụ thuộc được thiết lập trước khi truy cập đối tượng lần đầu. Nếu không, thuộc tính sẽ vẫn là nil, dẫn đến sự cố không mong muốn. Swinject giải quyết vấn đề này thông qua cơ chế Implicitly Unwrapped Optional và xác thực nghiêm ngặt ở giai đoạn giải quyết đồ thị phụ thuộc.

Method Injection

Method Injection tiêm phụ thuộc thông qua các tham số phương thức. Nó được sử dụng cho các dịch vụ chỉ cần thiết để thực hiện một thao tác duy nhất và không nên được lưu trữ như trạng thái vĩnh viễn của đối tượng. Đây là phương pháp ít phổ biến nhất nhưng hữu ích cho callback.

Phạm vi trong Swinject và mục đích của chúng

ObjectScope là một cơ chế xác định vòng đời của một instance được tạo bên trong container Swinject. Framework cung cấp ba phạm vi tích hợp với khả năng tạo phạm vi tùy chỉnh thông qua ObjectScopeProtocol.

ObjectScope.graph

Phạm vi graph là giá trị mặc định. Mỗi lần gọi resolve tạo một instance mới chỉ tồn tại trong thời gian giải quyết đồ thị phụ thuộc. Đây là lựa chọn an toàn cho các dịch vụ không trạng thái vì nó loại bỏ rò rỉ bộ nhớ do lưu cache.

ObjectScope.container

Phạm vi container là một singleton trong container. Instance được tạo một lần ở lần resolve đầu tiên và được trả lại cho tất cả các yêu cầu tiếp theo. Phù hợp cho các dịch vụ có trạng thái chia sẻ: cache dữ liệu, logger, cài đặt ứng dụng.

ObjectScope.transient

Phạm vi transient tạo một instance mới mỗi lần gọi resolve mà không lưu cache. Được sử dụng cho các đối tượng nhẹ không cần tái sử dụng — ví dụ, các mô-đun xử lý một yêu cầu HTTP cụ thể.

Phạm viVòng đờiSử dụng khuyến nghị
graphTrong thời gian giải quyết đồ thịDịch vụ không trạng thái mặc định
containerToàn bộ vòng đời của containerSingleton: cache, logger, client mạng
transientKhông lưu cacheĐối tượng nhẹ sử dụng một lần

Swinject trong các dự án iOS

Việc tích hợp Swinject vào một dự án iOS thực tế bắt đầu bằng việc khởi tạo container khi ứng dụng khởi động — trong AppDelegate hoặc scene. Nên cấu trúc các đăng ký thông qua Assembly: một lớp hoặc cấu trúc riêng biệt nhóm các dịch vụ liên quan.

Theo một khảo sát của Swift Developer Community (2025), 43% nhà phát triển iOS sử dụng container DI trong các dự án thương mại để quản lý phụ thuộc của tầng mạng, kho lưu trữ và điều phối viên điều hướng. Swinject vẫn là giải pháp phổ biến nhất nhờ cú pháp tối thiểu và khả năng tương thích với Objective-C.

Storyboard Injection là một tính năng độc đáo của Swinject: container tự động tiêm phụ thuộc vào các view controller được tạo từ Storyboard mà không cần mã bổ sung trong AppDelegate. Tính năng này sử dụng một trình giải quyết đặc biệt được truyền vào UIStoryboard qua phương thức init(container:), nó chặn việc tạo view controller và tiêm các phụ thuộc đã đăng ký.

Trong các dự án lớn, Swinject có thể kết hợp với điều phối viên điều hướng: điều phối viên nhận container và tạo màn hình bằng cách giải quyết phụ thuộc của chúng qua resolve, duy trì một điểm cấu hình duy nhất cho toàn bộ scene.

Kiến trúc Assembly là mẫu được khuyến nghị để tổ chức các đăng ký. Mỗi Assembly nhóm các dịch vụ liên quan (ví dụ: NetworkingAssembly, DatabaseAssembly) và có thể phụ thuộc vào các Assembly khác. Khi khởi tạo container, tất cả Assembly được tải và đăng ký dịch vụ của chúng, cung cấp sự phân tách trách nhiệm rõ ràng và đơn giản hóa việc điều hướng qua cấu hình DI trong các dự án lớn với hàng chục dịch vụ.

Để gỡ lỗi đồ thị DI, Swinject cung cấp phần mở rộng SwinjectPropertyLoader, tải cấu hình từ tệp plist, và SwinjectStoryboard — tích hợp storyboard thông qua một phiên bản đặc biệt của UIStoryboard. Những công cụ này đặc biệt hữu ích khi di chuyển một dự án hiện có từ xây dựng đối tượng thủ công sang DI: nhà phát triển có thể dần dần đăng ký dịch vụ, kiểm tra đồ thị phụ thuộc thông qua kiểm thử và ghi nhật ký lỗi giải quyết mà không dừng phát triển tính năng chính.

Swinject cũng cung cấp tích hợp với RxSwift và Combine thông qua phần mở rộng SwinjectAutoregistration để giải quyết phụ thuộc tự động dựa trên loại tham số bộ khởi tạo mà không cần đăng ký factory rõ ràng. Điều này giảm lượng mã đăng ký cho các dịch vụ đơn giản: chỉ cần gọi container.register(ServiceProtocol.self) mà không chỉ định factory, và Swinject sẽ tự động xây dựng factory dựa trên phản chiếu Signal do môi trường thực thi Swift cung cấp. Cách tiếp cận này được khuyến nghị cho các dịch vụ có constructor chỉ chấp nhận các loại cơ bản và không yêu cầu logic tạo phức tạp.

Các câu hỏi thường gặp

Swinject khác gì so với các framework DI khác cho Swift?

Swinject được viết bằng Swift thuần túy không cần tạo mã hay phản chiếu. Không giống Needle, nó không yêu cầu tạo mã nguồn, và so với Dip, nó cung cấp hỗ trợ Storyboard Injection tích hợp, đơn giản hóa việc tích hợp vào các dự án UIKit hiện có.

Làm thế nào để cài đặt Swinject qua Swift Package Manager?

Thêm gói qua URL github.com/Swinject/Swinject thông qua Xcode trong menu File — Add Packages. Cũng có thể cài đặt qua CocoaPods và Carthage. Sau khi cài đặt, import mô-đun Swinject và tạo một instance Container.

Có thể sử dụng Swinject trong các dự án SwiftUI không?

Có, Swinject hoàn toàn tương thích với SwiftUI. Các phụ thuộc được tiêm thông qua bộ khởi tạo View hoặc thông qua Environment, nơi container được truyền như một EnvironmentObject. Swinject không phụ thuộc vào UIKit và hoạt động tốt như nhau với cả hai framework.

Làm thế nào để sử dụng Swinject cho kiểm thử đơn vị?

Tạo một container riêng cho kiểm thử, thay thế các dịch vụ thực bằng mock. Swinject cho phép ghi đè đăng ký mà không thay đổi mã người tiêu dùng. Mỗi kiểm thử nhận một container cách ly với một tập phụ thuộc tối thiểu.

Nên chọn phạm vi nào cho dịch vụ phân tích?

Đối với phân tích, sử dụng phạm vi container để tất cả màn hình gửi sự kiện qua một instance duy nhất. Điều này đảm bảo hàng đợi gửi thống nhất và hoạt động tổng hợp batch chính xác mà không trùng lặp dữ liệu giữa các người tiêu dùng khác nhau.

Tổng kết

  • Swinject — một container DI cho Swift tự động hóa việc tiêm phụ thuộc thông qua Container và ObjectScope.
  • Dependency Injection giảm sự kết dính mã, đơn giản hóa kiểm thử và cho phép thay thế triển khai mà không thay đổi người tiêu dùng.
  • Container — một sổ đăng ký dịch vụ hỗ trợ register để đăng ký và resolve để lấy instance.
  • Constructor Injection là phương pháp tiêm ưa thích, đảm bảo trạng thái hợp lệ của đối tượng.
  • ObjectScope quản lý vòng đời: graph (mặc định), container (singleton) và transient (không cache).
  • Storyboard Injection tự động tiêm phụ thuộc vào các scene UIKit mà không cần thiết lập thủ công.
  • Đối với kiểm thử đơn vị, sử dụng một container riêng với triển khai mock của các dịch vụ.

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