Phỏng vấn vị trí lập trình viên Swift / SwiftUI


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.


  • Lời giải: structValue Type (lưu trên Stack, copy khi truyền đi), còn classReference Type (lưu trên Heap, chia sẻ cùng một instance qua các con trỏ).

  • Code Demo:

Swift
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" 
  • 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 weak hoặc unowned cho các mối quan hệ vòng lặp.

  • Code Demo:

Swift
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") }
}
  • Lời giải: Cả hai không làm tăng reference count. weak luôn là kiểu Optional (có thể nil), trong khi unowned không phải Optional và crash nếu truy cập khi đối tượng đã bị giải phóng.

  • Code Demo:

Swift
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 }
}
  • 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:

Swift
let name = person?.address?.street?.name // Trả về String?
  • 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 đến selfself cũng giữ closure đó (thường gặp trong xử lý mạng hoặc định nghĩa property).

  • Code Demo:

Swift
class APIClient {
    var onComplete: (() -> Void)?
    func fetchData() {
        onComplete = { [weak self] in 
            self?.updateUI()
        }
    }
    func updateUI() { print("UI Updated") }
}
  • 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:

Swift
enum Result<T> {
    case success(T)
    case failure(String)
}
let response = Result.success("Data loaded")
  • 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:

Swift
protocol Identifiable { var id: String { get } }
extension Identifiable {
    func logID() { print("My ID is \(id)") } // Default logic
}
  • 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). AnyObject chỉ đại diện cho kiểu class.

  • Code Demo:

Swift
let list: [Any] = ["Hello", 10, MyStruct(name: "A")]
let objects: [AnyObject] = [MyClass(name: "B")]
  • 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:

Swift
func swapValues<T>(_ a: inout T, _ b: inout T) {
    let temp = a
    a = b
    b = temp
}
  • Lời giải: map biến đổi phần tử; compactMap loại bỏ nil; flatMap làm phẳng mảng 2 chiều hoặc gỡ optional lồng nhau.

  • Code Demo:

Swift
let nums = ["1", "2", "three", "4"]
let mapped = nums.compactMap { Int($0) } // [1, 2, 4]

  • 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:

Swift
struct ChildView: View {
    @Binding var text: String
    var body: some View { TextField("Type...", text: $text) }
}
  • 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 @StateObject khi khởi tạo (init) và @ObservedObject khi nhận từ bên ngoài vào.

  • Code Demo:

Swift
struct ParentView: View {
    @StateObject var viewModel = MyViewModel() // Quản lý vòng đời
    var body: some View { ChildView(vm: viewModel) }
}
  • 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:

Swift
// Inject ở root
ContentView().environmentObject(UserSettings())

// Sử dụng ở bất kỳ View con nào
@EnvironmentObject var settings: UserSettings
  • Lời giải: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.

  • 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:

Swift
var body: some View { // Trả về duy nhất 1 View (như VStack)
    Text("Hello")
}
  • 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.

  • 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 @PublishedObservableObject) để thông báo cho View khi dữ liệu thay đổi.

  • 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:

Swift
AsyncImage(url: URL(string: "https://example.com/img.png")) { image in
    image.resizable()
} placeholder: { ProgressView() }
  • 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:

Swift
GeometryReader { geo in
    Text("Width: \(geo.size.width)")
        .frame(width: geo.size.width * 0.5)
}
  • 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:

Swift
.task {
    await viewModel.fetchData()
}

  • 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/catch dễ dàng hơn.

  • Code Demo:

Swift
func fetchData() async throws -> Data {
    let (data, _) = try await URLSession.shared.data(from: url)
    return data
}
  • 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:

Swift
@MainActor
class ViewModel: ObservableObject {
    @Published var data = ""
    func updateUI() { self.data = "Done" }
}
  • Lời giải: Task chạy một tác vụ đơn lẻ. TaskGroup cho phép chạy nhiều tác vụ song song và gộp kết quả lại.

  • 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:

Swift
actor BankAccount {
    var balance = 0
    func deposit(amount: Int) { balance += amount }
}
  • 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.


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

  • 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:

Swift
class ProfileViewModel {
    let service: NetworkService
    init(service: NetworkService) { self.service = service }
}
  • 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.

  • Lời giải: AppStorage là một property wrapper của SwiftUI giúp tự động cập nhật View khi giá trị trong UserDefaults thay đổi.

  • 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ì.


  • 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ớ.

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

  • Lời giải: Là một typealias của EncodableDecodable, giúp chuyển đổi dễ dàng giữa Model và JSON.

  • Code Demo:

Swift
struct User: Codable { let name: String }
let user = try JSONDecoder().decode(User.self, from: jsonData)
  • 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:

Swift
lazy var heavyView = HeavyView()
  • Lời giải: Là một lớp bao bọc logic truy cập của property (như @State, @Published).

  • Code Demo:

Swift
@propertyWrapper
struct Trimmed {
    private var value: String = ""
    var wrappedValue: String {
        get { value }
        set { value = newValue.trimmingCharacters(in: .whitespaces) }
    }
}
  • 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.

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

  • Lời giải: Sử dụng onOpenURL(perform:) trong SwiftUI hoặc SceneDelegate trong UIKit để bắt URL scheme hoặc Universal Links.

  • 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ế.

  • Lời giải: Core Data là framework cũ (dựa trên Objective-C). SwiftData là bản kế nhiệm hiện đại cho SwiftUI, dùng macro @Model và thuần Swift.


  • Lời giải: Sử dụng protocol UIViewRepresentable hoặc UIViewControllerRepresentable.

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

  • Lời giải: Sử dụng id: \.self hoặc Identifiable, tránh tính toán logic nặng trong body, sử dụng LazyVStack nếu cần.

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

  • 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 đó.

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

  • Lời giải: * Hugging: Độ ưu tiên để không bị giãn ra.

    • Resistance: Độ ưu tiên để không bị co lại.

  • Lời giải: Sử dụng LocalizedStringKey hoặc String Catalog (.xcstrings) mới trong Xcode 15.

  • 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:

Swift
struct TitleStyle: ViewModifier {
    func body(content: Content) -> some View {
        content.font(.largeTitle).foregroundColor(.blue)
    }
}
  • Lời giải: Giúp SwiftUI xác định danh tính của View. Khi id thay đổi, SwiftUI sẽ coi đó là một View hoàn toàn mới và reset lại trạng thái (Animation, State).

Phản hồi từ học viên

5

Tổng 0 đánh giá
Developer Toolbox

TEXT CASE

FORMAT & CLEAN

ENCODE & DECODE

JSON & CRYPTO

Đã sao chép!!!
Gozic - Hệ thống học lập trình, luyện thi, kiểm tra trắc nghiệm trực tuyến uy tín tại Việt Nam.
Hotline: 0967025996
Gozic - Hệ thống học lập trình, luyện thi, kiểm tra trắc nghiệm trực tuyến uy tín tại Việt Nam.