SwiftUI Performans: LazyVStack ve Karmaşık Listeleri Optimize Etme

LazyVStack ve SwiftUI listeleri için optimizasyon teknikleri. Bellek tüketimini azaltın, kaydırma performansını iyileştirin ve sık karşılaşılan tuzakları önleyin.

SwiftUI LazyVStack ve karmaşık liste performans optimizasyonu

Listeler, iOS uygulamalarında en sık kullanılan bileşenlerden birini temsil eder. SwiftUI'nin LazyVStack ve List bileşenleri veri koleksiyonlarını göstermek için performanslı çözümler sunar, ancak yanlış kullanım kullanıcı deneyimini hızla bozabilir. Bu bileşenlerin iç mekanizmalarını anlamak, sık karşılaşılan tuzakları önlemeye ve akıcı arayüzler oluşturmaya yardımcı olur.

Bu makalenin kapsamı

Bu makale SwiftUI listeleri için temel optimizasyon tekniklerini sunar: lazy loading, view geri dönüşümü, tanımlayıcı yönetimi ve büyük veri kümeleri için gelişmiş desenler.

SwiftUI'de Lazy Loading'i Anlamak

Lazy loading, view'ları yalnızca ekranda görünür hale geldiklerinde örneklendirir. Tüm alt view'ları hemen oluşturan VStack'in aksine, LazyVStack bu oluşturma işlemini erteler ve bellek tüketimini ile başlangıç render süresini önemli ölçüde azaltır.

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)
                }
            }
        }
    }
}

Performans farkı yalnızca birkaç yüz öğeyle bile belirgin hale gelir. 10.000 öğeyle VStack başlatılması birkaç saniye sürebilirken LazyVStack anında kalır.

Lazy Loading Etkisini Ölçmek

Instruments, bellek ve CPU kullanımını hassas bir şekilde ölçmeye olanak tanır. İşte farkı gösteren bir test view'i:

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()
    }
}

VStack ile çalıştırıldığında, 10.000 logun tamamı hemen görünür. LazyVStack ile, yalnızca görünür öğeler (ekran boyutuna bağlı olarak yaklaşık 15-20) başlangıçta loglanır ve kaydırma sırasında daha fazlası ortaya çıkar.

Tutma davranışı

LazyVStack, oluşturulan view'ları göründükten sonra bellekte tutar. Hücreleri aktif olarak geri dönüştüren List'in aksine, LazyVStack içindeki view'lar üst bileşen yok edilene kadar kalır.

Kararlı Tanımlayıcıların Önemi

Tanımlayıcılar SwiftUI liste güncellemelerinin merkezi mekanizmasını oluşturur. Kararsız bir tanımlayıcı gereksiz view yeniden oluşturmalarına neden olur ve yanlış animasyonlar veya kaydırma konumu kaybı gibi görsel hatalara yol açabilir.

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)
            }
        }
    }
}

Benzersiz ve kalıcı bir tanımlayıcı kullanmak, SwiftUI'nin güncellemeler, animasyonlar ve karşılaştırmalar sırasında öğeleri doğru şekilde ayırt edebilmesini garantiler.

Equatable ile Hücre Optimizasyonu

SwiftUI yeniden render gerekip gerekmediğini belirlemek için view'ları karşılaştırır. Varsayılan olarak bu karşılaştırma reflection kullanır ki bu da maliyetli olabilir. Equatable uygulamak optimize edilmiş ve açık karşılaştırma sağlar.

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))
            }
        }
    }
}

Bu optimizasyon, özellikle hesaplama veya görsel içeren hücrelerde hızlı kaydırma sırasında CPU yükünü önemli ölçüde azaltır.

iOS mülakatlarında başarılı olmaya hazır mısın?

İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.

Asenkron Görsel Yüklemenin Yönetimi

Görseller listelerde sıklıkla performans darboğazı haline gelir. Yanlış yönetim kaydırma takılmalarına ve aşırı bellek tüketimine neden olur.

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)
            }
        }
    }
}

Bu uygulama, akıcı bir kaydırma deneyimi için bellek önbelleğini, önleyici yeniden boyutlandırmayı ve asenkron yüklemeyi birleştirir.

Öngörü için Akıllı Prefetching

Çok uzun listeler için prefetching görselleri görünür hale gelmeden önce yükler:

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)
            }
        }
    }
}

List ve LazyVStack Arasında Seçim

List ile LazyVStack arasındaki seçim kullanım durumuna bağlıdır. Her bileşenin bilinmesi gereken kendine özgü avantajları vardır.

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)
    }
}
Hücre geri dönüşümü

List hücreleri aktif olarak geri dönüştürür ve bu, yerel state (@State) ile sorunlara neden olabilir. List hücrelerindeki @State değerleri beklenmedik şekilde yeniden kullanılabilir. State'i veri modelinde veya bir ViewModel'de saklamak tercih edilmelidir.

Optimize Edilmiş Bölümler ve Başlıklar

İçeriği bölümler halinde organize etmek okunabilirliği artırır ancak kötü uygulanırsa performansı etkileyebilir. Sabitlenmiş başlıklar ve bölüm yönetimi özel dikkat gerektirir.

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 }
    }
}

Sabitlenmiş başlıklar (pinnedViews: [.sectionHeaders]) kaydırma sırasında görünür kalır ve uzun listelerde gezinmeyi iyileştirir.

Sayfalama ve Sonsuz Kaydırma

Büyük veri kümeleri için sayfalama tüm verileri belleğe yüklemekten kaçınır. Uygulama kullanıcı için şeffaf olmalıdır.

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]
}

Bu desen, arayüzü engellemeden akıcı yükleme sağlar ve verimli bellek yönetimine olanak tanır.

iOS mülakatlarında başarılı olmaya hazır mısın?

İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.

Instruments ile Profilleme

Performans sorunlarını tanımlamak hassas ölçüm araçları gerektirir. Instruments, SwiftUI'ye uygun çeşitli şablonlar sunar.

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
            }
    }
}

Instruments Optimizasyon Kontrol Listesi

Etkili SwiftUI liste profillemesi için:

  1. Time Profiler: en çok CPU tüketen fonksiyonları tanımlayın
  2. Allocations: kaydırma sırasında bellek artışını doğrulayın
  3. SwiftUI Instrument: body değerlendirmelerini görselleştirin
  4. Core Animation: kare düşüşlerini tespit edin
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()

Bu SwiftUI hata ayıklama API'si, hangi özelliklerin değiştiğini ve body yeniden değerlendirmesini tetiklediğini konsola yazdırır. Gereksiz yeniden render'ları tanımlamak için vazgeçilmezdir.

drawingGroup ile İleri Düzey Optimizasyonlar

Birçok görsel efekt içeren karmaşık view'lar için drawingGroup(), view'ı bir Metal katmanına rasterize ederek performansı önemli ölçüde artırabilir.

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(), gradyanlar, gölgeler ve bulanıklık efektlerini birleştiren view'larda özellikle etkilidir.

Sonuç

SwiftUI liste optimizasyonu, lazy loading, geri dönüşüm ve view karşılaştırma mekanizmalarının derin bir anlayışına dayanır. Sunulan teknikler, 60 FPS'de akıcı kaydırmayı korurken binlerce öğeyi işleyebilen arayüzler oluşturmaya olanak tanır.

SwiftUI Performans Kontrol Listesi

  • ✅ Koleksiyonlar için VStack yerine LazyVStack veya List kullanın
  • ✅ Kararlı, benzersiz ID'lerle Identifiable uygulayın
  • ✅ Karmaşık hücreler için Equatable benimseyin
  • ✅ Görselleri görüntülemeden önce önbelleğe alın ve yeniden boyutlandırın
  • ✅ Akıllı prefetching ile verileri önceden yükleyin
  • ✅ Etkileşimler (swipe) için List veya özel düzenler için LazyVStack seçin
  • ✅ Bölüm başlıkları için pinnedViews kullanın
  • ✅ Büyük veri kümeleri için sayfalama uygulayın
  • ✅ Düzenli olarak Instruments ile profilleyin
  • ✅ Karmaşık görsel efektlere sahip view'lara drawingGroup() uygulayın

Pratik yapmaya başla!

Mülakat simülatörleri ve teknik testlerle bilgini test et.

Etiketler

#swiftui
#ios
#performance
#lazyvstack
#swift

Paylaş

İlgili makaleler