Hiệu Suất SwiftUI: Tối Ưu Hóa LazyVStack và Danh Sách Phức Tạp

Kỹ thuật tối ưu hóa cho LazyVStack và danh sách SwiftUI. Giảm tiêu thụ bộ nhớ, cải thiện hiệu suất cuộn và tránh các lỗi thường gặp.

Tối ưu hóa hiệu suất SwiftUI LazyVStack và danh sách phức tạp

Danh sách đại diện cho một trong những thành phần được sử dụng nhiều nhất trong các ứng dụng iOS. LazyVStackList của SwiftUI cung cấp các giải pháp hiệu suất cao để hiển thị các tập hợp dữ liệu, nhưng việc sử dụng không đúng cách có thể nhanh chóng làm suy giảm trải nghiệm người dùng. Hiểu được cơ chế bên trong của các thành phần này sẽ giúp tránh được các cạm bẫy phổ biến và xây dựng giao diện mượt mà.

Bài viết này bao gồm

Bài viết này trình bày các kỹ thuật tối ưu hóa thiết yếu cho danh sách SwiftUI: lazy loading, tái sử dụng view, quản lý định danh và các mẫu nâng cao cho tập dữ liệu lớn.

Hiểu Về Lazy Loading Trong SwiftUI

Lazy loading khởi tạo view chỉ khi chúng trở nên hiển thị trên màn hình. Khác với VStack tạo tất cả các view con ngay lập tức, LazyVStack trì hoãn việc tạo này, giảm đáng kể tiêu thụ bộ nhớ và thời gian render ban đầu.

LazyVStackComparison.swiftswift
import SwiftUI

// ❌ Problem: VStack instantiates all 10,000 views immediately
struct NonLazyListView: View {
    let items = (1...10000).map { "Item \($0)" }

    var body: some View {
        ScrollView {
            VStack {
                ForEach(items, id: \.self) { item in
                    // Each view is created at launch
                    ExpensiveRowView(title: item)
                }
            }
        }
    }
}

// ✅ Solution: LazyVStack creates views on demand
struct LazyListView: View {
    let items = (1...10000).map { "Item \($0)" }

    var body: some View {
        ScrollView {
            LazyVStack {
                ForEach(items, id: \.self) { item in
                    // Only visible views are created
                    ExpensiveRowView(title: item)
                }
            }
        }
    }
}

Sự khác biệt về hiệu suất trở nên đáng kể chỉ với vài trăm phần tử. Với 10.000 mục, VStack có thể mất vài giây để khởi động trong khi LazyVStack vẫn tức thì.

Đo Lường Tác Động Của Lazy Loading

Instruments cho phép đo lường chính xác việc sử dụng bộ nhớ và CPU. Đây là một view kiểm tra minh họa sự khác biệt:

PerformanceMeasurement.swiftswift
struct ExpensiveRowView: View {
    let title: String

    // Simulating expensive initialization
    init(title: String) {
        self.title = title
        // Log to visualize when the view is created
        print("Creating row: \(title)")
    }

    var body: some View {
        HStack {
            // Image with processing
            Circle()
                .fill(
                    LinearGradient(
                        colors: [.blue, .purple],
                        startPoint: .topLeading,
                        endPoint: .bottomTrailing
                    )
                )
                .frame(width: 50, height: 50)

            VStack(alignment: .leading) {
                Text(title)
                    .font(.headline)
                Text("Subtitle with computation")
                    .font(.caption)
                    .foregroundStyle(.secondary)
            }

            Spacer()
        }
        .padding()
    }
}

Khi chạy với VStack, tất cả 10.000 log xuất hiện ngay lập tức. Với LazyVStack, chỉ các phần tử hiển thị (khoảng 15-20 tùy thuộc vào kích thước màn hình) được ghi log ban đầu, với nhiều hơn xuất hiện khi cuộn.

Hành vi giữ lại

LazyVStack giữ các view đã tạo trong bộ nhớ sau khi chúng xuất hiện. Khác với List chủ động tái sử dụng các ô, các view trong LazyVStack tồn tại cho đến khi thành phần cha bị hủy.

Tầm Quan Trọng Của Định Danh Ổn Định

Định danh tạo thành cơ chế trung tâm cho việc cập nhật danh sách SwiftUI. Một định danh không ổn định gây ra việc tạo lại view không cần thiết và có thể kích hoạt các lỗi hiển thị như animation sai hoặc mất vị trí cuộn.

StableIdentifiers.swiftswift
// ❌ Problem: using index as identifier
struct UnstableIdentifierView: View {
    @State private var items = ["A", "B", "C", "D"]

    var body: some View {
        List {
            // Index changes if an element is deleted
            ForEach(items.indices, id: \.self) { index in
                Text(items[index])
            }
        }
    }
}

// ❌ Problem: using UUID() in ForEach
struct RegeneratedIdentifierView: View {
    let items = ["A", "B", "C", "D"]

    var body: some View {
        List {
            // UUID() generates a new ID on each render
            ForEach(items, id: \.self) { item in
                // Subtle issue if items contain duplicates
                Text(item)
            }
        }
    }
}

// ✅ Solution: model with stable identifier
struct Item: Identifiable {
    let id: UUID  // Created once
    var name: String

    init(name: String) {
        self.id = UUID()
        self.name = name
    }
}

struct StableIdentifierView: View {
    @State private var items = [
        Item(name: "A"),
        Item(name: "B"),
        Item(name: "C"),
        Item(name: "D")
    ]

    var body: some View {
        List {
            // id is stable for the item's lifetime
            ForEach(items) { item in
                Text(item.name)
            }
        }
    }
}

Việc sử dụng định danh duy nhất và liên tục đảm bảo SwiftUI có thể phân biệt chính xác các phần tử trong các cập nhật, animation và so sánh.

Tối Ưu Ô Với Equatable

SwiftUI so sánh các view để xác định liệu có cần render lại hay không. Theo mặc định, so sánh này sử dụng reflection, có thể tốn kém. Việc triển khai Equatable cho phép so sánh được tối ưu hóa và rõ ràng.

EquatableOptimization.swiftswift
// Data model
struct Contact: Identifiable, Equatable {
    let id: UUID
    var name: String
    var email: String
    var avatarURL: URL?
    var lastActivity: Date

    // Custom comparison: ignore lastActivity
    // if other properties are identical
    static func == (lhs: Contact, rhs: Contact) -> Bool {
        lhs.id == rhs.id &&
        lhs.name == rhs.name &&
        lhs.email == rhs.email &&
        lhs.avatarURL == rhs.avatarURL
        // lastActivity intentionally excluded
    }
}

// Optimized cell view
struct ContactRow: View, Equatable {
    let contact: Contact

    // Explicit comparison to avoid unnecessary re-renders
    static func == (lhs: ContactRow, rhs: ContactRow) -> Bool {
        lhs.contact == rhs.contact
    }

    var body: some View {
        HStack(spacing: 12) {
            // Async avatar
            AsyncImage(url: contact.avatarURL) { phase in
                switch phase {
                case .success(let image):
                    image
                        .resizable()
                        .aspectRatio(contentMode: .fill)
                case .failure:
                    Image(systemName: "person.circle.fill")
                        .foregroundStyle(.gray)
                default:
                    ProgressView()
                }
            }
            .frame(width: 44, height: 44)
            .clipShape(Circle())

            // Contact information
            VStack(alignment: .leading, spacing: 2) {
                Text(contact.name)
                    .font(.body.weight(.medium))

                Text(contact.email)
                    .font(.caption)
                    .foregroundStyle(.secondary)
            }

            Spacer()
        }
        .padding(.vertical, 4)
    }
}

// List using EquatableView
struct ContactListView: View {
    let contacts: [Contact]

    var body: some View {
        List {
            ForEach(contacts) { contact in
                // EquatableView prevents re-renders if contact unchanged
                EquatableView(content: ContactRow(contact: contact))
            }
        }
    }
}

Tối ưu hóa này giảm đáng kể tải CPU trong quá trình cuộn nhanh, đặc biệt với các ô chứa tính toán hoặc hình ảnh.

Sẵn sàng chinh phục phỏng vấn iOS?

Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.

Quản Lý Tải Hình Ảnh Bất Đồng Bộ

Hình ảnh thường trở thành điểm nghẽn hiệu suất trong danh sách. Xử lý không đúng cách gây ra giật khi cuộn và tiêu thụ bộ nhớ quá mức.

ImageLoadingOptimization.swiftswift
import SwiftUI

// Singleton image cache
actor ImageCache {
    static let shared = ImageCache()

    private var cache = NSCache<NSString, UIImage>()

    private init() {
        // Memory limit: 50 MB
        cache.totalCostLimit = 50 * 1024 * 1024
    }

    func image(for url: URL) -> UIImage? {
        cache.object(forKey: url.absoluteString as NSString)
    }

    func setImage(_ image: UIImage, for url: URL) {
        // Cost estimation: image bytes
        let cost = Int(image.size.width * image.size.height * 4)
        cache.setObject(image, forKey: url.absoluteString as NSString, cost: cost)
    }
}

// Optimized image view with caching
struct CachedAsyncImage: View {
    let url: URL?
    let size: CGSize

    @State private var image: UIImage?
    @State private var isLoading = false

    var body: some View {
        Group {
            if let image {
                Image(uiImage: image)
                    .resizable()
                    .aspectRatio(contentMode: .fill)
            } else if isLoading {
                Rectangle()
                    .fill(Color.gray.opacity(0.2))
                    .overlay(ProgressView())
            } else {
                Rectangle()
                    .fill(Color.gray.opacity(0.2))
            }
        }
        .frame(width: size.width, height: size.height)
        .clipped()
        .task(id: url) {
            await loadImage()
        }
    }

    private func loadImage() async {
        guard let url else { return }

        // Check cache
        if let cached = await ImageCache.shared.image(for: url) {
            self.image = cached
            return
        }

        isLoading = true
        defer { isLoading = false }

        // Download and resize
        do {
            let (data, _) = try await URLSession.shared.data(from: url)

            // Resize to save memory
            if let original = UIImage(data: data),
               let resized = await resizeImage(original, to: size) {
                await ImageCache.shared.setImage(resized, for: url)
                self.image = resized
            }
        } catch {
            // Handle error silently
        }
    }

    private func resizeImage(_ image: UIImage, to size: CGSize) async -> UIImage? {
        // Use screen scale
        let scale = await UIScreen.main.scale
        let targetSize = CGSize(
            width: size.width * scale,
            height: size.height * scale
        )

        return await withCheckedContinuation { continuation in
            DispatchQueue.global(qos: .userInitiated).async {
                let renderer = UIGraphicsImageRenderer(size: targetSize)
                let resized = renderer.image { _ in
                    image.draw(in: CGRect(origin: .zero, size: targetSize))
                }
                continuation.resume(returning: resized)
            }
        }
    }
}

Triển khai này kết hợp bộ đệm bộ nhớ, thay đổi kích thước phòng ngừa và tải bất đồng bộ cho trải nghiệm cuộn mượt mà.

Prefetching Thông Minh Để Dự Đoán

Đối với danh sách rất dài, prefetching tải hình ảnh trước khi chúng trở nên hiển thị:

ImagePrefetching.swiftswift
// Prefetching coordinator
@Observable
final class ImagePrefetcher {
    private var prefetchTasks: [URL: Task<Void, Never>] = [:]
    private let prefetchDistance = 10  // Number of items ahead

    func prefetchImages(for items: [Contact], visibleRange: Range<Int>) {
        // Calculate prefetch range
        let prefetchStart = max(0, visibleRange.lowerBound - prefetchDistance)
        let prefetchEnd = min(items.count, visibleRange.upperBound + prefetchDistance)

        // Launch prefetch for items in range
        for index in prefetchStart..<prefetchEnd {
            guard let url = items[index].avatarURL else { continue }

            // Avoid duplicates
            guard prefetchTasks[url] == nil else { continue }

            prefetchTasks[url] = Task {
                // Check if already cached
                if await ImageCache.shared.image(for: url) != nil {
                    return
                }

                // Prefetch
                do {
                    let (data, _) = try await URLSession.shared.data(from: url)
                    if let image = UIImage(data: data) {
                        await ImageCache.shared.setImage(image, for: url)
                    }
                } catch {
                    // Ignore prefetch errors
                }
            }
        }

        // Cancel out-of-range prefetches
        cancelOutOfRangePrefetches(validRange: prefetchStart..<prefetchEnd, items: items)
    }

    private func cancelOutOfRangePrefetches(validRange: Range<Int>, items: [Contact]) {
        let validURLs = Set(
            items[validRange].compactMap { $0.avatarURL }
        )

        for (url, task) in prefetchTasks {
            if !validURLs.contains(url) {
                task.cancel()
                prefetchTasks.removeValue(forKey: url)
            }
        }
    }
}

Lựa Chọn Giữa List và LazyVStack

Lựa chọn giữa ListLazyVStack phụ thuộc vào trường hợp sử dụng. Mỗi thành phần có những ưu điểm cụ thể đáng để tìm hiểu.

ListVsLazyVStack.swiftswift
// ✅ List: ideal for interactive content
// - Automatic cell recycling
// - Native swipe actions support
// - Built-in separators and styles
struct ContactsWithSwipeActions: View {
    @State private var contacts: [Contact] = []

    var body: some View {
        List {
            ForEach(contacts) { contact in
                ContactRow(contact: contact)
                    .swipeActions(edge: .trailing) {
                        Button(role: .destructive) {
                            deleteContact(contact)
                        } label: {
                            Label("Delete", systemImage: "trash")
                        }
                    }
                    .swipeActions(edge: .leading) {
                        Button {
                            favoriteContact(contact)
                        } label: {
                            Label("Favorite", systemImage: "star")
                        }
                        .tint(.yellow)
                    }
            }
        }
        .listStyle(.plain)
    }

    private func deleteContact(_ contact: Contact) {
        contacts.removeAll { $0.id == contact.id }
    }

    private func favoriteContact(_ contact: Contact) {
        // Favorite logic
    }
}

// ✅ LazyVStack: ideal for custom layouts
// - Full control over spacing and padding
// - No imposed styles
// - Better performance for simple display
struct CustomFeedView: View {
    let posts: [Post]

    var body: some View {
        ScrollView {
            LazyVStack(spacing: 16) {
                ForEach(posts) { post in
                    PostCard(post: post)
                }
            }
            .padding(.horizontal)
        }
    }
}

// Post model for example
struct Post: Identifiable {
    let id: UUID
    let author: String
    let content: String
    let imageURL: URL?
}

struct PostCard: View {
    let post: Post

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            // Header
            HStack {
                Circle()
                    .fill(Color.blue)
                    .frame(width: 40, height: 40)

                Text(post.author)
                    .font(.headline)

                Spacer()
            }

            // Content
            Text(post.content)

            // Optional image
            if let imageURL = post.imageURL {
                CachedAsyncImage(url: imageURL, size: CGSize(width: 300, height: 200))
                    .cornerRadius(12)
            }
        }
        .padding()
        .background(Color(.systemBackground))
        .cornerRadius(16)
        .shadow(color: .black.opacity(0.1), radius: 4, y: 2)
    }
}
Tái sử dụng ô

List chủ động tái sử dụng các ô, có thể gây ra vấn đề với state cục bộ (@State). Các giá trị @State trong các ô List có thể được sử dụng lại bất ngờ. Nên lưu trữ state trong mô hình dữ liệu hoặc trong ViewModel.

Section và Header Được Tối Ưu

Tổ chức nội dung thành các section cải thiện khả năng đọc nhưng có thể ảnh hưởng đến hiệu suất nếu được triển khai kém. Header được pin và quản lý section đòi hỏi sự chú ý đặc biệt.

OptimizedSections.swiftswift
// Grouped data model
struct GroupedContacts {
    let letter: String
    let contacts: [Contact]
}

// View with optimized sections
struct SectionedContactList: View {
    let groupedContacts: [GroupedContacts]

    var body: some View {
        ScrollView {
            LazyVStack(spacing: 0, pinnedViews: [.sectionHeaders]) {
                ForEach(groupedContacts, id: \.letter) { group in
                    Section {
                        // Section content
                        ForEach(group.contacts) { contact in
                            ContactRow(contact: contact)
                                .padding(.horizontal)
                                .padding(.vertical, 8)

                            // Custom separator
                            if contact.id != group.contacts.last?.id {
                                Divider()
                                    .padding(.leading, 68)
                            }
                        }
                    } header: {
                        // Optimized pinned header
                        SectionHeader(title: group.letter)
                    }
                }
            }
        }
    }
}

// Lightweight header for performance
struct SectionHeader: View {
    let title: String

    var body: some View {
        Text(title)
            .font(.headline)
            .foregroundStyle(.secondary)
            .frame(maxWidth: .infinity, alignment: .leading)
            .padding(.horizontal)
            .padding(.vertical, 8)
            .background(.ultraThinMaterial)
    }
}

// Optimized grouping function
extension Array where Element == Contact {
    func groupedByFirstLetter() -> [GroupedContacts] {
        // Dictionary for O(n) grouping
        var groups: [String: [Contact]] = [:]

        for contact in self {
            let letter = String(contact.name.prefix(1)).uppercased()
            groups[letter, default: []].append(contact)
        }

        // Sort groups alphabetically
        return groups
            .map { GroupedContacts(letter: $0.key, contacts: $0.value) }
            .sorted { $0.letter < $1.letter }
    }
}

Header được pin (pinnedViews: [.sectionHeaders]) vẫn hiển thị trong khi cuộn, cải thiện điều hướng trong các danh sách dài.

Phân Trang và Cuộn Vô Hạn

Đối với tập dữ liệu lớn, phân trang tránh tải tất cả dữ liệu vào bộ nhớ. Việc triển khai phải minh bạch đối với người dùng.

InfiniteScrolling.swiftswift
// ViewModel handling pagination
@Observable
final class PaginatedListViewModel {
    private(set) var items: [Contact] = []
    private(set) var isLoading = false
    private(set) var hasMorePages = true

    private var currentPage = 0
    private let pageSize = 20
    private let dataService: ContactDataService

    init(dataService: ContactDataService) {
        self.dataService = dataService
    }

    func loadInitialData() async {
        guard items.isEmpty else { return }
        await loadNextPage()
    }

    func loadMoreIfNeeded(currentItem: Contact) async {
        // Trigger loading when approaching the end
        guard let index = items.firstIndex(where: { $0.id == currentItem.id }) else {
            return
        }

        // Load 5 items before the end
        let thresholdIndex = items.count - 5

        if index >= thresholdIndex {
            await loadNextPage()
        }
    }

    private func loadNextPage() async {
        guard !isLoading, hasMorePages else { return }

        isLoading = true
        defer { isLoading = false }

        do {
            let newItems = try await dataService.fetchContacts(
                page: currentPage,
                limit: pageSize
            )

            items.append(contentsOf: newItems)
            currentPage += 1
            hasMorePages = newItems.count == pageSize
        } catch {
            // Handle error
        }
    }
}

// View with infinite scroll
struct InfiniteContactList: View {
    @State private var viewModel: PaginatedListViewModel

    init(dataService: ContactDataService) {
        _viewModel = State(initialValue: PaginatedListViewModel(dataService: dataService))
    }

    var body: some View {
        List {
            ForEach(viewModel.items) { contact in
                ContactRow(contact: contact)
                    .task {
                        // Check if more loading needed
                        await viewModel.loadMoreIfNeeded(currentItem: contact)
                    }
            }

            // Loading indicator at end of list
            if viewModel.isLoading {
                HStack {
                    Spacer()
                    ProgressView()
                    Spacer()
                }
                .padding()
            }
        }
        .task {
            await viewModel.loadInitialData()
        }
    }
}

// Protocol for data service
protocol ContactDataService {
    func fetchContacts(page: Int, limit: Int) async throws -> [Contact]
}

Mẫu này đảm bảo tải mượt mà mà không chặn giao diện và cho phép quản lý bộ nhớ hiệu quả.

Sẵn sàng chinh phục phỏng vấn iOS?

Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.

Profiling Với Instruments

Việc xác định các vấn đề hiệu suất đòi hỏi các công cụ đo lường chính xác. Instruments cung cấp một số mẫu phù hợp với SwiftUI.

ProfilingHelpers.swiftswift
// Measurement points for debugging
struct PerformanceMonitor {
    // Measure view creation time
    static func measureViewCreation<T: View>(
        _ name: String,
        @ViewBuilder content: () -> T
    ) -> T {
        let start = CFAbsoluteTimeGetCurrent()
        let view = content()
        let elapsed = CFAbsoluteTimeGetCurrent() - start

        #if DEBUG
        if elapsed > 0.016 {  // More than 16ms = frame drop
            print("⚠️ [\(name)] View creation took \(elapsed * 1000)ms")
        }
        #endif

        return view
    }
}

// Extension to trace renders
extension View {
    func debugRender(_ label: String) -> some View {
        #if DEBUG
        let _ = Self._printChanges()
        print("🔄 Rendering: \(label)")
        #endif
        return self
    }

    func measureRender(_ label: String) -> some View {
        modifier(RenderMeasureModifier(label: label))
    }
}

struct RenderMeasureModifier: ViewModifier {
    let label: String
    @State private var renderCount = 0

    func body(content: Content) -> some View {
        content
            .onAppear {
                renderCount += 1
                #if DEBUG
                print("📊 [\(label)] Render count: \(renderCount)")
                #endif
            }
    }
}

Danh Sách Kiểm Tra Tối Ưu Hóa Với Instruments

Để profiling danh sách SwiftUI hiệu quả:

  1. Time Profiler: xác định các hàm tiêu thụ CPU nhiều nhất
  2. Allocations: xác minh sự tăng trưởng bộ nhớ trong khi cuộn
  3. SwiftUI Instrument: trực quan hóa đánh giá body
  4. Core Animation: phát hiện rớt khung hình
InstrumentsExample.swiftswift
// Instrumented view for profiling
struct ProfiledContactList: View {
    let contacts: [Contact]

    var body: some View {
        let _ = Self._printChanges()  // Shows changes triggering re-render

        List {
            ForEach(contacts) { contact in
                ContactRow(contact: contact)
                    .measureRender("ContactRow-\(contact.id)")
            }
        }
    }
}
Self._printChanges()

API debugging SwiftUI này in ra console các thuộc tính nào đã thay đổi và kích hoạt việc đánh giá lại body. Cần thiết để xác định các render lại không cần thiết.

Tối Ưu Hóa Nâng Cao Với drawingGroup

Đối với các view phức tạp với nhiều hiệu ứng hình ảnh, drawingGroup() có thể cải thiện đáng kể hiệu suất bằng cách rasterize view thành một lớp Metal.

DrawingGroupOptimization.swiftswift
// Cell with complex visual effects
struct ComplexVisualRow: View {
    let item: Item

    var body: some View {
        HStack(spacing: 16) {
            // Circle with gradient and shadow
            Circle()
                .fill(
                    RadialGradient(
                        colors: [.blue, .purple, .pink],
                        center: .center,
                        startRadius: 0,
                        endRadius: 25
                    )
                )
                .frame(width: 50, height: 50)
                .shadow(color: .purple.opacity(0.5), radius: 8, y: 4)

            VStack(alignment: .leading, spacing: 4) {
                Text(item.name)
                    .font(.headline)

                // Progress bar with gradient
                GeometryReader { geometry in
                    Capsule()
                        .fill(Color.gray.opacity(0.2))
                        .overlay(alignment: .leading) {
                            Capsule()
                                .fill(
                                    LinearGradient(
                                        colors: [.green, .yellow, .orange],
                                        startPoint: .leading,
                                        endPoint: .trailing
                                    )
                                )
                                .frame(width: geometry.size.width * item.progress)
                        }
                }
                .frame(height: 8)
            }
        }
        .padding()
        // Rasterization for performance
        .drawingGroup()
    }
}

// List using optimized cells
struct OptimizedComplexList: View {
    let items: [Item]

    var body: some View {
        ScrollView {
            LazyVStack(spacing: 8) {
                ForEach(items) { item in
                    ComplexVisualRow(item: item)
                }
            }
            .padding()
        }
    }
}

struct Item: Identifiable {
    let id: UUID
    let name: String
    let progress: Double
}

drawingGroup() đặc biệt hiệu quả cho các view kết hợp gradient, bóng và hiệu ứng làm mờ.

Kết Luận

Tối ưu hóa danh sách SwiftUI dựa trên sự hiểu biết sâu sắc về các cơ chế lazy loading, tái sử dụng và so sánh view. Các kỹ thuật được trình bày cho phép xây dựng giao diện có khả năng xử lý hàng nghìn phần tử trong khi duy trì cuộn mượt mà ở 60 FPS.

Danh Sách Kiểm Tra Hiệu Suất SwiftUI

  • ✅ Sử dụng LazyVStack hoặc List thay vì VStack cho các tập hợp
  • ✅ Triển khai Identifiable với ID ổn định và duy nhất
  • ✅ Áp dụng Equatable cho các ô phức tạp
  • ✅ Cache và thay đổi kích thước hình ảnh trước khi hiển thị
  • ✅ Tải trước dữ liệu với prefetching thông minh
  • ✅ Chọn List cho tương tác (swipe) hoặc LazyVStack cho layout tùy chỉnh
  • ✅ Sử dụng pinnedViews cho header section
  • ✅ Triển khai phân trang cho tập dữ liệu lớn
  • ✅ Profiling thường xuyên với Instruments
  • ✅ Áp dụng drawingGroup() cho các view với hiệu ứng hình ảnh phức tạp

Bắt đầu luyện tập!

Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.

Thẻ

#swiftui
#ios
#performance
#lazyvstack
#swift

Chia sẻ

Bài viết liên quan