Thư viện trong hệ sinh thái của Apple
Trước khi đi sâu vào phát triển kiến trúc đã mô tả trước đó, cần giải thích một số kiến thức cơ bản. Cụ thể, chúng ta cần hiểu rõ loại thư viện sẽ được sử dụng để xây dựng dự án này và cách hoạt động của nó.
Trong hệ sinh thái của Apple hiện nay, chúng ta có hai lựa chọn chính khi tạo một thư viện:
- Thư viện liên kết động (Dynamically Linked Library), trước đây được gọi là Cocoa Touch Framework, hiện nay được gọi đơn giản là Framework.
- Thư viện liên kết tĩnh (Statically Linked Library), còn được biết đến với tên Static Library.
Ngoài ra, cần nhắc đến Swift Package và Swift Package Manager (SPM). SPM là một phần của hệ sinh thái Swift chứ không phải trực tiếp của Apple. Một Swift package mô tả cách mã nguồn nên được gắn vào một target, và người tạo package có thể lựa chọn liên kết tĩnh hoặc động. Mặc định, SPM sử dụng liên kết tĩnh, và giống như một Framework, nó cũng có thể đính kèm thêm tài nguyên bổ sung.
SPM không được thiết kế để chia sẻ các file thực thi đã biên dịch sẵn, mà nhằm mục tiêu chia sẻ mã nguồn một cách dễ dàng. Tuy nhiên, ngày nay cũng phổ biến việc chia sẻ một XCFramework (framework đã biên dịch cho nhiều nền tảng) thông qua SPM — trong trường hợp này, SPM chỉ đóng vai trò như một lớp bao bên ngoài cho file nhị phân đính kèm.

Thư viện là gì?
Theo Apple:
“Thư viện định nghĩa các symbol (ký hiệu) không được xây dựng như một phần của target của bạn.”
Vậy symbol là gì?
Symbol đề cập đến các đoạn mã hoặc dữ liệu trong một tệp nhị phân (binary).
Các loại thư viện:
Liên kết động (Dynamically linked)
- Dylib: Thư viện có tệp nhị phân riêng theo định dạng Mach-O (sẽ được giải thích sau).
➤ Định dạng:.dylib
- Framework: Một bundle chứa tệp nhị phân và các tài nguyên khác mà tệp nhị phân đó cần trong thời gian chạy.
➤ Định dạng:.framework
- TBD (Text-Based Dynamic Library Stub): Thư viện dạng stub (chỉ chứa symbol) dưới dạng văn bản, không bao gồm nhị phân vì nhị phân thực sự sẽ nằm trên hệ thống đích. Apple dùng TBD để cung cấp SDK nhẹ cho quá trình phát triển.
➤ Định dạng:.tbd
- XCFramework: Bắt đầu từ Xcode 11, Apple giới thiệu XCFramework để gom nhóm các framework cho nhiều nền tảng khác nhau (macOS, iOS, iOS simulator, watchOS, v.v.).
➤ Định dạng:.xcframework
Liên kết tĩnh (Statically linked)
- Archive: Tập hợp các tệp đối tượng (object files) đã được biên dịch, chứa mã nhị phân.
➤ Định dạng:.a
- Framework: Framework chứa nhị phân tĩnh hoặc archive tĩnh kèm theo các tài nguyên mà thư viện cần.
➤ Định dạng:.framework
- XCFramework: Giống như dạng liên kết động, XCFramework cũng hỗ trợ thư viện liên kết tĩnh.
➤ Định dạng:.xcframework
Hiểu đơn giản:
Framework có thể xem là một bundle độc lập có thể được đính kèm vào project, với nhị phân riêng của nó.
Tuy nhiên, bản thân nhị phân không thể chạy độc lập, nó phải là một phần của một target có thể thực thi (runnable target).
Vậy sự khác biệt chính xác là gì?
Câu trả lời sẽ nằm ở phần tiếp theo, nơi sẽ giải thích chi tiết về sự khác biệt giữa các dạng liên kết tĩnh và động, cách hoạt động của chúng trong quá trình build và runtime.
Thư viện liên kết động (dynamic) vs tĩnh (static)
Sự khác biệt chính giữa thư viện tĩnh và thư viện động nằm ở Inversion of Control (IoC – Đảo ngược kiểm soát) và cách chúng được liên kết với tệp thực thi chính.
- Khi bạn sử dụng một thư viện tĩnh, bạn kiểm soát nó vì mã trong thư viện sẽ được gắn trực tiếp vào file thực thi trong quá trình biên dịch (liên kết).
- Ngược lại, khi sử dụng thư viện động, bạn giao quyền điều khiển cho framework, vì nó được liên kết động (dynamically) vào tiến trình ứng dụng khi khởi động.
🧠 Giải thích chi tiết hơn:
- Thư viện tĩnh (static library), trên iOS không thể chứa gì ngoài mã thực thi, trừ khi nó được đóng gói trong một static framework.
- Framework (dù là tĩnh hay động) có thể chứa mọi thứ bạn cần: storyboards, XIBs, ảnh, dữ liệu, v.v.
🌀 Inversion of Control trong dynamic framework
- Khi bạn gọi một hàm trong dynamic framework, bạn không trực tiếp điều khiển nó.
- Ví dụ: bạn khởi tạo một class trong framework rồi gọi một method. Khi gọi, bạn trao quyền điều khiển cho framework để xử lý hành động đó, sau đó framework sẽ trả kết quả về cho bạn.
➡️ Đây chính là mô hình Inversion of Control: mã của bạn không kiểm soát luồng thực thi cuối cùng, mà là framework làm điều đó.
📁 Umbrella File vs Bridging Header
- Dynamic framework không hỗ trợ
Bridging-Header
như dự án Swift thường thấy. - Thay vào đó, nó sử dụng
umbrella.h
– một tập tin header chính chứa tất cả các import từ Objective-C (giống như bridging-header). - Thường được đặt tên theo framework, ví dụ:
MyFramework.h
.
🛠 Nếu không muốn tự thêm thủ công các file .h
, bạn có thể đánh dấu chúng là public, Xcode sẽ tự sinh ra header khi build.
- Với Swift, Xcode cũng sinh ra file
MyFramework-Swift.h
và expose các class/method public thông quaswiftmodule
.
Bạn có thể kiểm tra file umbrella.h
và swiftmodule
trong thư mục DerivedData sau khi framework đã được biên dịch.
🧱 Static Library
- Được gắn trực tiếp vào file thực thi trong quá trình liên kết (linking), vì bản thân thư viện đã chứa mã nhị phân được biên dịch sẵn.
- Không cần umbrella file hay IoC.
- Việc gọi hàm/method từ static library là trực tiếp, không qua lớp trừu tượng như dynamic framework.
📌 Lưu ý
- Các class, struct, method cần được đánh dấu là
public
nếu bạn muốn sử dụng chúng từ bên ngoài framework hoặc library. - Và dĩ nhiên, chỉ nên public những gì cần thiết cho người dùng framework – tránh expose không cần thiết gây rối và khó bảo trì.
👉 Tóm lại:
- Static library → nhúng trực tiếp, bạn kiểm soát.
- Dynamic framework → tách biệt, framework kiểm soát (IoC).
- Chọn loại nào tuỳ theo yêu cầu: tốc độ biên dịch, khả năng tái sử dụng, kích thước app, cách tổ chức codebase.
ƯU ĐIỂM & NHƯỢC ĐIỂM
Giờ hãy cùng xem qua một số ưu và nhược điểm của thư viện liên kết động (dynamic).
🔷 Thư viện động (Dynamic Library)
✅ ƯU ĐIỂM:
- Chỉ mở khi cần thiết → Nếu người dùng không mở phần app đó thì framework có thể không bao giờ được load (sử dụng
dlopen
). - Có thể liên kết bắc cầu với các thư viện động khác một cách dễ dàng.
- Có thể thay thế mà không cần biên dịch lại file thực thi chính, chỉ cần thay thế framework bằng phiên bản mới.
- Nạp vào vùng nhớ khác với vùng nhớ của file thực thi chính → giúp tách biệt rõ ràng.
- Có thể dùng chung giữa các ứng dụng → đặc biệt hữu ích với các thư viện hệ thống.
- Có thể nạp một phần → chỉ nạp những symbol cần thiết vào bộ nhớ (
dlsym
). - Hỗ trợ nạp chậm (lazy loading) → chỉ nạp các đối tượng khi có yêu cầu sử dụng.
- Tái sử dụng giữa các targets → ví dụ: app iOS và các app extension, app watchOS và extension của nó.
- Framework có thể dọn dẹp tài nguyên khi đóng lại (
dlclose
). - Tiềm năng tăng tốc khởi động app, nếu thư viện được nạp một cách lazy.
- Mergeable libraries (tính năng của Xcode 15): gộp nhiều dynamic libraries thành một framework duy nhất → tận dụng ưu điểm của cả static và dynamic.
- Tách biệt mã nguồn rõ ràng → giúp tăng tốc độ biên dịch của toàn ứng dụng.
❌ NHƯỢC ĐIỂM:
- Khởi động app chậm hơn vì mỗi dynamic library cần được mở và nạp vào bộ nhớ riêng biệt.
- Trong trường hợp tệ nhất, nếu thư viện có thuật toán khởi tạo phức tạp (
dlopen initializer
), startup sẽ càng chậm.
- Trong trường hợp tệ nhất, nếu thư viện có thuật toán khởi tạo phức tạp (
- Ứng dụng phải copy đầy đủ tất cả các dynamic library liên quan – nếu thiếu, app sẽ crash ngay khi mở hoặc khi thư viện được gọi (lỗi
dyld library not found
). - Kích thước binary lớn hơn so với static:
- Compiler không thể lược bỏ các symbol trong dynamic lib như với static lib.
- Nếu thay thế dynamic lib bằng phiên bản mới mà giao diện (interface) khác đi, file thực thi chính có thể bị lỗi.
- Gọi hàm từ dynamic lib chậm hơn vì nó nằm ở vùng nhớ khác, gọi qua interface trung gian.
- Thời gian khởi động ứng dụng lâu hơn, nếu tất cả các dynamic lib được mở cùng lúc khi app start.
📦 Thư viện tĩnh (Static Library)
✅ ƯU ĐIỂM:
- Khởi động app nhanh hơn, vì chỉ có một file thực thi duy nhất được nạp vào bộ nhớ.
- Trở thành một phần của file thực thi chính, nên app không thể bị crash trong lúc khởi động hoặc runtime vì thiếu thư viện.
- Kích thước file thực thi nhỏ hơn tổng thể, vì các symbol không sử dụng có thể bị loại bỏ (stripped).
- Tốc độ gọi hàm nhanh vì không có sự khác biệt giữa code của thư viện và code của app – tất cả đều là một phần của cùng một executable.
- Trình biên dịch (compiler) có thể thực hiện các tối ưu bổ sung trong quá trình build file thực thi chính.
❌ NHƯỢC ĐIỂM:
- Không được phép liên kết bắc cầu (transitive linking):
- Nếu một thư viện tĩnh được liên kết nhiều lần (qua nhiều module), nó sẽ bị đưa vào nhiều lần.
- Khi đó app sẽ phải quyết định sử dụng phiên bản nào, dễ gây lỗi khi chạy.
- Phải biên dịch lại toàn bộ app, ngay cả khi chỉ cập nhật thư viện mà giao diện thư viện không thay đổi.
- Thời gian build chậm hơn, do không có interface rõ ràng như dynamic lib, nên hệ thống build phải tính toán lại phần nào cần build lại mỗi lần.
Những điều thiết yếu
Khi xây dựng bất kỳ kiến trúc mô-đun nào, điều quan trọng cần ghi nhớ là thư viện tĩnh (static library) sẽ được gắn trực tiếp vào file thực thi, trong khi thư viện động (dynamic library) thì được mở và liên kết khi app khởi động.
Do đó, nếu có hai framework liên kết cùng một thư viện tĩnh, app sẽ khởi động với cảnh báo:
“Class loaded twice… one of them will be used.”
Điều này khiến thời gian khởi động app càng chậm hơn, vì app phải quyết định nên dùng lớp nào.
Hơn nữa, nếu hai phiên bản khác nhau của cùng một thư viện tĩnh được sử dụng, app sẽ dùng chúng lẫn lộn, dẫn đến việc debug sẽ trở thành ác mộng. Vì vậy, rất quan trọng phải đảm bảo quá trình liên kết (linking) được thực hiện đúng và không có cảnh báo nào xuất hiện.
Tất cả lý do trên chính là lý do vì sao nên sử dụng framework được liên kết động trong quá trình phát triển nội bộ. Tuy nhiên, việc làm việc với thư viện tĩnh là điều không thể tránh khỏi, đặc biệt khi sử dụng thư viện bên thứ ba.
Các công ty lớn như Google, Microsoft, hoặc Amazon thường phân phối SDK của họ dưới dạng thư viện tĩnh. Ví dụ hiện tại:
- GoogleMaps
- GooglePlaces
- Firebase
- MSAppCenter
và tất cả các phần con của các SDK này đều liên kết tĩnh.
Khi sử dụng trình quản lý phụ thuộc của bên thứ ba như CocoaPods để liên kết một thư viện tĩnh cho nhiều project (App hoặc Framework), việc cài đặt có thể thất bại với lỗi:
target has transitive dependencies that include static binaries.
Vì vậy, cần nỗ lực thêm để có thể liên kết thư viện tĩnh vào nhiều framework khác nhau.
Bài tiếp, chúng ta sẽ tìm hiểu cách liên kết thư viện tĩnh vào một SDK được liên kết động.