View Protocol là giao thức nền tảng của SwiftUI mà mọi thành phần giao diện trực quan phải tuân theo. Theo Apple Developer Documentation, 2024, View định nghĩa một hợp đồng duy nhất: cấu trúc hoặc lớp triển khai giao thức này phải cung cấp thuộc tính tính toán body. Thông qua giao thức này, SwiftUI xây dựng toàn bộ hệ thống phân cấp màn hình từ các nhãn văn bản đơn giản đến cấu trúc điều hướng phức tạp.
Những điểm chính
View Protocol là giao thức trung tâm của SwiftUI định nghĩa cách bất kỳ phần tử trực quan nào mô tả nội dung của nó. Không giống như UIKit, nơi mỗi phần tử kế thừa từ UIView thông qua các lớp, SwiftUI sử dụng cách tiếp cận theo hướng giao thức: bất kỳ kiểu nào tuân thủ giao thức View đều có thể được hiển thị trên màn hình.
Giao thức View yêu cầu triển khai một thuộc tính tính toán duy nhất body trả về một số nội dung. Tuy nhiên, đằng sau sự đơn giản này là một hệ thống kết hợp mạnh mẽ: body có thể trả về bất kỳ kiểu nào tuân thủ View, bao gồm các kiểu nguyên thủy (Text, Image, Button), vùng chứa (VStack, HStack, ZStack) và các thành phần tổng hợp tùy chỉnh.
Theo WWDC 2023, hơn 95% tất cả các màn hình trong ứng dụng SwiftUI được xây dựng thông qua sự kết hợp của các cấu trúc triển khai giao thức View. Điều này làm cho View Protocol trở thành nền tảng của toàn bộ kiến trúc SwiftUI.
SwiftUI yêu cầu View phải là kiểu giá trị (value type) (struct), không phải lớp. Đây là một quyết định kiến trúc quan trọng: kiểu giá trị có vòng đời có thể dự đoán trước, không có trạng thái thay đổi được chia sẻ và cho phép SwiftUI xác định hiệu quả phần nào của hệ thống phân cấp đã thay đổi và cần vẽ lại.
Nếu bạn cố gắng biến View thành một lớp, trình biên dịch sẽ báo lỗi: giao thức View kế thừa từ giao thức DynamicViewProperty, yêu cầu ngữ nghĩa giá trị. Các lớp có thể tuân thủ View, nhưng điều này phá vỡ cách tiếp cận thông thường và mất đi lợi ích của việc cập nhật tự động.
body là yêu cầu bắt buộc duy nhất của giao thức View. Nó là một thuộc tính tính toán trả về nội dung được hiển thị trên màn hình. Kiểu trả về là some View, có nghĩa là “một kiểu tuân thủ View, sẽ được xác định bởi trình biên dịch”.
struct GreetingView: View {
var name: String
var body: some View {
VStack {
Text("Hello, \(name)!")
.font(.title)
.foregroundColor(.blue)
Button("Start") {
print("Button pressed")
}
}
}
}
Cách body hoạt động: SwiftUI gọi body mỗi khi trạng thái ứng dụng thay đổi và cần vẽ lại. Khung so sánh cây View mới với cây cũ và chỉ áp dụng những thay đổi cần thiết (diffing). Đây là cách tiếp cận hoàn toàn khai báo — bạn mô tả những gì cần hiển thị, và SwiftUI xử lý cách triển khai.
Một chi tiết quan trọng: body không được có tác dụng phụ. Nó được gọi nhiều lần trong suốt vòng đời ứng dụng, và nếu body sửa đổi trạng thái bên ngoài — điều này dẫn đến hành vi không thể dự đoán trước. Đối với các tác dụng phụ, hãy sử dụng task, onChange hoặc DispatchQueue.
SwiftUI áp đặt một hạn chế: body chỉ có thể trả về một phần tử gốc duy nhất. Nếu bạn cần hiển thị nhiều phần tử ở cùng một cấp, hãy bọc chúng trong một vùng chứa — VStack, HStack, ZStack hoặc Group. Với sự ra đời của @ViewBuilder, hạn chế này trở nên ít rõ ràng hơn, nhưng về mặt khái niệm, body luôn trả về một View duy nhất.
some View là cú pháp kiểu không minh bạch (opaque type) được giới thiệu trong Swift 5.1 đặc biệt cho SwiftUI. Nó có nghĩa là một hàm hoặc thuộc tính trả về một kiểu cụ thể tuân thủ giao thức View, nhưng mã gọi không biết và không cần biết kiểu chính xác nào được trả về.
Trình biên dịch Swift cố định kiểu cụ thể tại thời điểm biên dịch cho mỗi triển khai body, nhưng ẩn nó với thế giới bên ngoài. Điều này cho phép SwiftUI tối ưu hóa hệ thống phân cấp View bằng cách biết các kiểu chính xác của tất cả các thành phần, đồng thời cung cấp cho nhà phát triển sự linh hoạt để thay đổi triển khai mà không thay đổi chữ ký.
struct ContentView: View {
var body: some View {
Text("Hello, World!") // Compiler knows this is Text
}
}
Tại sao some View mà không phải chỉ View? Nếu body chỉ trả về View (như một giao thức), SwiftUI sẽ không thể xác định kiểu cụ thể tại thời điểm biên dịch. Điều này thêm chi phí cho việc đóng gói trong một vùng chứa hiện sinh (existential container). some View cung cấp cho trình biên dịch đủ thông tin để tối ưu hóa trong khi vẫn duy trì tính linh hoạt của giao thức.
Hạn chế chính là body phải trả về cùng một kiểu. Bạn không thể trả về Text trong một nhánh của điều kiện và Image trong nhánh khác mà không có bộ bọc đặc biệt (AnyView, Group hoặc @ViewBuilder). Trình biên dịch kiểm tra điều này tại thời điểm biên dịch: tất cả các đường dẫn trả về có thể phải có cùng kiểu.
Để vượt qua hạn chế này, hãy sử dụng @ViewBuilder (tạo một kiểu TupleView duy nhất), Group (cũng trả về một kiểu duy nhất) hoặc AnyView (xóa kiểu nhưng thêm chi phí). AnyView chỉ nên được sử dụng khi các tùy chọn khác không khả thi, vì nó vô hiệu hóa các tối ưu hóa của SwiftUI.
@ViewBuilder là một trình xây dựng kết quả (result builder) mà chú thích của nó cho phép tập hợp nhiều Views thành một tổ hợp duy nhất mà không cần vùng chứa lồng nhau. @ViewBuilder tự động bọc nhiều biểu thức vào một bộ (TupleView) hoặc áp dụng logic có điều kiện (If / else / switch) với kiểu trả về chính xác.
struct DashboardView: View {
var isLoggedIn: Bool
@ViewBuilder
var body: some View {
if isLoggedIn {
Text("Welcome!")
.font(.largeTitle)
ProfileCard()
} else {
LoginButton()
.padding()
}
}
}
Cách @ViewBuilder hoạt động: trình biên dịch biến đổi mỗi khối mã bên trong @ViewBuilder thành các lời gọi đến các phương thức tĩnh buildBlock, buildEither, buildOptional, v.v. Nếu một khối chứa nhiều biểu thức — chúng được bọc trong TupleView. Nếu một khối chứa logic có điều kiện — trình biên dịch tạo ra ConditionalContent, ẩn kiểu của nhánh.
@ViewBuilder áp đặt một hạn chế: tối đa 10 phần tử trong một khối (giới hạn TupleView). Nếu bạn cần tập hợp hơn mười phần tử, hãy sử dụng Group, ForEach hoặc chia thành các thành phần phụ. Hạn chế này tồn tại vì Swift tạo ra một quá tải buildBlock riêng cho mỗi số lượng đối số từ 1 đến 10.
Tổ hợp (Composition) là một nguyên tắc chính của SwiftUI: các giao diện phức tạp được xây dựng từ các thành phần View nhỏ, có thể tái sử dụng. Mỗi thành phần triển khai giao thức View và chịu trách nhiệm cho phần màn hình của nó. Các bộ sửa đổi (font, padding, foregroundColor) được áp dụng cho View và trả về một View mới với các cài đặt đã thay đổi.
Các bộ sửa đổi trong SwiftUI không phải là đột biến, mà là việc tạo ra một bộ bọc mới xung quanh View gốc. Mỗi bộ sửa đổi trả về một kiểu mới (ModifiedContent), cho phép SwiftUI xây dựng một cây bộ sửa đổi và chỉ vẽ lại hiệu quả các phần đã thay đổi. Thứ tự áp dụng các bộ sửa đổi rất quan trọng: các thứ tự khác nhau tạo ra kết quả trực quan khác nhau.
Text("Hello, SwiftUI!")
.font(.title) // ModifiedContent
.padding() // ModifiedContent<..., PaddingModifier>
.background(.yellow) // ModifiedContent<..., BackgroundModifier>
.cornerRadius(8) // ModifiedContent<..., CornerRadiusModifier>
Tối ưu hóa hiệu suất: SwiftUI không so sánh các giá trị cụ thể của View mà so sánh danh tính của chúng thông qua cơ chế danh tính (id, ForEach, danh tính ổn định của cấu trúc). Nếu cấu trúc View không thay đổi — body không được gọi. Điều này đạt được thông qua so sánh Equatable và cơ chế PreferenceKey để truyền dữ liệu lên trên hệ thống phân cấp.
Để có tổ hợp hiệu quả, nên chia các màn hình phức tạp thành các thành phần phụ độc lập, mỗi thành phần có trạng thái tối thiểu riêng. Điều này cho phép SwiftUI chỉ vẽ lại các phần đã thay đổi của hệ thống phân cấp, không phải toàn bộ màn hình.
Câu hỏi thường gặp
View Protocol là giao thức cơ bản của SwiftUI mà mọi thành phần được hiển thị phải tuân theo. Nó yêu cầu một thuộc tính body tính toán duy nhất trả về nội dung. Tất cả các phần tử SwiftUI tiêu chuẩn — Text, Button, Image, VStack — đều triển khai giao thức này.
SwiftUI sử dụng ngữ nghĩa giá trị (value semantics) để cập nhật giao diện có thể dự đoán trước. Các cấu trúc không có trạng thái thay đổi được chia sẻ, cho phép SwiftUI so sánh hiệu quả hệ thống phân cấp View cũ và mới và chỉ vẽ lại các phần tử đã thay đổi. Các lớp phá vỡ sự tối ưu hóa này.
body trả về some View — một kiểu không minh bạch ẩn triển khai cụ thể. Thực tế nó trả về bất kỳ kiểu nào tuân thủ View: Text, Image, VStack, cấu trúc tùy chỉnh. Trình biên dịch cố định kiểu cụ thể tại thời điểm biên dịch để tối ưu hóa.
some View là kiểu không minh bạch với kiểu cụ thể được cố định tại thời điểm biên dịch. AnyView là xóa kiểu (type erasure), bọc bất kỳ View nào trong một vùng chứa duy nhất. some View hiệu quả hơn; AnyView thêm chi phí và chỉ được sử dụng khi cần chuyển đổi kiểu động.
Tối đa 10 phần tử — đây là giới hạn TupleView, tạo buildBlock cho số lượng đối số từ 1 đến 10. Nếu bạn cần nhiều phần tử hơn, hãy sử dụng Group, ForEach, List hoặc chia thành các thành phần phụ. Giới hạn này tồn tại ở cấp trình biên dịch Swift.
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