<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های Amir Abbas Mousavian</title>
        <link>https://virgool.io/feed/@a.mosavian</link>
        <description></description>
        <language>fa</language>
        <pubDate>2026-06-19 08:34:46</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/5887/avatar/avatar.png?height=120&amp;width=120</url>
            <title>Amir Abbas Mousavian</title>
            <link>https://virgool.io/@a.mosavian</link>
        </image>

                    <item>
                <title>کار با متغیرها در iOS از چند رشته (Multi-thread) - بخش ۲</title>
                <link>https://virgool.io/nstehran/multi-thread-apple-part2-m218nc8vi1c1</link>
                <description>خب توی بخش اول از نظر تئوری، مشکل کار با متغیرهای mutable از چند رشته را دیدیم و فهمیدیم چه جوری پیداشون کنیم. حالا که مشکل را میدونیم باید ببینیم چه جوری میشه حلش کرد.توی ابجکتیو-سی کار تقریبا راحته. کافیه پراپرتی از نوع اتمیک تعریف شه. یا متغیر @synchronize باشه. اما توی سوییفت کار یه کم سخت‌تره چون اینها را نداریم.خب بیاید فکر کنیم ببینیم چه راهی میتونیم پیاده کنیم. گفتیم علت این موضوع اینه که ار چند رشته همزمان میخوایم به یه متغیر یا منظقه‌ای از حافظه دسترسی داشته باشیم. خب میایم صورت مسئله را دستکاری می‌کنیم. یعنی فقط از یه رشته دسترسی داشته باشیم. مثلا متغیر را اینجوری تعریف کنیم:class Object {
    private let _array_q = DispatchQueue(label: &quot;Object.property.atomic&quot;)
    private var _array: [Int] = []
    
    public var array: [Int] {
        get {
            return _array_q.sync { return _array }
        }
        set {
            _array_q.async { self._array = newValue }
        }
    }
}خب یه DispatchQueue ایجاد کردیم. توجه کنید DispatchQueue به طور پیشفرض سریال هست و تا یه بلوک کد اجراش کامل نشه بلوک بعدی را اجرا نمی‌کنه. یه متغیر stored هم داریم که مقدار را توی حافظه نگه می‌داره اما private تعریف کردیم که کسی بهش دسترسی نداشته باشه. تغییر این متغیر صرفا از طریق متغیر محاسباتی array امکان پذیر هست اما اینجا صرفا از طریق کیویی که تعریف کردیم دسترسی داریم.توجه کنید نه فقط برای set که حتی برای get هم داریم از این کیو استفاده می‌کنیم. ممکنه خیال کنید اگر فقط set از طریق کیو باشه کافیه ولی اینجوری نیست و همه‌ی دسترسی‌ها صرفا باید از طریق یک کیوی خاص باشه.خب مشکل را حل کردیم. (میتونید با TSan تست کنید!) اما چند تا مشکل جدید خلق کردیم!۱- شاید برای چند تا متغیر محدود مناسب باشه ولی وقتی تعداد زیاد بشه، تعداد زیادی ترد بی‌مصرف و منتظر داریم که خب به معنی مصرف منابع کرنل هست.۲- ساخت ترد کار پرهزینه‌ای هست. در کل این روش روش کندی هست. نسبت به روش دیگه‌ای که معرفی می‌کنیم ۱۵ برابر کندتر هست.من وقتی این موضوع را فهمیدم شروع کردم به فکر و سرچ بیشتر. سمافور راه خوبی به نظر می‌رسه. سریع هم هست! اما یه مشکل عمده داره. به راحتی باعث deadlock میشه و اگر یکی از تردهای درگیر این deadlock ترد اصلی باشه، برنامه هنگ می‌کنه. خب پس باید بیشتر بگردیم.با سرچ بیشتر رسیدم به pthread_mutex_t که جزوی از کتابخانه‌ی استاندارد C هست. مخصوص این کار هست. سریع هست. کار باهاش تقریبا ساده ست ولی مشکلش اینه که مدیریتش دستی هست. یعنی هروقت بهش نیازی ندارید خودتون باید destroy کنید. تصمیم گرفتم یه کلاس سوییفت بنویسم که این جزییات را برام انجام بده ولی خب متوجه شدم اپل قبلا این کار را کرده!کمی داکیومنت‌های فاندیشن را شخم بزنیم به کلاس NSLock می‌رسیم. این کلاس یه wrapper برای mutex هست و از همون استفاده میکنه. ولی کار باهاش سوییفتی تره. حالا اگر بخوایم کد بالا را بازنویسی کنیم اینجوری میشه.class Object {
     private let _array_lock = NSLock()
     private var _array: [Int] = []
     
     public var array: [Int] {
          get {
               _array_lock.lock()
               defer { _array_lock.unlock() }
               return _array
         }
         set {
               _array_lock.lock()
               defer { _array_lock.unlock() }
               return _array
          }
     }
}خب مشکل حل شد. توجه کنید عبارت توی defer بعد از return اجرا میشه. پس اول فقل می‌کنیم. بعد متغیر را دستکاری می‌کنیم یا مقدارش را میخونیم. و بعد قفل را باز می‌کنیم.اگر بخوایم می‌تونیم بین لاک و آنلاک هر عبارتی اجرا کنیم. مثلا append کنیم. اینجوری سریعتر از خوندن و نوشتن کل آرایه هست. بنابراین شاید لازم باشه متدی شبیه این هم داشته باشیم. تا امکان این را بده:withThreadSafeArray&lt;R&gt;(_ handler: (_ array: [Int]) throws -&gt; R) rethrows -&gt; R {
    _array_lock.lock()
    defer { _array_lock.unlock() }
    return try handler(self._array)
}استفاده‌ش هم ساده ست:let removedElement: Int = withThreadSafeArray { (array) in
    array.append(1)
    return array.remove(at: 0)
}اجرای lock توی یه ترد باعث میشه اگر یه ترد دیگه هم بخواد lock کنه، سیستم ترد دوم را منتظر نگه می‌داره تا unlock ترد اول اجرا بشه. از سمافور هم باهوشتره و اگر دو تا ترد لاک کنن و منتظر نتیجه‌ی همدیگه باشن،‌خودش میفهمه و به یکی از تردها اجازه‌ی اجرا میده تا مشکل ددلاک بوجود نیاد.توجه کنید اگر بدون unlock، مجددا توی همون ترد lock اجرا بشه، باعث ددلاک می‌شه. ولی خب برخلاف سمافور این موضوع دست خودمون هست و نشون دهنده‌ی اینه که کد ایراد داره و باید درست شه.منتهی اگر واقعا منطق کد جوری هست که نیاز به لاک‌های تو در تو توی یه ترد داریم، از NSRecursiveLock استفاده می‌کنیم. در این حالت ترد دوم اینقدر منتظر می‌مونه تا آخرین unlock ترد اول اجرا بشه. تا جای ممکن نباید از ریکرسیو لاک استفاده کرد چون باعث پوشش اشتباهات ما میشه و خودش رو نشون نمی‌ده.ممکنه این لاک ریکرسیو مشهود نباشه. مثلا توی مثال بالا توی withThreadSafeArray کافیه بخوایم متغیر محاسباتی array رو بگیریم یا ست کنیم!جمع بندیبرخلاف اسمش، پیاده‌سازی atomicity برای متغیر کار سختی نیست. فقط باید از موضوع درک داشته باشیم و بدونیم کجا و چه جوری ازش استفاده کنیم. دقت کنید برای کلاس‌هایی که صراحتا ترد سیف هستن، مثل NSCache، نیازی به این کار نیست و خود اپل برای شما این کار را انجام داده.اگر خواستید، می‌تونید یه کلاس جنریک (نه استراکت!) بنویسید که متغیری به صورت اتمیک در اختیارتون بذاره تا لازم نباشه این کد boilerplate رو برای هر متغیر تکرار کنید.راه‌های دیگه‌ای هم هست. مثل استفاده از objc_sync_enter/objc_sync_exit اگر آبجکت ما مشتق از NSObject باشه. یا استفاده از اسپین لاک (spinlock) که برای پرفورمنس بالاتر روی سی‌پی‌یوهای چندهسته‌ای مناسبتره. ولی اگر مطمئن نیستید از همین NSLock استفاده کنید. اپل انواع کلاس‌های لاک دیگه برای شرایط شما گذاشته که می‌تونید داکیومنت اپل رو مطالعه کنید.موفق باشید</description>
                <category>Amir Abbas Mousavian</category>
                <author>Amir Abbas Mousavian</author>
                <pubDate>Sat, 30 Jun 2018 00:42:44 +0430</pubDate>
            </item>
                    <item>
                <title>کار با متغیرها در iOS از چند رشته (Multi-thread) - بخش ۱</title>
                <link>https://virgool.io/nstehran/multi-thread-apple-part1-cmq1ep9aw26l</link>
                <description>در زمان‌های قدیم برنامه‌ها ساده بود، در هر زمان تنها یه برنامه اجرا می‌شد و به کل حافظه دسترسی داشت. به ندرت برنامه‌ای یافت میشد که بیش از یک رشته داشت و همین باعث سادگی برنامه‌نویسی می‌شد.امروزه به ندرت میشه اپی نوشت که چند رشته‌ای نباشه. کافیه برنامه بخواد چیزی را از شبکه دریافت کنه یا یه فایل حجیم را از روی دیسک بخونه. اگر بخوایم ترد اصلی را منتظر تکمیل بذاریم صفحه‌ی برنامه برای چند ثانیه هنگ میکنه. چیزی که برای برنامه‌های جدید و با وجود GUI قابل تحمل نیست.یه برنامه‌ی خوب، به نحو احسن از قابلیت چند رشته‌ای استفاده می‌کنه. روی پلتفرم اپل، فریمورک Dispatch خلق و کار با سیستم چند رشته‌ای را بسیار ساده می‌کنه. ولی یه مشکل بزرگ به وجود میاد، کار با حافظه‌ی قابل تغییر و به طور دقیقتر، متغیرهای mutable! (در سوییفت، var ها)با ظهور سی‌پی‌یو های چند هسته‌ای این احتمال زیاده که چند رشته به طور همزمان اجرا شن. و اگر یکی از این رشته‌ها در حال نوشتن روی قسمتی از حافظه باشه و رشته‌ی دومی قصد خوندن یا نوشتن همون قسمت از حافظه را داشته باشه، مشکلی خلق میشه که در کامپیوتر بهش میگن Race condition.تعریف Race یعنی مسابقه‌ی اسب سواری و condition یعنی حالت. بعبارتی یعنی حالتی که دو تا رشته قصد دارن به صورت همزمان به خط پایان یا همون حافظه‌ی متغیر برسن. این که چه کسی برنده می‌شه قابل پیش‌بینی نیست. در حالی که عموما انتظار داریم اولی برنده بشه، ولی ممکنه دومی برنده بشه و تمام معادلات را بهم بزنه! خود Race condition گاهی اینقدر جدی هست که می‌تونه منجر به حفره‌های امنیتی بشه. برای همین اپل یه قسمتی از داکیومنت‌های امنیتی‌ش را به این موضوع اختصاص داده.(البته برای فایل روی دیسک هست. و ربطی به بحث ما نداره)پیدا کردن Race condition هم معضل بزرگی هست (البته نه با روشی که خواهم گفت). ممکنه اسبی که  روش شرط بستیم هزار بار برنده شه و اون یکی فقط یکبار، یا اینکه برنده‌ای نباشه! بنابراین توی تست‌ها چیزی معلوم نمیشه. ولی برای کاربر نهایی ممکنه باعث کرش بشه و کاربر را شاکی کنه. (در واقع اگر کرش بکنه، شانس آوردید که برنامه در حالت ناپایدار ادامه پیدا نکرده!)برای اینکه بفهمید چقدر پیش‌بینی ناپذیر هست، کد زیر را روی playground سوییفت چند دفعه اجرا کنید و ببینید که گاهی بعد تعداد نامعلومی کرش میکنه و گاهی هم بدون مشکل اجرا می‌شه!var a: [Int] = []
let iterations = 2000
DispatchQueue.concurrentPerform(iterations: iterations) { index in
    a.append(index)
}این مشکل باعث شده که برخی زبان‌های برنامه‌نویسی مثل Lisp یا Haskell متغیرهای mutable را تقریبا بذارن کنار. ولی سوییفت بعنوان یه زبان انعطاف‌پذیر، صرفا این را توصیه می‌کنه.اما گاهی چاره‌ای نیست. حالا قدم اول اینه که بفهمیم کدوم متغیرهای ما درگیر این موضوع هستن و باید روشون کار کنیم. به طور کلی هر متغیر mutable که از چند رشته دسترسی خواندن/نوشتن داره باید بررسی بشه. گاهی هم متدهای کلاس/استراکت ما که mutating هستن باعث ایجاد مشکل می‌شن.اینجا ۲ تا تعریف داریم. یکی Thread-safe هست، یعنی کلاسی که این امکان را می‌ده که از چند رشته همزمان متدهاش اجرا بشه و مشکلی پیش نیاد. فی الواقع خودش با روشهایی که بعدا میگم مشکل را حل می‌کنه. اکثر کلاس‌های پایه‌ای مثل Array/NSMutableArray و Dictionary/NSMutableDictionary و String/NSMutableString ترد-سیف نیستن و باید احتیاط کنید. ولی اگر در داکیومنت اپل نوشتن باشن که هست، یعنی نگران نباشید. کلاس‌های NSFileManager، NSURLSession و NSCache از جمله کلاسهای ترد-سیف هستن. در مورد کلاس UIImage هم احتیاطاتی هست.تعریف دیگه Atomic هست. یعنی متغیری که خودش دسترسی به حافظه را به ترتیب انجام میده تا خراب نشه. اینکه چه جوری چنین متغیری بسازیم، بعدا میگم.پیدا کردن مظنون‌هاکامپایلر Clang به طبعش Xcode امکانی دارن برای یافتن متغیرهایی که می‌تونن باعث ایجاد مشکل بشن و هنوز thread-safe یا atomic نیستن. اول دیوایس را یکی از انواع سیمولاتور (و نه دیوایس واقعی)  انتخاب کنید. از توی Xcode برید توی منوی Product -&gt; Scheme -&gt; Edit Scheme و صفحه‌ی زیر را بیاریدصفحه‌ی Edit Schemeحالا اون قسمتی که قرمز کردم را مارک بزنید.در واقع الان داریم از امکانی استفاده می‌کنیم به اسم Thread Sanitizer یا TSan که توسط گوگل توسعه داده شده و البته به لطف اپل روی سوییفت هم قابل استفاده ست. این گزینه فقط وقتی فعاله که روی سیمولاتور باشیم.حالا برنامه را اجرا می‌کنیم و به قسمت‌های مختلفش میریم. چون این مشکل/ارور از نوع ران-تایم هست، حتما باید کد قسمت‌هایی که به متغیر دسترسی پیدا می‌کنن حداقل یک مرتبه اجرا شه.اگر کد ما مشکل داشته باشه، اروری توی قسمت زیر ظاهر میشه. همچنین معلوم می‌کنه از کجا‌ها داره دسترسی همزمان به متغیر صورت می‌گیره.خب اگر خوب به ارور بالا دقت کنیم، هر کدوم از ارورها یه قسمت Read داره یه قسمت Write و یه قسمت Heap allocation. قسمت Read میگه از کدوم ترد در حال خوندن مقدار متغیر هستیم، قسمت Write هم که معلومه. قسمت allocation هم میگه توی کدوم ترد شی را ایجاد کردیم.همونطور که میبینید ترد ۱۰ داره میخونه ولی ترد ۵ داره مینویسه. زیر هر کدوم یه call stack داریم که میگه دقیقا از کجا به متغیر بی محابا دسترسی پیدا می‌کنیم و اگر روشون بزنید می‌پره روی خط کد. البته برای ما صرفا مهم هست که کدوم متغیر این مشکل را داره.ایشالله در مقاله بعدی توضیح می‌دم چه جوری باید این متغیر مشکل‌دار، اتمیک بشه و این معضل را حل کنیم.</description>
                <category>Amir Abbas Mousavian</category>
                <author>Amir Abbas Mousavian</author>
                <pubDate>Wed, 20 Jun 2018 14:47:12 +0430</pubDate>
            </item>
                    <item>
                <title>نوشتن اپ iOS/mac را از کجا شروع کنم؟ و چگونه مدل خوب بنویسم؟</title>
                <link>https://virgool.io/nstehran/where-to-start-to-develop-an-app-dr2rv7f6lf1i</link>
                <description>وقتی پروژه ای را شروع می‌کنیم، اولین سوالی که به ذهن می‌رسه اینه که از کجا باید شروع کنم. یه برنامه‌نویس عادی احتمالا پیاده‌سازی مدل، ویو و کنترلر را با هم شروع می‌کنه. تا بتونه همزمان پیشرفت کار را ببینه. این روش مزایای خودش را داره بخصوص وقتی قراره کار را به یه مدیر پروژه‌ی بی‌سواد پرزنت کنیم. اما میزان دوباره کاری و اتلاف وقت و انرزی توش زیاده، و طبیعتا هزینه‌ی تولید بالاتر میره.به نظر من بهتره اول مدل را به طور کامل پیاده سازی کنیم. برای پیاده سازی ویوکنترلر حتما باید یه مدل (که با دیتای مشقی پر می‌شه) داشته باشیم تا قابل نمایش باشه. در صورتی که برای پیاده سازی مدل نیازی به ویوکنترلر نیست. در واقع حتی نیاز نیست صفحه‌ی اول اپ چیزی داشته باشه! کافیه یه تست مناسب برای قسمتهای مختلفش بنویسیم تا مطمئن شیم درست کار می‌کنه. تازه داشتن test case های جامع بخصوص برای قسمت مدل، هزینه نگهداری کد را در آینده به شدت کاهش میده و اطمینان میده که برنامه همیشه درست کار خواهد کرد.علاوه بر اینها، طراحی مدل می‌تونه تمام ریلیشن شیپ های بین ویوهای مختلفتون را مشخص کنه. اینجوری حتی می‌تونیم بفهمیم کدوم ویوها/ویوکنترلرها تکرار می‌شن و می‌تونید با جداسازیش، با یکبار پیاده سازی همه جا استفاده ش کنیم.برای پیاده سازی مدل چهار مرحله‌ی اجباری هست و یه مرحله‌ی اختیاری. من مراحل را برای پلتفرم iOS/macOS توضیح میدم ولی برای سیستم‌های دیگه هم رویه مشابهی ولی با ابزارهای متفاوت برقرار هست.مرحله‌ی نخست: تعریف ساختاراز نظر فنی این مرحله تعریف memory representation داده‌ها ست.این قسمت از نظر پیاده سازی بسیار ساده ست. کافیه تعداد کلاس/استراکت تعریف کنیم که یه سری متغیر دارن! ولی خب طراحیش پیچیده ست. اینکه چه کلاسهایی باید تعریف شه مستلزم اینه که یه پلن تقریبا کامل از برنامه داشته باشیم. بدونیم چه صفحاتی داره و چه اطلاعاتی قراره نگهداری کنه. اگر قبلا بخش بک‌اند/سرور را داشته باشیم یا قبلا اپ را روی پلتفرم دیگه‌ای مثل اندرویید و وب پیاده کرده باشیم این مرحله قبلا انجام شده. کافیه همون json را معادل کنیم. حتی ابزارهایی مثل quicktype می‌تونه این قسمت را خیلی سریع کنه.توی همین مرجله ریلیشن بین قسمتها و ویو/ویوکنترلر ها هم در میاد. از اونجا که توی پیاده سازی خوب هر ویوکنترلر دقیقا باید نماینده‌ی یه موجود توی مدل باشه، هر متغیری که توی یه کلاس داشته باشیم که تایپش یه کلاس تعریف شده‌ی دیگه باشه یعنی ویوکنترلر مربوط به متغیر، فرزند ویوکنترلر دیگه می‌شه.این مرحله فی الواقع abstraction ساختار داده‌ی ما را امکان پذیر می‌کنه.نکته‌ی جانبی: اگر هنوز بلد نیستید که چندین ویوکنترلر را توی یه صفحه یا ویوکنترلر مادر نمایش بدید، این ویدیوی آموزشی از فرزاد نظیفی را در مورد MVC 2.0 مشاهده کنید. توصیه‌ی من اینه که حتی سلول‌های کالکشن ویو اگر چیزی بیش از یه تصویر دارن، یه ویوکنترلر باشن.مرحله‌ی دوم: سریالیزیشن (Serialization)سریالیزیشن یعنی عملی که داده‌ی توی مموری یا رم را تبدیل به داده‌ی قابل ذخیره یا انتقال بکنه. بعبارتی هدف نهایی ما این هست که بتونیم استراکت یا کلاس خودمون را تبدیل به فرمتهای استانداردی مثل json یا فرمت‌های اختصاصی بکنیم تا به بک اند انتقال بدیم یا روی دیسک ذخیره کنیم. بستگی به اینکه می‌خوایم تحت اینترنت باشیم یا داده را روی دیسک ذخیره کنیم، اپروچ به این قسمت ممکنه فرق کنه.برای انتقال روی وب، قبلا طراحی این قسمت کار آسونی نبود چون برای فرستادن باید همه‌ی متغیرها را به یه دیکشنری منتقل می‌کردیم و بعد با کلاس NSJSONSerialization اون را تبدیل به json می‌کردیم. برای خوندن هم باید با همین کلاس، فایل را تبدیل به دیکشنری می‌کردیم و متغیرهای کلاس را دونه دونه مقدار دهی می‌کردیم.اگر هنوز از objective c استفاده می‌کنید هنوز راه حل به همین سختی هست. ولی توی سوییفت با استفاده از پروتکل‌های Encodable و Decodable (یا به صورت کلی Codable) این قسمت خیلی راحت شده. کافیه کلاس یا استراکت را از نوع Codable تعریف کنیم تا متدهای مربوط به تبدیل و سریالیزیشن خودشون ایجاد بشن.البته هنوز اگر نیاز به اعتبارسنجی json دریافتی داریم مجبوریم دستی پیاده‌سازی کنیم.گفتم اعتبارسنجی. این قسمت خیلی مهمی هست که معمولا توی این مرحله گم می‌شه. همین نکات کوچیک فرق یه برنامه‌ی معمولی و یه برنامه‌ی حرفه‌ای هست. مثلا فرض کنید یه فیلد تاریخ داریم که انتظار داریم از تاریخ عرضه‌ی برنامه عقب‌تر نباشه. یا تاریخ تغییر قطعا باید بعد از تاریخ ساخت یه مورد باشه. خب همین جا چک کنیم و اگر منطبق نبود، از ادامه‌ی خوندن فایل خودداری کنیم و یه پیغام خطای مناسب برای خراب بودن داده را نمایش بدیم. این قسمت باید توی متد decode پیاده سازی بشه و اگر اطلاعات دریافتی معتبر نبود، یه ارور مناسب throw بشه.اما اگر می‌خوایم داده را روی دیسک یا جایی مثل NSUserDefaults ذخیره کنیم، بهتره کلاسمون را منطبق با پروتکل NSCoding کنیم و از NSKeyedArchiver و NSKeyedUnarchiver برای ذخیره و بازیابی استفاده کنیم. این سیستم تا حدود زیادی تضمین می‌کنه که اطلاعات را روی کلاس مناسب و مختص این نوع داده منتقل کنیم.مرحله‌ی سوم: انتقال و ذخیره سازی یا Persistencyمرحله‌ی قبلی انجام شد تا بتونیم این مرحله را پیاده سازی کنیم. حالا فایل json یا دیتایی داریم که می‌تونیم ذخیره کنیم یا بفرستیم. یا اینکه فایل json را از سرور بگیریم. برای این مرحله باید روی شبکه مسلط باشیم ولی خب توی iOS یه سیستم NSURLSession هست که کار باهاش خیلی راحته و امکانات خیلی خوبی هم داره. فقط کافیه یه ریکوئست مناسب بسازیم و بفرستیم به سرور تا دیتای خام دریافتی را در اختیارمون بذاره. حالا همین داده را در اختیار متدهای مرحله ۲ میذاریم.یه سری third-party library مثل Alamofire هست که در عمل یه wrapper روی همین URLSession هست. اگر با اونها راحتترید یا روشون مسلط هستید خب از اونا استفاده کنید ولی اگر تازه کارید حتما تمرکزتون را روی یادگیری URLSession بذارید.یه مزیت عمده‌ی URLSession نسبت به Alamorfire روی کانفیگ بکگراند هست. این امکان وقتی به درد میخوره که برنامه حالت آفلاین هم داره. از اونجا که دانلودها و آپلودهای سیشن بکگراند مستقل از برنامه ست،‌ می‌تونیم زمانبندی کنیم که در تاریخ خاصی ارسال بشن (البته iOS 11.0). یا اینکه فرض کنید می‌خواید هروقت اینترنت وصل شد، دیتای سابمیت شده توسط کاربر فرستاده بشه (کاری که اپلیکیشنهای ایمیل می‌کنن!) خب اینجا کانفیگ بک‌گراند کار را راه میندازه. Task یی که روی سیشن بکگراند ایجاد می‌کنیم تا ۷ روز منتظر وصل شدن اینترنت میمونه و موقع اتصال حتی اگر برنامه در حال اجرا نباشه، داده را آپلود/دانلود می‌کنه. البته کار با سیشن بکگراند نکاتی داره، مثلا نمیتونیم از کلوژر/بلاک استفاده کنیم (خطای runtime میده) و دیتای دریافتی را حتما باید با دلگیت هندل کنیم. همچنین برای آپلود حتما باید فایل روی دیسک باشه.مرحله‌ی چهارم: پیاده سازی Cacheاین مرحله اختیاری هست. ممکنه برنامه‌ واقعا بهش احتیاجی نداشته باشه. ولی این مرحله دقیقا فرق یه برنامه‌ی متوسط و یه برنامه‌ی خوب هست. کاربر انتظار نداره عکس یا داده‌ای که قبلا دانلود شده مجدد دانلود بشه! و انتظار حداکثر سرعت از برنامه را داره. پیاده ساری صحیح این قسمت خیلی مهم هست چون پیاده سازی نادرست می‌تونه مقدار زیادی از رم را مصرف کنه که توی iOS منجر به کرش برنامه می‌شه! به همین سادگی ممکنه کاربر شاکی بشه!خود پلتفرم سه تا کلاس مناسب در اختیار ما میذاره. در ۹۰ درصد موارد همینها کافیه. ممکنه از کلاسهای third-party هم استفاده کنید براش که کارتون راحتتر بشه.اولین کلاسی که باید بلد باشیم NSCache هست. این کلاس عین Dictionary هست منتهی با سه تا مزیت برای کار ما:۱- برخلاف دیکشنری کاملا thread-safe هست. از اونجا که لود داده و آپدیت روی تردهای پس‌زمینه انجام می‌شه این موضوع خیلی مهم هست. عدم توجه به این بخش باعث می‌شه برنامه شما از هر صد دفعه، یه دفعه کرش کنه که خوب نیست۲- مدیریت حافظه روش خیلی بهینه هست. برخلاف دیکشنری عادی، خالی کردنش مستلزم دسترسی به مقادیر ذخیره شده نیست. علاوه بر این خود سیستم توی شرایط تحت فشار خودش کش را پاک می‌کنه.۳- بهش می‌شه حد بالا داد که برای کش، تا مقدار زیادی از رم مصرف نشه و سهمیه بندی بکنیم.البته این کلاس برخلاف دیکشنری سوییفت، فقط با انواع سازگار با objective-c یعنی کلاس‌های مشتق از NSObject کار می‌کنه. (مثل NSString یا NSURL یا UIImage)این کلاس داده‌ها را روی رم نگهداری می‌کنه و نیاز به دسترسی به دیسک را کاهش میده. گزینه‌ی ایده‌آلی برای نگهداری thumbnailها و لیست ها هست.کلاس دوم NSURLCache هست که می‌تونه response های برگشته از سرور را ذخیره کنه. در واقع قبل از درخواست جدید به سرور می‌تونیم چک کنیم که آیا این مورد را قبلا گرفتیم یا نه. محدودیت‌های روی دیسک و رم هم قابل تنظیم هست. دقت کنید که برای کانفیک URLSession می‌تونیم urlCache تعریف کنیم تا کار تقریبا اتومات بشه. البته باید سرور شما هدر Cache-Control بفرسته تا این سیستم خیلی خوب کار کنه. ولی می‌تونید ریسپانسها را توش دستی ذخیره و دریافت کنید.کلاس سوم NSPurgableData هست که دقیقا مثل NSMutableData هست ولی این امکان را میده که در مواقع لازم خود سیستم تخلیه‌ش کنه. سعی کنید دیتاهایی که میتونید از سرور مجدد بگیرید توی این ذخیره کنید تا به رم فشار نیاد.نکته‌ی جانبی اینکه CGImage/UIImage خیلی حافطه می‌گیره. دقیقا ۴ بایت برای هر پیکسل یا بعبارتی ۸۰ مگ برای تصویر ۲۰ مگاپیکسلی! برای کوچیک کردن تصویر (یعنی درست کردن thumbnail)، بدون اینکه به رم فشار بیاد از این روتین استفاده کنید تا موقع پردازش تصویر peak مموری نداشته باشید. اگر تصویر خیلی بزرگ هست و نیاز به زوم نیست، با همین روش می‌تونید کوچکترش کنید (دقیقا اندازه‌ی ویوی مقصد) و بعد نمایش بدید.اگر برنامه امکان offline داشته باشه، باید مقداری زحمت بکشیم و قسمتهای تکمیلی را با استفاده از کلاس NSKeyedArchiver پیاده سازی کنیم. برای یادگیری ذخیره و بازیابی حالت برنامه اینجا را بخونید.مرحله پنجم: نوشتن test caseاین مرحله اکثرا به طور کامل نادیده گرفته می‌شه. به نظر من شاید برای قسمت UI بتونیم از خیر تست بگذریم اما برای قسمت مدل تنبلی موجب خسران هست. کوچکترین تغییری در سمت سرور ممکنه بدون اینکه متوجه بشیم قسمتی از اپ را از کار بندازه. ولی با وجود تست اطمینان کاملی به کارکرد قسمت مدل خواهیم داشت. تازه نوشتن تست کیس برای مدل خیلی ساده ست.نوشتن تست بسیار ساده ست. آموزشش را اینجا بخونید. ولی سوالی که مطرح می‌شه اینه که خب چه چیزایی باید تست بشه و چه چیزایی را بنویسیم. برای هر کدوم از ۴ مرحله‌ی بالا باید تست نوشته بشه. اگر این اصول رعایت شه، یه تست کیس خوب خواهیم داشت:۱- باید کلیه‌ی متغیرهای computed و توابع تست بشن که داده‌ی مناسبی را با توجه به متغیرهای ذخیره‌ای و ورودی‌هاشون برمی‌گردونن. و در مقابل ورودی غیرمتعارف چه واکنشی میدن.۲- باید مطمئن شیم قسمت سریالیزیشن بدون مشکل هست. کافیه یه json آماده داشته باشیم (یا از سرور بگیریم) و چک کنیم کلاسهای مربوطه ساخته میشن یا ارور میده. همچنین کلاسهای مقداردهی شده آیا json مورد انتظار ما را تولید می‌کنن یا خیر.۳- باید مطمئن شیم اعتبارسنجی یا validation مقادیر به درستی صورت می‌گیره. کافیه یه سری json مشکل‌دار داشته باشیم و سعی کنیم باهاشون کلاس را initialize کنیم. استرینگ بجای عدد، عدد بجای استرینگ یا تاریخهای در بازه‌ی غیرصحیح نمونه‌ی چیزایی هست که تست می‌شن.۴- می‌تونیم (انتخابی) مطمئن شیم ارتباط با سرور برقرار می‌شه. دیتا مناسب دریافت می‌شه و جواب مناسب برای دیتا درست می‌فرسته. همچنین می‌تونیم منطق authentication کاربر را همینجا تست کنیم.و درنهایت...وقتی مطمئن شدیم مدل درست و مطابق انتظار کار می‌کنه، بدون درگیری ذهنی و اتلاف وقت خیلی سریع بریم روی پیاده سازی ویوها و ویوکنترلها.نکته‌ی جانبی اینکه سعی کنید تا حد امکان طراحی شما ماژولار باشه تا اگر خواستید بتونید مدل خودتون را به‌صورت یه فریمورک جدا در بیارید.اگر یه تیم باشه، خیلی راحت می‌شه همزمان با پیاده سازی قسمت مدل، یه دیزاینر داشت تا نمای نهایی برنامه را طراحی کنه. اینجوری سرعت طراحی برنامه چند برابر می‌شه و کارفرما راضی‌تر. همزمان چیزی هم برای ارایه به مدیر پروژه وجود داره. برای این کار استفاده از اپلیکیشن Sketch توصیه می‌شه که می‌تونید منابع مورد نیاز برای طراحی اینترفیس iOS را از خود سایت اپل دریافت کنید.</description>
                <category>Amir Abbas Mousavian</category>
                <author>Amir Abbas Mousavian</author>
                <pubDate>Tue, 19 Jun 2018 14:57:59 +0430</pubDate>
            </item>
            </channel>
</rss>