১৭ নভে, ২০২৫·7 মিনিট পড়তে

দীর্ঘ SwiftUI লিস্টের পারফরম্যান্স টিউনিং: ব্যবহারিক সমাধানগুলি

দীর্ঘ SwiftUI লিস্টের পারফরম্যান্স টিউনিং: পুনঃরেন্ডারিং, স্থিতিশীল রো আইডেন্টিটি, পেজিনেশন, ইমেজ লোডিং, এবং পুরনো iPhone-এ মসৃণ স্ক্রলিং-এর ব্যবহারিক সমাধান।

দীর্ঘ SwiftUI লিস্টের পারফরম্যান্স টিউনিং: ব্যবহারিক সমাধানগুলি

বাস্তব SwiftUI অ্যাপে 'ধীর তালিকা' কেমন হয়

SwiftUI-তে একটি “ধীর তালিকা” সাধারণত কোনো বাগ নয়। এটা সেই মুহূর্ত যখন আপনার UI আপনার আঙুলের গতির সাথে তাল মিলাতে পারে না। স্ক্রল করার সময় আপনি লক্ষ্য করেন: তালিকাটি হকচক করে, ফ্রেম পড়ে, এবং সবকিছু ভারী মনে হয়।

সাধারণ লক্ষণগুলো:

  • বিশেষ করে পুরোনো ডিভাইসে স্ক্রল স্টাটার করে
  • রো-গুলো ফ্লিকার করে বা সংক্ষিপ্ত সময়ের জন্য ভুল কনটেন্ট দেখায়
  • ট্যাপ বিলম্বিত মনে হয়, বা সোয়াইপ অ্যাকশন দেরিতে শুরু হয়
  • ফোন গরম হয় এবং ব্যাটারি দ্রুত হারায়
  • স্ক্রল যত বেশি করেন মেমরি ব্যবহার বাড়ে

দীর্ঘ তালিকা ছোট দেখতে পেলেও ধীর মনে হতে পারে, কারণ খরচ কেবল পিক্সেল আঁকার নয়। SwiftUI-কে এখনও নির্ধারণ করতে হয় প্রতিটি রো কী, লেআউট গণনা করা, ফন্ট ও ইমেজ রেজলভ করা, আপনার ফরম্যাটিং কোড চালানো, এবং ডেটা বদলালে আপডেট ডিফ করা। এই কাজগুলোর কোনোটি অতিরিক্ত ভাবে ঘটলে তালিকা হটস্পট হয়ে যায়।

এখানেই দুটি ধারণা আলাদা করে দেখলে সুবিধা হয়। SwiftUI-তে একটি “রিরেন্ডার” প্রায়ই মানে ভিউর body পুনরায় গণনা। সেটা সাধারণত সস্তা। ব্যয়বহულია সেই পুনরায় গণনা যা ট্রিগার করে: ভারী লেআউট, ইমেজ ডিকোডিং, টেক্সট মেজারমেন্ট, বা অনেক রো পুনর্নির্মাণ কারণ SwiftUI তাদের আইডেন্টিটি বদলে মনে করেছে।

ধরা যাক একটি চ্যাট যেখানে 2,000টি মেসেজ আছে। প্রতি সেকেন্ডে নতুন মেসেজ আসে, এবং প্রতিটি রো টাইমস্ট্যাম্প ফরম্যাট করে, মাল্টি-লাইনের টেক্সট মাপ করে, এবং অ্যাভাটার লোড করে। শুধু একটি আইটেম যোগ করলেও, খারাপভাবে স্কোপ করা স্টেট চেঞ্জ অনেক রোকে আবার মূল্যায়ন করাতে পারে, এবং তাদের মধ্যে কিছু পুনরায় আঁকা হতে পারে।

উদ্দেশ্য মাইক্রো-অপটিমাইজেশন নয়। আপনি চান মসৃণ স্ক্রলিং, তাত্ক্ষণিক ট্যাপ, এবং আপডেট যা কেবল সেই রো-গুলোকে স্পর্শ করে সেগুলোই বদলায়। নিচের সমাধানগুলো স্থিতিশীল আইডেন্টিটি, সস্তা রো-গুলি, অপ্রয়োজনীয় আপডেট কমানো, এবং নিয়ন্ত্রিত লোডিং-এ ফোকাস করে।

মূল কারণগুলো: আইডেন্টিটি, প্রতি রো কাজ, এবং আপডেট স্টর্ম

যখন SwiftUI তালিকা ধীর মনে হয়, সাধারণত সেটা “অধিক রো” জনিত নয়। স্ক্রল করার সময় অতিরিক্ত কাজ হচ্ছে: রো পুনর্নির্মাণ, লেআউট পুনরায় গণনা, বা বারবার ইমেজ লোড করা।

বেশিরভাগ মূল কারণ তিনটি ভাগে পড়ে:

  • অস্থিতিশীল আইডেন্টিটি: রো-গুলোর স্থির id নেই, অথবা এমন মানে \\.self ব্যবহার করছেন যা পরিবর্তনশীল। SwiftUI পুরোনো রো-কে নতুন রো-র সাথে মিলাতে পারে না, ফলে অপ্রয়োজনীয়ভাবে বেশিবার পুনর্নিমাণ করে।
  • প্রতি রো বেশি কাজ: তারিখ ফরম্যাটিং, ফিল্টারিং, ইমেজ রিসাইজিং, বা নেটওয়ার্ক/ডিস্ক কাজ রো ভিউয়ের ভিতরে করা হচ্ছে।
  • আপডেট স্টর্ম: এক পরিবর্তন (টাইপিং, টাইমার টিক, প্রোগ্রেস আপডেট) ঘন ঘন স্টেট আপডেট ট্রিগার করে এবং তালিকা বারবার রিফ্রেশ হয়।

উদাহরণ: আপনার কাছে 2,000টি অর্ডার আছে। প্রতিটি রো কারেন্সি ফরম্যাট করে, এট্রিবিউটেড স্ট্রিং তৈরি করে, এবং ইমেজ ফেচ শুরু করে। পাশাপাশি প্যারেন্ট ভিউতে একটি "last synced" টাইমার প্রতি সেকেন্ডে আপডেট করে। অর্ডার ডেটা বদলালে না হলেও, সেই টাইমার তালিকাটিকে এতবার অব্যবহার্য করে দিতে পারে যে স্ক্রলিং চপি হয়ে যায়।

কেন List এবং LazyVStack আলাদা মনে হতে পারে

List কেবল একটি স্ক্রল ভিউ নয়; এটি টেবিল/কলেকশন আচরণ এবং সিস্টেম অপ্টিমাইজেশনের চারপাশে ডিজাইন করা। এটি প্রায়ই বড় ডেটাসেট কম মেমরিতে পরিচালনা করে, কিন্তু আইডেন্টিটি ও ঘন ঘন আপডেটের প্রতি সংবেদনশীল হতে পারে।

ScrollView + LazyVStack আপনাকে লেআউট ও ভিজ্যুয়াল নিয়ন্ত্রণ বেশি দেয়, কিন্তু সহজেই অতিরিক্ত লেআউট কাজ বা ব্যয়বহুল আপডেট ট্রিগার করা যায়। পুরোনো ডিভাইসে সেই অতিরিক্ত কাজ দ্রুত দেখা যায়।

UI পুনর্লিখনের আগে মেপে দেখুন। স্থিতিশীল আইডি, রো-র বাইরে কাজ সরানো, এবং স্টেট চর্ন কমানো ছোটো ফিক্সগুলোর মধ্যে অনেক সময়ই সমস্যার সমাধান দেয় কন্টেইনার না বদলিয়েই।

রো আইডেন্টিটি ঠিক করুন যাতে SwiftUI দক্ষভাবে ডিফ করতে পারে

দীর্ঘ তালিকা জ্যাংকি মনে হলে, আইডেন্টিটি প্রায়ই দোষী। SwiftUI কোন রোReuse করা যায় তা আইডি তুলনা করে নির্ধারণ করে। যদি আইডি বদলে যায়, SwiftUI রো-টাকে নতুন মনে করে, পুরোনোগুলো ফেলে দেয়, এবং প্রয়োজনের চেয়ে বেশি পুনর্নির্মাণ করে। এটি এলোমেলো রি-রেন্ডার, স্ক্রল পজিশন হারানো, বা অকারণ অ্যানিমেশন হিসেবে দেখা যায়।

সবচেয়ে সহজ জয়: প্রতিটি রো-র id স্থিতিশীল এবং আপনার ডেটা সোর্সের সাথে যুক্ত রাখুন।

সাধারণ ভুল হলো ভিউর ভিতরে আইডেন্টিটি জেনারেট করা:

ForEach(items) { item in
  Row(item: item)
    .id(UUID())
}

এটি প্রতিটি রেন্ডারে একটি নতুন আইডি জোর করে তৈরি করে, তাই প্রতিটি রো প্রত্যেকবার "বিভিন্ন" হয়।

মডেলের মধ্যে ইতিমধ্যেই থাকা আইডি ব্যবহার করুন, যেমন ডাটাবেস প্রাইমারি কি, সার্ভার আইডি, বা একটি স্থিতিশীল স্লাগ। যদি না থাকে, মডেল তৈরি হওয়ার সময় একটায় তৈরি করুন — ভিউর ভিতরে নয়।

struct Item: Identifiable {
  let id: Int
  let title: String
}

List(items) { item in
  Row(item: item)
}

ইনডেক্স নিয়ে সতর্ক থাকুন। ForEach(items.indices, id: \.self) পজিশনের উপর আইডেন্টিটি নির্ভর করায়। যদি আপনি ইনসার্ট, ডিলিট, বা সোর্ট করেন, রো-গুলো "গমন" করে, এবং SwiftUI ভুল ভিউকে পুনরায় ব্যবহার করতে পারে। ইনডেক্স শুধুমাত্র তখনই ব্যবহার করুন যখন অ্যারে সত্যিই স্ট্যাটিক।

id: \.self ব্যবহার করলে নিশ্চিত হন উপাদানের Hashable মান সময়ের সাথে স্থিতিশীল থাকে। যদি হ্যাশ এমনভাবে বদলে যায় যে ক্ষেত্র আপডেট হলে আইডি বদলে যায়, তাহলে রো-র আইডেন্টিটি বদলে যাবে। Equatable এবং Hashable-এ নিরাপদ নীতি: সেগুলোকে একটি একক, স্থিতিশীল আইডির উপর ভিত্তি করে রাখুন, সম্পাদনাযোগ্য প্রপার্টি যেমন name বা isSelected-এর উপর নয়।

স্যানিটি চেকগুলো:

  • আইডি ডেটা সোর্স থেকে আসে (ভিউতে UUID() নয়)
  • রো কনটেন্ট বদলে আইডি বদলে না যায়
  • আইডেন্টিটি অ্যারে পজিশনের উপর নির্ভর না করে যতক্ষণ না তালিকা কখনো রিইরেক্ট হয় না

রি-রেন্ডার কমান: রো ভিউগুলোকে সস্তা করুন

একটি দীর্ঘ তালিকা প্রায়ই ধীর মনে হয় কারণ প্রতিটি রো body পুনরায় গণনার সময় অনেক কাজ করে। লক্ষ্যটি সহজ: প্রতিটি রো পুনর্নির্মাণ সস্তা করুন।

গোপন ব্যয়গুলোর মধ্যে একটি হলো বড় মান পাস করা। বড় struct, গভীর নেস্টেড মডেল, বা ভারী কম্পিউটেড প্রপার্টি ভিউ অচেনা হলেও অতিরিক্ত কাজ ট্রিগার করতে পারে। আপনি স্ট্রিং পুনর্নির্মাণ, তারিখ পার্সিং, ইমেজ রিসাইজ, বা জটিল লেআউট ট্রি বেশি বেশি তৈরি করছেন—এগুলো অনেক বেশি সময় নিতে পারে।

ধীর কাজ body-এর বাইরে নিয়ে যান

যদি কোনো কিছু ধীর হয়, সেটাকে রো body-তে বারবার পুনর্নির্মাণ করবেন না। ডেটা পৌঁছানোর সময় প্রি-কম্পিউট করুন, ভিউমডেলে ক্যাশ করুন, বা একটি ছোট হেল্পারে মেমোইজ করুন।

রো স্তরের যা খরচ দ্রুত বাড়ায়:

  • প্রতি রো নতুন DateFormatter বা NumberFormatter তৈরি করা
  • body-তে ভারী স্ট্রিং ফরম্যাটিং (joins, regex, markdown পার্সিং)
  • body-তে .map বা .filter দিয়ে ডেরাইভড অ্যারে তৈরি করা
  • ভিউ-তে বড় ব্লব পড়ে রূপান্তর করা (যেমন JSON ডিকোড করা)
  • অনেক নেস্টেড স্ট্যাক এবং কন্ডিশনালসহ অতিরিক্ত কনপ্লেক্স লেআউট

সরল উদাহরণ: ফরম্যাটারগুলো স্ট্যাটিক রাখুন এবং রো-তে প্রিফরম্যাটেড স্ট্রিং পাস করুন।

enum Formatters {
    static let shortDate: DateFormatter = {
        let f = DateFormatter()
        f.dateStyle = .medium
        f.timeStyle = .none
        return f
    }()
}

struct OrderRow: View {
    let title: String
    let dateText: String

    var body: some View {
        HStack {
            Text(title)
            Spacer()
            Text(dateText).foregroundStyle(.secondary)
        }
    }
}

রো ভাঙুন এবং যখন উপযোগী Equatable ব্যবহার করুন

যদি কেবল একটি ছোট অংশ পরিবর্তিত হয় (যেমন ব্যাজ কাউন্ট), সেটাকে একটি সাবভিউতে আলাদা করে রাখুন যাতে বাকিটা স্থিতিশীল থাকে।

যদি UI পুরোপুরি মান-চালিত হয়, একটি সাবভিউ Equatable করা (বা EquatableView দিয়ে মোড়ানো) SwiftUI-কে ইনপুট না বদলালে কাজ স্কিপ করতে সাহায্য করতে পারে। Equatable ইনপুটগুলো ছোট এবং নির্দিষ্ট রাখুন—পুরো মডেল নয়।

রাজ্য আপডেট নিয়ন্ত্রণ করুন যা পুরো তালিকা রিফ্রেশ ট্রিগার করে

Choose how you ship and host
Deploy to AppMaster Cloud, AWS, Azure, Google Cloud, or export source for self-hosting.
Explore AppMaster

কখনো কখনো রো-গুলো ঠিক থাকে, কিন্তু কিছু একটা পুরো তালিকাকে বারবার রিফ্রেশ করতে বলছে। স্ক্রলিং চলাকালে এমন ছোট আপডেটও স্টাটারে পরিণত হতে পারে, বিশেষ করে পুরোনো ডিভাইসগুলোতে।

একটি সাধারণ কারণ হলো মডেল বারবার পুনরায় তৈরি করা। যদি একটি প্যারেন্ট ভিউ পুনর্নির্মাণ করে এবং আপনি @ObservedObject ব্যবহার করে ভিউমডেল দিয়েছেন যা ভিউ নিজে মালিকানাধীন, তাহলে SwiftUI সেটি পুনরায় তৈরি করতে পারে, সাবস্ক্রিপশন রিসেট করে এবং নতুন প্রকাশ_trigger করে। যদি ভিউ মালিক হয়, @StateObject ব্যবহার করুন যাতে এটি একবার তৈরি হয় এবং স্থিতিশীল থাকে। বাইরের জিনিস Inject করা হলে @ObservedObject ব্যবহার করুন।

আরেকটি নীরব পারফরম্যান্স কিলার হলো ঘন ঘন প্রকাশ করা। টাইমার, Combine পাইপলাইন, এবং প্রোগ্রেস আপডেট প্রতি সেকেন্ডে অনেকবার রান করতে পারে। যদি কোনো প্রকাশিত প্রপার্টি তালিকাকে প্রভাবিত করে (অথবা এমন একটি শেয়ারড ObservableObject-এ থাকে যা স্ক্রিন ব্যবহার করে), প্রতি টিকে তালিকাকে বারবার অব্যবহার্য করে দিতে পারে।

উদাহরণ: একটি সার্চ ফিল্ড আছে যা প্রতিটি কীবোর্ড ধাক্কায় query আপডেট করে এবং 5,000 আইটেম ফিল্টার করে। যদি আপনি একেবারেই সঙ্গে সঙ্গে ফিল্টার করেন, তালিকা টাইপিং চলাকালে প্রতিনিয়ত রি-ডিফ হবে। কুয়েরিকে ডিবাউন্স করুন, এবং ছোট বিরতির পরে ফিল্টারেড অ্যারে আপডেট করুন।

সাধারণ প্যাটার্নগুলো যা সহায় হয়:

  • দ্রুত পরিবর্তনশীল মানগুলো তালিকা চালানো অবজেক্ট থেকে দূরে রাখুন (ছোট অবজেক্ট বা লোকাল @State ব্যবহার করুন)
  • সার্চ ও ফিল্টারিং ডিবাউন্স করুন যাতে টাইপিং বন্ধ হলে তালিকা আপডেট হয়
  • উচ্চ-ফ্রিকোয়েন্সি টাইমার প্রকাশ এড়িয়ে চলুন; কম ঘনতার সাথে আপডেট করুন বা কেবল মান বদলে গেলে আপডেট করুন
  • প্রতি-রো স্টেট লোকাল রাখুন (@State রোতেই) যতক্ষণ না সেটা সার্বজনীনভাবে প্রয়োজন
  • বড় মডেলগুলো ভাগ করুন: তালিকা ডেটার জন্য একটি ObservableObject, স্ক্রিন-স্তরের UI স্টেটের জন্য অন্যটি

নীতিটি সহজ: স্ক্রলিং টাইমকে শান্ত রাখুন। যদি কিছুই গুরুত্বপূর্ণ বদলে যায়নি, তালিকাকে কাজ করতে বলা উচিত নয়।

সঠিক কন্টেইনার নির্বাচন: List বনাম LazyVStack

আপনি যে কন্টেইনার বেছে নেবেন তা iOS কতটা কাজ করে তার ওপর প্রভাব ফেলে।

List সাধারণত নিরাপদ পছন্দ যখন আপনার UI একটি স্ট্যান্ডার্ড টেবিলের মতো: টেক্সট, ইমেজ, সোয়াইপ অ্যাকশন, সিলেকশন, সেপারেটর, এডিট মোড এবং অ্যাক্সেসিবিলিটি। ভিতরে অনেক বছর ধরে Apple অপ্টিমাইজেশন করা আছে।

কাস্টম লেআউট: কার্ড, মিক্সড কনটেন্ট ব্লক, বিশেষ হেডার বা ফিড-স্টাইল ডিজাইনের জন্য ScrollView+LazyVStack ভাল। “Lazy” মানে স্ক্রিনে আসার সঙ্গে সঙ্গে রো তৈরি করে, কিন্তু সব ক্ষেত্রে List-এর মতো আচরণ দেয় না। খুব বড় ডেটাসেটের ক্ষেত্রে এটি বেশি মেমরি নেয় এবং পুরোনো ডিভাইসে স্ক্রলিং ঝক্কি হতে পারে।

সরল সিদ্ধান্ত নিয়ম:

  • ক্লাসিক টেবিল স্ক্রিনের জন্য List ব্যবহার করুন: settings, inboxes, orders, admin lists
  • কাস্টম লেআউট ও মিক্সড কন্টেন্টের জন্য ScrollView + LazyVStack
  • হাজার হাজার আইটেম থাকলে এবং কেবল টেবিল লাগে, List দিয়ে শুরু করুন
  • পিক্সেল-পারফেক্ট কন্ট্রোল চাইলে LazyVStack চেষ্টা করুন, তারপর মেমরি ও ফ্রেম ড্রপ মেপুন

স্টাইলিং-এও সাবধান: প্রতি রো এফেক্ট যেমন শ্যাডো, ব্লার, জটিল ওভারলে রেন্ডারিং বাড়ায়। যদি ডেপথ চাইলে, পুরো রো নয় বরং ছোট উপাদানে এফেক্ট দিন (যেমন আইকনে)।

কংক্রিট উদাহরণ: 5,000 রো-সহ একটি "Orders" স্ক্রীন প্রায়ই List-এ মসৃণ থাকে কারন রো-গুলো পুনঃব্যবহৃত হয়। যদি আপনি LazyVStack-এ পরিবর্তন করে কার্ড-স্টাইল রো বানান বড় শ্যাডো ও ওভারলে দিয়ে, তবে কোড পরিষ্কার লাগলেও জ্যাংক দেখবেন।

পেজিনেশন যা মসৃণ মনে করে এবং মেমরি স্পাইক এড়ায়

Get identity right from day one
Design stable IDs and PostgreSQL tables visually before your SwiftUI UI ever renders a row.
Create Project

পেজিনেশন দীর্ঘ তালিকাকে দ্রুত রাখতে সাহায্য করে কারণ আপনি কম রো রেন্ডার করেন, কম মডেল মেমরিতে রাখেন, এবং SwiftUI-কে কম ডিফিং করতে হয়।

একটি পরিষ্কার পেজিং চুক্তি দিয়ে শুরু করুন: একটি নির্দিষ্ট পেজ সাইজ (উদাহরণস্বরূপ 30 থেকে 60 আইটেম), একটি “নো আরো রেজাল্ট” ফ্ল্যাগ, এবং একটি লোডিং রো যা কেবল তখনই দেখায় যখন ডেটা ফেচ করা হচ্ছে।

সাধারণ ফাঁদ হল পরবর্তী পেজ তখনি ট্রিগার করা যখন শেষ রোটি দেখা যাবে। সেটা প্রায়ই দেরি হয়ে যায়, তাই ব্যবহারকারী শেষ পয়েন্টে পৌঁছলে বিরতি দেখতে পায়। পরিবর্তে, শেষ কয়েক রো-র মধ্যে যখন কোনো একটি রো দেখা যায় তখন লোডিং শুরু করুন।

সরল প্যাটার্ন:

@State private var items: [Item] = []
@State private var isLoading = false
@State private var reachedEnd = false

func loadNextPageIfNeeded(currentIndex: Int) {
    guard !isLoading, !reachedEnd else { return }
    let threshold = max(items.count - 5, 0)
    guard currentIndex >= threshold else { return }

    isLoading = true
    Task {
        let page = try await api.fetchPage(after: items.last?.id)
        await MainActor.run {
            let newUnique = page.filter { p in !items.contains(where: { $0.id == p.id }) }
            items.append(contentsOf: newUnique)
            reachedEnd = page.isEmpty
            isLoading = false
        }
    }
}

এটি সাধারণ সমস্যা এড়ায় যেমন ডুপ্লিকেট রো (ওভারল্যাপিং API ফলাফল), একাধিক onAppear কল থেকে রেস কন্ডিশন, এবং একযোগে অনেক লোডিং করা।

আপনার তালিকা পুল টু রিফ্রেশ সাপোর্ট করলে, পেজিং স্টেট সাবধানে রিসেট করুন (items খালি করুন, reachedEnd রিসেট করুন, সম্ভব হলে ইন-ফ্লাইট টাস্কগুলোキャンসেল করুন)। যদি ব্যাকএন্ড নিয়ন্ত্রণ করতে পারেন, স্থিতিশীল আইডি ও কার্সার-ভিত্তিক পেজিং UI কে উল্লেখযোগ্যভাবে মসৃণ করে।

ইমেজ, টেক্সট, এবং লেআউট: রো রেন্ডারিংকে হালকা রাখুন

Create fast admin screens quickly
Build internal tools and admin lists that stay responsive even with thousands of records.
Try AppMaster

দীর্ঘ তালিকা সাধারণত কন্টেইনারের কারণে ধীর হয় না। বেশিরভাগ ক্ষেত্রেই সমস্যার মূল রো। ইমেজই সাধারণত অপরাধী: ডিকোডিং, রিসাইজিং, এবং ড্রয়িং স্ক্রল স্পিড ছাড়িয়ে যেতে পারে, বিশেষ করে পুরোনো ডিভাইসে।

রিমোট ইমেজ লোড করলে নিশ্চিত করুন ভারি কাজ মেইন থ্রেডে না ঘটে স্ক্রলিং চলাকালে। এছাড়াও 44-80 pt থাম্বনেইলের জন্য পূর্ণ রেজোলিউশনের অ্যাসেট ডাউনলোড করা এড়িয়ে চলুন।

উদাহরণ: অ্যাভাটার সহ একটি “Messages” স্ক্রীন। যদি প্রতিটি রো 2000x2000 ইমেজ ডাউনলোড করে, সেটিকে স্কেল করে, এবং ব্লার বা শ্যাডো প্রয়োগ করে, তালিকা স্টাটার করবে এমনকি যদি ডেটা মডেল সাদা।

ইমেজ কাজ ভবিষ্যদ্বাণীময় রাখুন

উচ্চ-প্রভাবযুক্ত অভ্যাসগুলো:

  • সার্ভার-সাইড বা প্রি-জেনারেটেড থাম্বনেইল ব্যবহার করুন যা প্রদর্শিত সাইজের নিকট
  • যেখানে সম্ভব মেইন থ্রেডের বাইরে ডিকোড ও রিসাইজ করুন
  • থাম্বনেইল ক্যাশ করুন যাতে দ্রুত স্ক্রলিং পুনরায় ফেচ বা রি-ডিকোড না করে
  • ফ্লিকার ও লেআউট ঝাঁকুনি প্রতিরোধে একই সাইজের প্লেসহোল্ডার ব্যবহার করুন
  • রো-তে ভারী মডিফায়ার (ভারি শ্যাডো, মাস্ক, ব্লার) এড়িয়ে চলুন

লেআউট স্থির করুন যাতে থ্র্যাশ এড়ানো যায়

যদি রো উচ্চতা বারবার বদলায় SwiftUI মাপাতে বেশি সময় খরচ করতে পারে। রো-গুলোকে প্রেডিকটেবল রাখার চেষ্টা করুন: থাম্বনেইলের জন্য ফিক্সড ফ্রেম, ধারাবাহিক লাইন লিমিট, এবং স্থিতিশীল স্পেসিং। যদি টেক্সট বাড়তে পারে, সীমা দিন (উদাহরণস্বরূপ ১–২ লাইন) যাতে একটি আপডেট অতিরিক্ত মেপিং কাজ না করে।

প্লেসহোল্ডারও গুরুত্বপূর্ণ। একটি ধূসর বৃত্ত পরে পরে অ্যাভাটার হওয়ার আগেও একই ফ্রেম দখল করা উচিত, যাতে রো মাঝ স্ক্রলিং-এ পুনঃপ্রবাহ না করে।

কীভাবে মাপবেন: Instruments চেক যা সঠিক বটলনেক উন্মোচন করে

শুধু "জ্যাংকি মনে হচ্ছে" থেকে কাজ করা অনুমানভিত্তিক হতে পারে। Instruments আপনাকে দেখায় কি মেইন থ্রেডে রান করছে, দ্রুত স্ক্রলিংয়ের সময় কী অস্থায়ীভাবে অ্যালোকেট হচ্ছে, এবং কী ফ্রেম ড্রপ ঘটাচ্ছে।

বাস্তব ডিভাইসে (যদি আপনি সমর্থন করেন তাহলে পুরোনো ডিভাইসে) একটি বেসলাইন ডিফাইন করুন। একটি পুনরাবৃত্তিযোগ্য কাজ করুন: স্ক্রীন খুলুন, দ্রুত টপ থেকে বটম স্ক্রল করুন, লোড-মোর একবার ট্রিগার করুন, তারপর উপরে ফিরে স্ক্রল করুন। বাজে হিট-পয়েন্ট, মেমরি পিক, এবং UI প্রতিক্রিয়া লক্ষ্য করুন।

তিনটি Instruments ভিউ যা ফল দেয়

একসঙ্গে এগুলো ব্যবহার করুন:

  • Time Profiler: স্ক্রল করার সময় মেইন-থ্রেড স্পাইক দেখুন। লেআউট, টেক্সট মেজারমেন্ট, JSON পার্সিং, এবং ইমেজ ডিকোডিং এখানে প্রায়ই সমস্যা বোঝায়।
  • Allocations: দ্রুত স্ক্রলিংয়ের সময় অস্থায়ী অবজেক্টে উচ্ছ্বাস খুঁজে দেখুন। এটা সাধারণত বারবার ফরম্যাটিং, নতুন অ্যাট্রিবিউটেড স্ট্রিং, বা প্রতি-রো মডেল পুনর্নির্মাণ নির্দেশ করে।
  • Core Animation: ড্রপড ফ্রেম এবং দীর্ঘ ফ্রেম টাইম কনফার্ম করুন। এটা রেন্ডারিং চাপকে ডাটা কাজ থেকে আলাদা করতে সাহায্য করে।

স্পাইক খুঁজে পেলে কল ট্রিতে ক্লিক করে জিজ্ঞাসা করুন: এটা স্ক্রিনে একবার ঘটছে, না প্রতিটি রো-তে, প্রতি স্ক্রল? দ্বিতীয়টি হলো যা মসৃণ স্ক্রল ভাঙে।

স্ক্রল ও পেজিনেশন ইভেন্ট জন্য সাইনপোস্ট যোগ করুন

অনেক অ্যাপ স্ক্রলিং চলাকালে অতিরিক্ত কাজ করে (ইমেজ লোড, পেজিনেশন, ফিল্টারিং)। সাইনপোস্ট টাইমলাইন-এ সেই মুহূর্তগুলো দেখতে সাহায্য করে।

import os
let log = OSLog(subsystem: "com.yourapp", category: "list")
os_signpost(.begin, log: log, name: "LoadMore")
// fetch next page
os_signpost(.end, log: log, name: "LoadMore")

প্রতিটি পরিবর্তনের পরে পুনঃপরীক্ষা করুন, এক এক করে পরিবর্তন করুন। যদি FPS ভাল হয় কিন্তু Allocations খারাপ হয়, আপনি সম্ভবত স্টাটার কমিয়েছেন কিন্তু মেমরি চাপ বাড়িয়েছেন। বেসলাইন নোট রাখুন এবং মাত্র সেই পরিবর্তনগুলো রাখুন যা সংখ্যা উন্নত করে।

সাধারণ ভুলগুলো যা তালিকা পারফরম্যান্সকে নীরবভাবে ক্ষতি করে

Keep row work off the UI
Move formatting and business rules into backend logic so rows stay lightweight on-device.
Build App

কিছু সমস্যা স্পষ্ট (বড় ইমেজ, বিশাল ডেটাসেট)। অন্যগুলো কেবল তখনই দেখা দেয় যখন ডেটা বাড়ে, বিশেষ করে পুরোনো ডিভাইসে।

1) অস্থিতিশীল রো আইডি

ক্লাসিক ভুল হল ভিউর ভিতরে আইডি তৈরি করা, যেমন id: \.self রেফারেন্স টাইপের জন্য, বা UUID() রো বডিতে। SwiftUI আপডেট ডিফ করার জন্য আইডেন্টিটি ব্যবহার করে। যদি আইডি বদলে যায়, SwiftUI রো-টাকে নতুন মনে করে, পুনর্নির্মাণ করে, এবং ক্যাশ করা লেআউট ফেলে দিতে পারে।

আপনার মডেল থেকে স্থিতিশীল আইডি (ডাটাবেস প্রাইমারি কী, সার্ভার আইডি, বা আইটেম তৈরি সময় সংরক্ষিত UUID) ব্যবহার করুন। না থাকলে একটি যোগ করুন।

2) onAppear-এর ভিতরে ভারি কাজ

onAppear প্রত্যাশা থেকে বেশি ঘন ঘন চলে কারণ স্ক্রলিং চলাকালে রো আসে এবং যায়। যদি প্রতিটি রো সেখানে ইমেজ ডিকোডিং, JSON পার্সিং, বা ডাটাবেস লুকআপ শুরু করে, আপনি পুনরাবৃত্তি স্পাইক পাবেন।

ভারি কাজ রো থেকে সরান। ডেটা লোডের সময় প্রিকম্পিউট করুন, ফলাফল ক্যাশ করুন, এবং onAppear-কে পেজিনেশন ট্রিগারের মতো সস্তা কাজেই সীমাবদ্ধ রাখুন।

3) পুরো তালিকাকে বাইন্ড করে রো এডিট করা

যখন প্রতিটি রো একটি বড় অ্যারেতে @Binding পায়, একটি ছোট এডিটও বড় পরিবর্তন মনে হতে পারে। এতে অনেক রো পুনরায় মূল্যায়ন হয়, এবং কখনও কখনও পুরো তালিকা রিফ্রেশ হয়।

রো-এ অপরিবর্তনীয় মান পাস করা এবং একটি হালকা অ্যাকশন দিয়ে পরিবর্তন ফিরে পাঠানো (উদাহরণ: "id-এর জন্য favorite টগল করুন") পছন্দ করুন। প্রতি-রো স্টেট শুধু তখনই রো-র ভিতরে রাখুন যখন সত্যিই সেটি সেখানে থাকা উচিত।

4) স্ক্রলিং চলাকালে বেশি অ্যানিমেশন

অ্যানিমেশন তালিকায় ব্যয়বহুল কারণ এগুলি অতিরিক্ত লেআউট পাস ট্রিগার করতে পারে। পুরো তালিকার উপর উচ্চ-স্তরে animation(.default, value:) প্রয়োগ করা বা প্রতিটি ছোট স্টেট পরিবর্তন অ্যানিমেট করা স্ক্রলিংকে স্টিকি করে তোলে।

সহজ রাখুন:

  • অ্যানিমেশন সীমাবদ্ধ রাখুন শুধু যে রো বদলায়
  • দ্রুত স্ক্রলিং চলাকালে অ্যানিমেশন এড়িয়ে চলুন (বিশেষ করে সিলেকশন/হাইলাইটের জন্য)
  • ঘনঘন পরিবর্তিত মানগুলোর উপর ইমপ্লিসিট অ্যানিমেশন এড়িয়ে চলুন
  • জটিল সম্মিলিত এফেক্টের চাইতে সরল ট্রানজিশন পছন্দ করুন

বাস্তব উদাহরণ: একটি চ্যাট-স্টাইল তালিকা যেখানে প্রতিটি রো onAppear-এ নেটওয়ার্ক ফেচ শুরু করে, UUID() id-এর জন্য ব্যবহার করে, এবং “seen” স্ট্যাটাস পরিবর্তনের সময় অ্যানিমেশন করে। সেই সমন্বয় ক্রমাগত রো চর্ণ সৃষ্টি করে। আইডেন্টিটি ঠিক করা, কাজ ক্যাশ করা, এবং অ্যানিমেশন সীমাবদ্ধ করা প্রায়ই একই UI-কে তাত্ক্ষণিকভাবে মসৃণ করে।

দ্রুত চেকলিস্ট, একটি সরল উদাহরণ, এবং পরবর্তী ধাপ

আপনি কেবল কিছু কাজই করতে চান, তাহলে এখান থেকেই শুরু করুন:

  • প্রতিটি রো-র জন্য স্থিতিশীল, ইউনিক id ব্যবহার করুন (না অ্যারে ইনডেক্স, না নতুন UUID)
  • রো কাজ ছোট রাখুন: ভারী ফরম্যাটিং, বড় ভিউ ট্রি, এবং ব্যয়বহুল কম্পিউটেড প্রপার্টি body-তে এড়িয়ে চলুন
  • প্রকাশ নিয়ন্ত্রণ করুন: দ্রুত পরিবর্তিত স্টেট (টাইমার, টাইপিং, নেটওয়ার্ক প্রোগ্রেস) পুরো তালিকাকে অব্যবহার্য করতে দেবেন না
  • পেজিং এবং প্রিফেচ করুন যাতে মেমরি সমতল থাকে
  • অনুমান না করে Instruments দিয়ে আগে ও পরে মাপুন

ধরা যাক একটি সাপোর্ট ইনবক্স যেখানে 20,000 কনভার্সেশন আছে। প্রতিটি রো-তে বিষয়, শেষ মেসেজ প্রিভিউ, টাইমস্ট্যাম্প, আনরিড ব্যাজ, এবং একটি অ্যাভাটার দেখান। ব্যবহারকারীরা সার্চ করেন, এবং নতুন মেসেজ স্ক্রল করার সময় আসে। ধীর ভার্সনে সাধারণত কিছু একই সময়ে ঘটে: তারা প্রতিটি কীস্ট্রোকে রি-বিল্ড করে, টেক্সট অতিরিক্ত মাপ করে, এবং অনেক ইমেজ খুব আগেই ফেচ করে।

প্র্যাকটিক্যাল প্ল্যান যা কোডবেস পুরোপুরি টিয়ার না করেই কাজ করে:

  • বেসলাইন: Instruments-এ ছোট একটি স্ক্রল ও সার্চ সেশন রেকর্ড করুন (Time Profiler + Core Animation)।
  • আইডেন্টিটি ঠিক করুন: নিশ্চিত করুন আপনার মডেলের একটি বাস্তব id আছে সার্ভার/ডাটাবেস থেকে, এবং ForEach সেটি ধারাবাহিকভাবে ব্যবহার করে।
  • পেজিং যোগ করুন: প্রথমে নতুন 50–100 আইটেম নিয়ে শুরু করুন, তারপর ব্যবহারকারী শেষের কাছাকাছি এলে আরও লোড করুন।
  • ইমেজ অপ্টিমাইজ করুন: ছোট থাম্বনেইল ব্যবহার করুন, ফলাফল ক্যাশ করুন, এবং মেইন থ্রেডে ডিকোড না করুন।
  • পুনঃমাপ করুন: কম লেআউট পাস, কম ভিউ আপডেট, এবং পুরোনো ডিভাইসে স্থির ফ্রেম টাইম কনফার্ম করুন।

আপনি যদি একটি পূর্ণ প্রোডাক্ট নির্মাণ করেন (iOS অ্যাপ প্লাস ব্যাকএন্ড ও ওয়েব অ্যাডমিন প্যানেল), তাহলে ডেটা মডেল ও পেজিং চুক্তি শুরু থেকেই ডিজাইন করা সাহায্য করে। Platforms like AppMaster (appmaster.io) এই ফুল-স্ট্যাক ওয়ার্কফ্লোর জন্য তৈরি: আপনি ভিজ্যুয়ালি ডেটা ও বিজনেস লজিক সংজ্ঞায়িত করতে পারেন, এবং বাস্তব সোর্স কোড জেনারেট বা সেলফ-হোস্ট করতে পারবেন।

প্রশ্নোত্তর

What’s the fastest fix when my SwiftUI list scrolls with stutters?

প্রথমে রো-আইডেন্টিটি ঠিক করুন। আপনার মডেলের থেকে একটি স্থিতিশীল id ব্যবহার করুন এবং ভিউতে আইডি জেনারেট করা থেকে বিরত থাকুন — কারণ আইডি বদলালে SwiftUI প্রতিটি রোকে নতুন হিসেবে ধরা শুরু করে এবং প্রয়োজনের চেয়ে বেশি রি-রেন্ডার করে।

Is SwiftUI slow because it “re-renders” too much?

body পুনরায় গণনা সাধারণত সস্তা; ব্যয়বহুল অংশ হল সেটা যে কাজগুলো ট্রিগার করে: জটিল লেআউট, টেক্সট মেজারমেন্ট, ইমেজ ডিকোডিং, অথবা অনির্ধারিত আইডেন্টিটির কারণে অনেক রো পুনর্নির্মাণ।

How do I choose a stable `id` for `ForEach` and `List`?

ভিউতে UUID() ব্যবহার করবেন না বা অ্যারে ইনডেক্সে নির্ভর করবেন না যদি ডেটা ইনসার্ট/ডিলিট/রিওর্ডার হতে পারে। সার্ভার/ডাটাবেস আইডি বা তৈরি হওয়ার সময় মডেলে সংরক্ষিত UUID ব্যবহার করুন যাতে আইডি আপডেটে অপরিবর্তিত থাকে।

Can `id: \.self` make list performance worse?

id: \.self পারফরম্যান্স খারাপ করতে পারে, বিশেষ করে যদি যে মানটি হ্যাশ করে তা সম্পাদনযোগ্য ফিল্ড বদলে যায়; SwiftUI তখন সেটিকে ভিন্ন রো মনে করতে পারে। যদি আপনাকে Hashable ব্যবহার করতে হয়, তাহলে একক স্থিতিশীল আইডির উপর ভিত্তি করে করুন, যেমন সার্ভার আইডি।

What should I avoid doing inside a row’s `body`?

ধারণা করুন যে body-তে ধীর কোনো কাজ হচ্ছে — সেটাকে সেখানে বারবার চালাবেন না। তার পরিবর্তে ডেটা এলাকা পৌঁছানোর সময় প্রিফর্ম্যাট করুন, ভিউমডেলে ক্যাশ করুন, অথবা ছোট হেল্পারের মাধ্যমে মেমোইজ করুন। নতুন DateFormatter/NumberFormatter প্রতি রোতে তৈরি করা বা বড় .map/.filter inside body-তে চালানো এড়িয়ে চলুন।

Why is my `onAppear` firing so often in a long list?

onAppear স্ক্রলিং করলে রো বারবার দৃশ্যমান-অদৃশ্যমান হওয়ার কারণে বারবার ট্রিগার হয়। যদি প্রতিটি onAppear-এ ভারি কাজ (ইমেজ ডিকোড, ডাটাবেস পড়া, পার্সিং) শুরু করেন, তাহলে পুনরাবৃত্তি স্পাইক পাবেন। onAppear-কে হালকা রাখুন; পেজিনেশন ট্রিগার করা ভালো উদাহরণ।

What causes “update storms” that make scrolling feel sticky?

যে কোনো দ্রুত পরিবর্তনশীল প্রকাশিত মান (টাইলার, টাইপিং স্টেট, প্রোগ্রেস) যদি তালিকার সাথে শেয়ার করা থাকে, তা বারবার তালিকাকে অব্যবহার্য করে দিতে পারে। টাইমার, টাইপিং স্টেট ইত্যাদি বড় অবজেক্ট থেকে আলাদা রাখুন, সার্চ-কে ডিবাউন্স করুন, এবং দরকার হলে বড় ObservableObjectগুলো ছোট করে ভাগ করুন।

When should I use `List` vs `LazyVStack` for large datasets?

List ব্যবহার করুন যদি আপনার UI ক্লাসিক টেবিলের মতো (রো, সোয়াইপ অ্যাকশন, সিলেকশন, সেপারেটর)। কাস্টম লেআউট বা মিক্সড কন্টেন্ট হলে ScrollView + LazyVStack ভাল, কিন্তু মেমরি ও ফ্রেম ড্রপ মাপুন কারণ সেখানে যোগ করা অতিরিক্ত কাজ পুরোনো ডিভাইসে দ্রুত দেখা যায়।

What’s a simple pagination approach that stays smooth?

শেষ রো-তে পৌঁছলে লোড করা অনেক সময় দেরি হয়ে যায়। ব্যবহারকারী যখন শেষের কাছাকাছি কয়েকটি রো দেখতে পাওয়ার মধ্যে থেকেই পরবর্তী পেজ লোড শুরু করুন (যেমন শেষ 5 রোর মধ্যে)। isLoadingreachedEnd ব্যবহার করে ডেডুপ করুন এবং একই রেকর্ড বারবার যোগ হওয়া রোধ করুন।

How do I measure what’s actually slowing my SwiftUI list down?

রিয়েল ডিভাইসে একটি বেসলাইন নিয়ে Instruments চালান: দ্রুত উপরে-নিচ স্ক্রল, লোড-মোর একবার ট্রিগার করা ইত্যাদি। Time Profiler, Allocations এবং Core Animation মিলে কোন অংশ প্রধান থ্রেডে চাপ দিচ্ছে, কোথায় অস্থায়ী অবজেক্ট তৈরি হচ্ছে এবং ফ্রেম ড্রপ হচ্ছে তা দেখাবে।

শুরু করা সহজ
কিছু আশ্চর্যজনকতৈরি করুন

বিনামূল্যের পরিকল্পনা সহ অ্যাপমাস্টারের সাথে পরীক্ষা করুন।
আপনি যখন প্রস্তুত হবেন তখন আপনি সঠিক সদস্যতা বেছে নিতে পারেন৷

এবার শুরু করা যাক