Chào bạn, dưới đây là bộ 50 câu hỏi phỏng vấn iOS (Swift & SwiftUI) được biên soạn kỹ lưỡng từ cấp độ cơ bản đến nâng cao, bám sát các dự án thực tế và kiến thức chuyên sâu về bộ nhớ, kiến trúc và UI.
Phần 1: Swift Core & Memory Management
Câu 1: Sự khác biệt giữa struct và class là gì?
Lời giải:
structlà Value Type (lưu trên Stack, copy khi truyền đi), cònclasslà Reference Type (lưu trên Heap, chia sẻ cùng một instance qua các con trỏ).Code Demo:
struct MyStruct { var name: String }
class MyClass { var name: String; init(name: String) { self.name = name } }
// Value type: s2 không đổi khi s1 đổi
var s1 = MyStruct(name: "A")
var s2 = s1
s1.name = "B"
// Reference type: c2 đổi theo c1
let c1 = MyClass(name: "A")
let c2 = c1
c1.name = "B"
Câu 2: ARC là gì? Làm sao để tránh Strong Reference Cycle?
Lời giải: ARC (Automatic Reference Counting) tự động quản lý bộ nhớ bằng cách đếm số lượng reference mạnh. Để tránh leak bộ nhớ (Retain Cycle), ta dùng
weakhoặcunownedcho các mối quan hệ vòng lặp.Code Demo:
class Person {
var apartment: Apartment?
deinit { print("Person deinitialized") }
}
class Apartment {
weak var tenant: Person? // Dùng weak để phá vỡ retain cycle
deinit { print("Apartment deinitialized") }
}
Câu 3: Phân biệt weak và unowned.
Lời giải: Cả hai không làm tăng reference count.
weakluôn là kiểu Optional (có thể nil), trong khiunownedkhông phải Optional và crash nếu truy cập khi đối tượng đã bị giải phóng.Code Demo:
class Customer {
let name: String
var card: CreditCard?
init(name: String) { self.name = name }
}
class CreditCard {
unowned let customer: Customer // Một cái thẻ luôn phải có chủ
init(customer: Customer) { self.customer = customer }
}
Câu 4: Optional Chaining là gì?
Lời giải: Cách truy cập các thuộc tính/phương thức của một Optional mà không cần force unwrap. Nếu giá trị là nil, toàn bộ chuỗi sẽ trả về nil.
Code Demo:
let name = person?.address?.street?.name // Trả về String?
Câu 5: Closure là gì? Tại sao cần [weak self] trong closure?
Lời giải: Closure là một khối mã có thể thực thi. Cần
[weak self]khi closure giữ một tham chiếu đếnselfmàselfcũng giữ closure đó (thường gặp trong xử lý mạng hoặc định nghĩa property).Code Demo:
class APIClient {
var onComplete: (() -> Void)?
func fetchData() {
onComplete = { [weak self] in
self?.updateUI()
}
}
func updateUI() { print("UI Updated") }
}
Câu 6: Enum trong Swift có gì đặc biệt?
Lời giải: Enum trong Swift hỗ trợ Associated Values (giá trị đi kèm) và Raw Values. Nó cũng có thể chứa hàm và computed properties.
Code Demo:
enum Result<T> {
case success(T)
case failure(String)
}
let response = Result.success("Data loaded")
Câu 7: Protocol và Protocol Extensions là gì?
Lời giải: Protocol định nghĩa bản thiết kế. Extension cho phép cung cấp các cài đặt mặc định (default implementation) cho các hàm trong protocol đó.
Code Demo:
protocol Identifiable { var id: String { get } }
extension Identifiable {
func logID() { print("My ID is \(id)") } // Default logic
}
Câu 8: Sự khác biệt giữa Any và AnyObject?
Lời giải:
Anyđại diện cho bất kỳ kiểu dữ liệu nào (bao gồm cả struct, enum, function).AnyObjectchỉ đại diện cho kiểuclass.Code Demo:
let list: [Any] = ["Hello", 10, MyStruct(name: "A")]
let objects: [AnyObject] = [MyClass(name: "B")]
Câu 9: Generics là gì?
Lời giải: Giúp viết code linh hoạt, tái sử dụng được cho nhiều kiểu dữ liệu mà vẫn đảm bảo Type Safety.
Code Demo:
func swapValues<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}
Câu 10: Phân biệt map, flatMap và compactMap.
Lời giải:
mapbiến đổi phần tử;compactMaploại bỏ nil;flatMaplàm phẳng mảng 2 chiều hoặc gỡ optional lồng nhau.Code Demo:
let nums = ["1", "2", "three", "4"]
let mapped = nums.compactMap { Int($0) } // [1, 2, 4]
Phần 2: SwiftUI & State Management
Câu 11: Sự khác biệt giữa @State, @Binding và @ObservedObject?
Lời giải: *
@State: Quản lý dữ liệu nội bộ của View (Value Type).@Binding: Kết nối (read-write) với dữ liệu từ View cha.@ObservedObject: Theo dõi một class (Reference Type) tuân thủObservableObject.
Code Demo:
struct ChildView: View {
@Binding var text: String
var body: some View { TextField("Type...", text: $text) }
}
Câu 12: @StateObject khác @ObservedObject như thế nào?
Lời giải:
@StateObjectđảm bảo object không bị tạo lại khi View bị render lại. Luôn dùng@StateObjectkhi khởi tạo (init) và@ObservedObjectkhi nhận từ bên ngoài vào.Code Demo:
struct ParentView: View {
@StateObject var viewModel = MyViewModel() // Quản lý vòng đời
var body: some View { ChildView(vm: viewModel) }
}
Câu 13: EnvironmentObject dùng để làm gì?
Lời giải: Để truyền dữ liệu xuyên suốt nhiều tầng View mà không cần truyền qua từng initializer (Dependency Injection ở mức toàn cục bộ bộ).
Code Demo:
// Inject ở root
ContentView().environmentObject(UserSettings())
// Sử dụng ở bất kỳ View con nào
@EnvironmentObject var settings: UserSettings
Câu 14: SwiftUI View là struct hay class? Tại sao?
Lời giải: Là
struct. Vì View trong SwiftUI rất nhẹ, được tạo và hủy liên tục. Việc dùng struct giúp tăng hiệu năng cực cao và tránh các vấn đề về tham chiếu.
Câu 15: any View và some View là gì?
Lời giải:
some View(Opaque type) yêu cầu View trả về 1 kiểu cụ thể duy nhất tại thời điểm compile.any View(Existential type) cho phép trả về nhiều loại View khác nhau nhưng hiệu năng kém hơn.Code Demo:
var body: some View { // Trả về duy nhất 1 View (như VStack)
Text("Hello")
}
Câu 16: Làm sao để SwiftUI View cập nhật khi dữ liệu thay đổi?
Lời giải: Dữ liệu phải được đánh dấu bằng các Property Wrapper (
@State,@Published, v.v.). Khi giá trị thay đổi, SwiftUI so sánh View hierarchy và vẽ lại những phần cần thiết.
Câu 17: Combine framework là gì? Liên quan gì đến SwiftUI?
Lời giải: Là framework xử lý các luồng dữ liệu bất đồng bộ theo thời gian. SwiftUI sử dụng Combine (qua
@PublishedvàObservableObject) để thông báo cho View khi dữ liệu thay đổi.
Câu 18: AsyncImage trong SwiftUI dùng để làm gì?
Lời giải: Để tải và hiển thị hình ảnh từ một URL một cách bất đồng bộ, có hỗ trợ placeholder.
Code Demo:
AsyncImage(url: URL(string: "https://example.com/img.png")) { image in
image.resizable()
} placeholder: { ProgressView() }
Câu 19: Cách sử dụng GeometryReader?
Lời giải: Dùng để lấy kích thước và vị trí của View cha nhằm tính toán layout linh hoạt.
Code Demo:
GeometryReader { geo in
Text("Width: \(geo.size.width)")
.frame(width: geo.size.width * 0.5)
}
Câu 20: Chức năng của .task modifier trong SwiftUI?
Lời giải: Dùng để chạy một tác vụ bất đồng bộ (async) khi View xuất hiện và tự động hủy nếu View biến mất.
Code Demo:
.task {
await viewModel.fetchData()
}
Phần 3: Networking & Concurrency (Swift Concurrency)
Câu 21: async/await có lợi ích gì so với Completion Handler?
Lời giải: Giúp code đọc giống tuần tự (linear), tránh "Pyramid of Doom" (callback hell), và xử lý lỗi bằng
try/catchdễ dàng hơn.Code Demo:
func fetchData() async throws -> Data {
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
Câu 22: MainActor là gì?
Lời giải: Là một singleton actor đảm bảo các tác vụ (như cập nhật UI) luôn được thực thi trên Main Thread.
Code Demo:
@MainActor
class ViewModel: ObservableObject {
@Published var data = ""
func updateUI() { self.data = "Done" }
}
Câu 23: Phân biệt Task và TaskGroup.
Lời giải:
Taskchạy một tác vụ đơn lẻ.TaskGroupcho phép chạy nhiều tác vụ song song và gộp kết quả lại.
Câu 24: Actor trong Swift là gì?
Lời giải: Là một kiểu dữ liệu mới (Reference Type) giúp bảo vệ trạng thái khỏi Data Race bằng cách đảm bảo chỉ có một luồng truy cập vào dữ liệu của nó tại một thời điểm.
Code Demo:
actor BankAccount {
var balance = 0
func deposit(amount: Int) { balance += amount }
}
Câu 25: GCD (Grand Central Dispatch) là gì? Các loại Queue cơ bản?
Lời giải: Là API cấp thấp quản lý các tác vụ đồng thời. Gồm: Main Queue (UI), Global Queues (Background), và Custom Queues.
Phần 4: Architecture & Design Patterns
Câu 26: Giải thích kiến trúc MVVM.
Lời giải: * Model: Dữ liệu/Logic nghiệp vụ.
View: Hiển thị UI (SwiftUI View).
ViewModel: Cầu nối, chứa logic xử lý dữ liệu cho View, sử dụng
@Publishedđể binding.
Câu 27: Dependency Injection (DI) là gì? Tại sao cần nó?
Lời giải: Là việc truyền các thành phần phụ thuộc từ bên ngoài vào thay vì khởi tạo bên trong. Giúp code dễ unit test và linh hoạt.
Code Demo:
class ProfileViewModel {
let service: NetworkService
init(service: NetworkService) { self.service = service }
}
Câu 28: Singleton Pattern là gì? Khi nào nên dùng?
Lời giải: Đảm bảo một class chỉ có duy nhất một instance toàn cục (như
URLSession.shared). Nên dùng cho các tài nguyên dùng chung như Network, Database.
Câu 29: Sự khác biệt giữa AppStorage và UserDefaults?
Lời giải:
AppStoragelà một property wrapper của SwiftUI giúp tự động cập nhật View khi giá trị trongUserDefaultsthay đổi.
Câu 30: Clean Architecture là gì?
Lời giải: Là kiến trúc phân lớp (Entities, Use Cases, Adapters) nhằm tách biệt logic nghiệp vụ khỏi UI và Framework, giúp hệ thống dễ mở rộng và bảo trì.
Phần 5: Advanced Swift & iOS Features
Câu 31: COW (Copy-on-Write) hoạt động như thế nào?
Lời giải: Với các Collection như
Array,Dictionary, dữ liệu chỉ thực sự bị copy khi bạn thực hiện thay đổi. Nếu chỉ đọc hoặc gán, chúng dùng chung bộ nhớ.
Câu 32: Opaque Types (some) khác gì với Generics?
Lời giải: Generics cho phép người gọi (caller) quyết định kiểu dữ liệu. Opaque types cho phép hàm/view (callee) quyết định kiểu dữ liệu trả về nhưng giấu chi tiết đó đi.
Câu 33: Codable là gì?
Lời giải: Là một typealias của
EncodablevàDecodable, giúp chuyển đổi dễ dàng giữa Model và JSON.Code Demo:
struct User: Codable { let name: String }
let user = try JSONDecoder().decode(User.self, from: jsonData)
Câu 34: Lazy property là gì?
Lời giải: Biến chỉ được khởi tạo giá trị khi nó được truy cập lần đầu tiên. Giúp tiết kiệm tài nguyên.
Code Demo:
lazy var heavyView = HeavyView()
Câu 35: Property Wrapper là gì?
Lời giải: Là một lớp bao bọc logic truy cập của property (như
@State,@Published).Code Demo:
@propertyWrapper
struct Trimmed {
private var value: String = ""
var wrappedValue: String {
get { value }
set { value = newValue.trimmingCharacters(in: .whitespaces) }
}
}
Câu 36: Result Builder là gì?
Lời giải: Là cơ chế đằng sau cú pháp DSL của SwiftUI (như các View trong VStack), cho phép gộp nhiều giá trị thành một kết quả duy nhất.
Câu 37: Error Handling trong Swift dùng defer làm gì?
Lời giải: Đảm bảo một khối mã luôn được chạy trước khi kết thúc hàm (dùng để đóng file, giải phóng tài nguyên).
Câu 38: Cách xử lý Deep Link trong iOS?
Lời giải: Sử dụng
onOpenURL(perform:)trong SwiftUI hoặcSceneDelegatetrong UIKit để bắt URL scheme hoặc Universal Links.
Câu 39: Unit Test và UI Test khác nhau thế nào?
Lời giải: Unit Test kiểm tra logic nhỏ nhất (hàm, model). UI Test giả lập thao tác người dùng trên giao diện thực tế.
Câu 40: Core Data vs SwiftData?
Lời giải:
Core Datalà framework cũ (dựa trên Objective-C).SwiftDatalà bản kế nhiệm hiện đại cho SwiftUI, dùng macro@Modelvà thuần Swift.
Phần 6: UI/UX & Thực tế dự án
Câu 41: Làm sao để tích hợp UIKit vào SwiftUI?
Lời giải: Sử dụng protocol
UIViewRepresentablehoặcUIViewControllerRepresentable.
Câu 42: Swift Package Manager (SPM) là gì?
Lời giải: Công cụ quản lý thư viện chính thức của Apple, tích hợp sẵn trong Xcode thay thế cho CocoaPods.
Câu 43: Làm sao để tối ưu hóa hiệu năng List trong SwiftUI?
Lời giải: Sử dụng
id: \.selfhoặcIdentifiable, tránh tính toán logic nặng trongbody, sử dụngLazyVStacknếu cần.
Câu 44: Dynamic Type là gì?
Lời giải: Tính năng cho phép người dùng thay đổi kích thước font chữ hệ thống và ứng dụng phải tự điều chỉnh theo.
Câu 45: Phân biệt Frame và Alignment trong SwiftUI?
Lời giải:
Frameđịnh nghĩa kích thước View.Alignmentđịnh nghĩa cách nội dung nằm trong Frame đó.
Câu 46: Tại sao ứng dụng bị crash EXC_BAD_ACCESS?
Lời giải: Thường do cố gắng truy cập vào một vùng nhớ đã bị giải phóng (deallocated).
Câu 47: Content Compression Resistance và Content Hugging là gì?
Lời giải: * Hugging: Độ ưu tiên để không bị giãn ra.
Resistance: Độ ưu tiên để không bị co lại.
Câu 48: Làm sao để xử lý đa ngôn ngữ (Localization) trong SwiftUI?
Lời giải: Sử dụng
LocalizedStringKeyhoặc String Catalog (.xcstrings) mới trong Xcode 15.
Câu 49: ViewModifier là gì?
Lời giải: Cách đóng gói các thuộc tính style để tái sử dụng cho nhiều View.
Code Demo:
struct TitleStyle: ViewModifier {
func body(content: Content) -> some View {
content.font(.largeTitle).foregroundColor(.blue)
}
}
Câu 50: Tác dụng của .id() modifier trong SwiftUI?
Lời giải: Giúp SwiftUI xác định danh tính của View. Khi
idthay đổi, SwiftUI sẽ coi đó là một View hoàn toàn mới và reset lại trạng thái (Animation, State).