طراحی سایت و برنامه نویسی

آموزش طراحی سایت و برنامه نویسی

طراحی سایت و برنامه نویسی

آموزش طراحی سایت و برنامه نویسی

نشت حافظه در اپلیکیشن‌ های اندروید — از صفر تا صد

 برنامه نویسی  55 بازدید

ساخت یک اپلیکیشن اندروید کار آسانی است، اما ایجاد یک اپلیکیشن اندروید با کیفیت کاملاً بالا که از نظر حافظه کارآمد باشد چنین نیست. برخی توسعه‌دهندگان علاقه زیادی به تمرکز و یافتن قابلیت‌ها، کارکردها و اجزای UI جدید در اپلیکیشن‌های خود دارند. برخی برنامه‌نویسان تمایل دارند روی چیزی کار کنند که تأثیر دیداری بیشتری داشته باشد تا این که زمان خود را صرف چیزهایی بکنند که اغلب افراد در نگاه نخست ممکن است حتی متوجه آن نشوند. اما این رویه خوبی نیست، شما باید این عادت اولویت‌بندی پایین‌تر موارد مرتبط با بهینه‌سازی اپلیکیشن را ترک کنید. یکی از مهم‌ترین گام‌های بهینه‌سازی اپلیکیشن نیز اصلاح موارد نشت حافظه است.

این وضعیت به صورت طبیعی منجر به تشکیل یک بدهی فنی می‌شود که در بلندمدت، شروع به تأثیرگذاری روی کیفیت اپلیکیشن‌ها می‌کند. شما باید خود را عادت بدهید که به تدریج ذهنیت فوق را که اشاره کردیم کنار بگذارید و بیشتر روی عملکرد اپلیکیشن‌های خود متمرکز شوید.

مفهوم نشت حافظه برای بسیاری از توسعه‌دهندگان آزاردهنده است، چون آن را دشوار، زمان‌بر، خسته‌کننده و غیرضروری می‌پندارند، اما خوشبختانه هیچ کدام از این موارد صحت ندارند. زمانی که شروع به بررسی آن بکنید، متوجه می‌شوید که موضوع کاملاً زیبا و جذابی است.

در این مقاله تلاش می‌کنیم این موضوع را تا حد امکان ساده بکنیم، به طوری که حتی توسعه‌دهندگان تازه‌کار نیز بتوانند از همان آغاز مسیر حرفه‌ای خود، شروع به ساخت اپلیکیشن‌های اندرویدی با کیفیت خوب و عملکرد بالا بکنند.

Garbage Collector دوست شما است؛ اما نه همیشه

جاوا زبان پایداری است. در اندروید کدی به زبان‌های C یا ++C نوشته نمی‌شود (البته به جز برخی موارد نادر). بدین ترتیب مجبور نیستیم که کل فرایند تخصیص حافظه و آزادی سازی آن را شخصاً مدیریت کنیم. خوشبختانه جاوا به خوبی از عهده انجام این امور برمی‌آید.

پس اینک نخستین سؤالی که به ذهن می‌آید این است که آیا جاوا یک سیستم مدیریت حافظه اختصاصی درونی دارد که می‌تواند به صورت خودکار حافظه را در صورت استفاده نشدن پاک کند؟ در این صورت چرا ما به عنوان توسعه‌دهنده باید در مورد این موضوع دغدغه داشته باشیم؟ آیا Garbage Collector مستعد خطا است؟

پاسخ سؤال فوق منفی است. Garbage Collector دقیقاً همان طور که طراحی شده است کار می‌کند، اما این اشتباه‌های برنامه‌نویسی خود ما است که برخی اوقات Garbage Collector را از گردآوری بخش‌های استفاده نشده حافظه بازمی‌دارد.

بنابراین باید گفت که اساساً این خطای ما است که منجر به بروز مشکل در حافظه می‌شود. Garbage Collector یکی از برترین دستاوردهای جاوا محسوب می‌شود و از این رو شایسته احترام است.

توضیحی در خصوص Garbage Collector

پیش از آن که در خصوص موضوع مقاله بیشتر توضیح بدهیم، باید اندکی در مورد طرز کار عملی Garbage Collector بدانیم. تئوری کارکرد Garbage Collector کاملاً ساده است، اما آن چه در پشت صحنه اتفاق می‌افتد در پاره‌ای موارد کاملاً پیچیده است. در هر حال جای نگرانی نیست، چون ما به طور عمده روی بخش‌های ساده آن تمرکز خواهیم داشت.

نشت حافظه

هر اپلیکیشن اندروید (یا جاوا) باید یک نقطه آغازین داشته باشد که اشیا از آنجا شروع به وهله‌سازی می‌کنند و متدها فراخوانی می‌شوند. بنابراین می‌توانیم این نقطه آغازین را به عنوان ریشه یا root درخت حافظه تصور کنیم. برخی اشیا مستقیماً یک ارجاع به root دارند و برخی دیگر از آن‌ها وهله‌سازی می‌شوند و ارجاع خود را به این اشیا نگه می‌دارند و همین طور ادامه می‌یابد.

از این رو یک زنجیره از ارجاعات شکل می‌گیرد که درخت حافظه را تشکیل می‌دهند. بنابراین Garbage Collector از ریشه‌های GC کار خود را آغاز می‌کند و اشیا را مستقیماً پیمایش می‌کند یا به صورت غیرمستقیم تا root می‌پیماید. در انتهای این فرایند، برخی اشیا وجود خواهند داشت که هرگز از سوی GC ملاقات نشده‌اند.

این موارد همان Garbage (یا اشیای مرده) هستند و همین‌ها هستند که باید از سوی Garbage Collector دوست‌داشتنی ما گردآوری شوند. تا به اینجا داستان شبیه به یک افسانه ساده کودکان بوده است، اما در ادامه کمی عمیق‌تر می‌شویم تا با جذابیت واقعی کارکرد Garbage Collector آشنا شویم.

نشت حافظه چیست؟

تا به اینجا ایده خلاصه‌ای از ماهیت Garbage Collector و طرز کار عملی مدیریت حافظه در اپلیکیشن‌های اندرویدی به دست آورده‌ایم. اکنون نوبت آن رسیده است که روی موضوع نشت حافظه با جزییات بیشتری تمرکز کنیم.

به بیان ساده نشت حافظه زمانی رخ می‌دهد که یک شیء را مدت‌ها پس از آن که هدف خود را برآورده ساخت نگهداری کنیم. مفهوم واقعی موضوع به همین سادگی است. هر شیء طول عمر خاص خود را دارد که در زمان پایان یافتن، باید از آن خداحافظی کرد و در نتیجه حافظه را ترک می‌کند. اما اگر برخی شیء (-های) دیگر به صورت مستقیم یا غیرمستقیم این شیء را نگه دارند، در این صورت Garbage Collector نمی‌تواند آن را گردآوری کند. این وضعیت همان نشت حافظه است.

اما خبر خوب این است که لازم نیست در مورد وقوع رخداد منفرد نشت حافظه در اپلیکیشن نگران باشید، چون همه نشت‌های حافظه باعث از کار افتادن برنامه شما نمی‌شوند.

برخی نشت‌ها هستند که کاملاً کوچک محسوب می‌شوند (نشت چند کیلوبایت حافظه)، برخی از نشت‌ها نیز در خود فریمورک اندروید رخ می‌دهند که شما نمی‌توانید و نباید آن‌ها را رفع کنید. این موارد عموماً تأثیر اندکی روی عملکرد اپلیکیشن شما دارند و می‌توان آن‌ها را به صورت امنی نادیده گرفت.

اما موارد دیگری هستند که می‌توانند اپلیکیشن را از کار بیندازند و آن را به مقدار زیادی کُند سازند به ترتیبی که به زانو درآید. این نشت‌ها آن‌هایی هستند که باید مراقبشان باشیم.

چرا باید نشت‌های حافظه را رفع کرد؟

هیچ کس نمی‌خواهد از اپلیکیشنی استفاده کند که کُند و دارای وقفه است و پس از چند دقیقه مقدار زیادی از حافظه را مصرف می‌کنند. این وضعیت می‌تواند تجربه واقعاً بدی برای کاربر ایجاد کند و اگر برای مدت زیادی طول بکشد، در این صورت احتمال بالایی وجود دارد که آن کاربر را برای همیشه از دست بدهید.

نشت حافظه

زمانی که کاربر به استفاده از اپلیکیشن شما ادامه می‌دهد، حافظه heap نیز شروع به افزایش می‌کند و اگر نشت حافظه در اپلیکیشن رخ بدهد، در این صورت حافظه استفاده نشده در heap نمی‌تواند از سوی GC رهاسازی شود. بنابراین حافظه heap در اپلیکیشن به صورت مداوم افزایش می‌یابد تا این که به نقطه مرگ می‌رسد و حافظه دیگری را نمی‌توان به اپلیکیشن تخصیص داد. در این مرحله خطای OutOfMemoryError اعلام شده و در نهایت اپلیکیشن از کار می‌افتد.

همچنین باید به خاطر داشته باشید که گردآوری garbage یک فرایند سنگین است و از این رو هر چه garbage collector کمتر اجرا شود، برای اپلیکیشن بهتر است.

زمانی که اپلیکیشن مورد استفاده قرار می‌گیرد و حافظه heap شروع به افزایش می‌کند، یک GC کوتاه اجرا می‌شود و شروع به پاکسازی بی‌درنگ شیءهای مرده می‌کند. در این مرحله این GC-ها به صورت همزمان (روی نخ مجزا) اجرا می‌شوند و اپلیکیشن شما را کُند نمی‌سازند و در کل یک مکث 2 تا 5 میلی‌ثانیه‌ای خواهد داشت.

اما اگر اپلیکیشن با نشت‌های حافظه جدی مواجه باشد که در پشت صحنه پنهان شده باشند، در این صورت GC-های کوتاه قادر به آزادسازی حافظه نخواهند بود و هیپ شروع به افزایش می‌کند، بدین ترتیب نیاز به یک GC بزرگ‌تر وجود خواهد داشت که عموماً موجب یک مکث از نوع «توقف کامل» (stop-the-world) در نخ اصلی اپلیکیشن می‌شوند. این مکث حدود 50 تا 100 میلی‌ثانیه زمان طول می‌کشد و بدین ترتیب باعث می‌شود که اپلیکیشن دچار وقفه جدی شود و برای مدت زمانی تقریباً غیر قابل استفاده شود.

بدین ترتیب اکنون تأثیر این نشت‌های حافظه را که بر روی اپلیکیشن واقع می‌شوند و همچنین علت این که چرا باید به سرعت آن‌ها را رفع کنیم را می‌دانیم. بدین ترتیب کاربران بهترین تجربه کاربری را که شایسته‌اش هستند به دست می‌آورند.

چگونه نشت حافظه را تشخیص دهیم؟

تا به اینجا احتمالاً به طور کامل قانع شده‌اید که باید این نشت‌های حافظه را که به صورت پنهان درون اپلیکیشن شما وجود دارند رفع کنید. اما سؤال این است که چگونه می‌توان آن‌ها را تشخیص داد؟

خبر خوب این است که اندروید استودیو ابزار بسیار مفید و قدرتمندی به این منظور دارد که Monitors نام دارد. در واقع مانیتورهای منفردی وجود دارند که نه تنها برای نظارت بر مصرف حافظه بلکه برای نظارت بر مصرف CPU و GPU نیز استفاده می‌شوند.

نشت حافظه

در زمان استفاده و دیباگ کردن اپلیکیشن می‌توانید نگاهی به این مانیتور حافظه داشته باشید. نخستین نشانه‌های نشت حافظه هنگامی رخ می‌دهند که وقتی به استفاده از اپلیکیشن ادامه می‌دهید، گراف مصرف حافظه به سرعت رو به فزونی می‌گذارد و هرگز حتی هنگامی که اپلیکیشن در پس زمینه اجرا می‌شود، پایین نمی‌آید.

Allocation Tracker در مواردی که بتوانید از آن برای بررسی درصد حافظه تخصیص‌یافته به انواع شیءها در اپلیکیشن استفاده کنید بسیار کارآمد خواهد بود. بدین ترتیب می‌توان ایده روشنی از این که کدام اشیا بخش زیادی از حافظه را اشغال کرده‌اند و باید به طور خاص مورد رسیدگی قرار گیرند، به دست آورد.

اما این کار به تنهایی کافی نیست، چون اکنون باید از گزینه Dump Java Heap برای ایجاد یک heap dump استفاده کنید که در عمل نماینده یک اسنپ‌شات از حافظه در نقطه خاصی از زمان است. چنان که می‌بینید این وضعیت نیازمند مقدار زیادی کار خسته‌کننده و تکراری است.

نشت حافظه

ما مهندسان غالباً از کارهای دشوار اجتناب می‌کنیم و این دقیقاً همان موقعیتی است که LeakCanary (+) به کمک ما می‌آید. این کتابخانه همراه با اپلیکیشن اجرا می‌شود و حافظه را در زمان‌های مورد نیاز dump می‌کند و به دنبال موارد احتمالی نشت حافظه می‌گردد تا به همراه یک رد پشته تمیز و مفید به شما اعلان کند و بدین ترتیب می‌توانید ریشه نشت حافظه را پیدا کنید. LeakCanary امکان تشخیص نشت حافظه را برای همه افراد به طور آسانی فراهم ساخته است.

برخی سناریوهای رایج نشت حافظه و روش اصلاح آن‌ها

برخی سناریوهای بسیار رایج نشت حافظه وجود دارند که بروز آن‌ها بسیار محتمل است و اغلب در تجربه توسعه روزمره اندروید با این سناریوها مواجه می‌شویم. زمانی که بدانید چه هنگام، کجا و چگونه نشت‌های حافظه رخ می‌دهند، می‌توانید بدون دشواری اقدام به اصلاح آن‌ها بکنید.

شنونده‌های ثبت نشده

موقعیت‌های زیادی وجود دارند که شما یک «شنونده» (Listener) را در اکتیویتی (یا فرگمان) خود ثبت می‌کنید، اما فراموش می‌کنید آن را از فهرست ثبت نام خارج کنید. در صورتی که خوش‌شانس نباشید، این وضعیت به سادگی منجر به نشت حافظه زیادی می‌شود. به طور کلی این شنونده‌ها همدیگر را متعادل می‌سازند و از این رو اگر جایی در اپلیکیشن ثبت شوند باید، آن‌ها را دقیقاً همان جا از فهرست ثبت خارج کنید.

اینک نوبت آن رسیده است که یک مثال ساده را بررسی کنیم. فرض کنید می‌خواهید به‌روزرسانی‌های مکان را در اپلیکیشن خود به دست آورید و از این رو باید سرویس سیستم LocationManager را استفاده کرده و یک شنونده برای به‌روزرسانی موقعیت ثبت کنید.

شما این اینترفیس شنونده را در اکتیویتی خود پیاده‌سازی می‌کنید و از این رو LocationManager یک ارجاع به آن نگهداری می‌کند. اکنون اگر زمان آن رسیده باشد که اکتیویتی متوقف شده و فریمورک اندروید متد ()onDestroy را روی آن فراخوانی کند، اما garbage collector نخواهد توانست وهله‌ای از آن را از حافظه پاک کند، زیرا LocationManager همچنان یک ارجاع قوی برای آن نگهداری می‌کند.

راه‌حل بسیار ساده است. کافی است شنونده را در متد ()onDestroy ثبت کنید و بدین ترتیب مشکل حل می‌شود. این جزییاتی است که اغلب ما فراموش می‌کنیم و یا شاید حتی نمی‌دانیم.

کلاس‌های داخلی

کلاس‌های داخلی در جاوا بسیار رایج هستند و به دلیل سادگی‌شان از سوی بسیاری از توسعه‌دهندگان اندروید برای وظایف مختلفی استفاده می‌شوند. اما اگر به صورت درستی از آن‌ها استفاده نمی‌کنید، همین کلاس‌های داخلی می‌توانند منجر به نشت‌های حافظه بالقوه‌ای شوند.

در ادامه به کمک یک مثال ساده به بررسی این وضعیت می‌پردازیم.

این یک اکتیویتی کاملاً ساده است که یک وظیفه با اجرای بلندمدت را در نخ پس زمینه آغاز می‌کند. این وظیفه می‌تواند یک کوئری پایگاه داده یا یک فراخوانی کُند شبکه باشد. پس از این که وظیفه پایان یابد، نتیجه در یک TextView نمایش می‌یابد. ظاهر همه چیز خوب است.

البته مشکلی وجود دارد. مشکل این است که در این کد کلاس داخلی غیر استاتیک یک ارجاع صریح به کلاس احاطه کننده بیرونی خود (که همان اکتیوتی است) نگهداری می‌کند. اکنون اگر صفحه را بچرخانید یا اگر وظیفه بلندمدت، بیشتر از عمر اکتیویتی طول بکشد، garbage collector نخواهد توانست وهله‌ای از یک اکتیوتی کامل را از حافظه پاک کند. بدین ترتیب یک خطای ساده منجر به نشت حافظه بزرگی می‌شود.

اما در این مورد نیز راه‌حل کار آسان است و کافی است نگاهی به کد زیر بیندازید تا آن را تشخیص دهید:

آن چنان که می‌بینید، ما کلاس داخلی غیر استاتیک را به یک کلاس داخلی استاتیک تبدیل کرده‌ایم، چون کلاس داخلی استاتیک هیچ ارجاع صریحی به کلاس محاط بیرونی خود نگه نمی‌دارد. اما اینک از یک کانتسکت استاتیک دیگر به متغیرهای غیر استاتیک (مانند TextView) در کلاس بیرونی دسترسی نداریم و از این رو مجبور هستیم ارجاع شیءهای مورد نیاز را از طریق سازنده به کلاس درونی ارسال کنیم.

قویاً توصیه می‌شود که این ارجاع‌های شیء را در یک WeakReference قرار دهیم تا از بروز نشت‌های حافظه بیشتر جلوگیری کنیم. بدین ترتیب شما باید با انواع مختلف ارجاع‌های موجود در جاوا آشنا باشید و بدانید که چگونه می‌توانید بهترین استفاده از آن‌ها را برای جلوگیری از بروز نشت حافظه داشته باشید.

کلاس‌های بی نام

کلاس‌های بی نام در میان اغلب توسعه‌دهندگان محبوب هستند، زیرا روشی که برای تعریف آن‌ها استفاده می‌شود موجب می‌شود که نوشتن کد با استفاده از آن‌ها بسیار آسان و فشرده باشد. اما برخی از توسعه دهنگان نیز بر این باور هستند که این کلاس‌های بی نام مهم‌ترین دلیل بروز نشت‌های حافظه هستند.

کلاس‌های بی نام چیزی به جز کلاس‌های داخلی غیر استاتیک نیستد که موجب بروز نشت‌های حافظه بالقوه می‌شوند و در مورد دلیل این مسئله در بخش قبلی توضیح دادیم. شما ممکن است در چند جای مختلف اپلیکیشن از آن‌ها استفاده کنید اما شاید این موضوع را ندانید که اگر روش استفاده از آن‌ها نادرست باشد می‌تواند تأثیر شدیدی بر روی عملکرد اپلیکیشن داشته باشد.

در کد فوق ما از یک کتابخانه بسیار محبوب به نام Retrofit (+) برای ایجاد یک فراخوانی شبکه و نمایش نتیجه در یک TextView استفاده می‌کنیم. کاملاً روشن است که این شیء قابل فراخوانی نیز یک ارجاع به کلاس اکتیویتی محاط خود نگه می‌دارد.

اکنون اگر فراخوانی شبکه روی یک اتصال بسیار کُند اجرا شود و پیش از فراخوانی پایان گیرد، اگر اکتیویتی به نوعی دچار چرخش یا تخریب شود، در این صورت کل وهله اکتیویتی نشت خواهد یافت.

استفاده از کلاس‌های داخلی استاتیک به جای کلاس‌های بی نام در همه مواردی که ممکن یا ضروری باشد، یک توصیه کاملاً مفید محسوب می‌شود. البته به این آن معنی نیست که توصیه کنیم استفاده از کلاس‌های بی نام را بی‌درنگ و به طور کامل متوقف کنید، بلکه باید در مورد این که چه زمان استفاده از آن‌ها امن است و چه زمان چنین نیست، درک مناسبی داشته و در مورد آن قضاوت کنید.

Bitmap

هر تصویری که در اپلیکیشن می‌بینید چیزی به جز اشیای Bitmap نیست که شامل داده‌های پیکسلی یک تصویر است. این اشیای بیت‌مپ عموماً کاملاً سنگین هستند و اگر به درستی با آن‌ها برخورد نشود، منجر به نشت قابل توجهی در حافظه می‌شوند و می‌توانند در نهایت باعث از کار افتادن اپلیکیشن به دلیل خطای OutOfMemoryError شوند. حافظه بیت‌مپ مرتبط با منابع تصویر که در اپلیکیشن استفاده می‌کنید همیشه به صورت خودکار از سوی فریمورک مدیریت می‌شوند، اما اگر بیت‌مپ‌ها را به صورت دستی مدیریت کنید، باید مطمئن شوید که آن‌ها را پس از استفاده ()recycle می‌کنید.

همچنین باید بدانید که چگونه می‌توان بیت‌مپها را به درستی مدیریت کرد. بارگذاری بیت‌مپ‌های بزرگ به وسیله مقیاس‌بندی کردن آن‌ها و استفاده از کش کردن بیت‌مپ و pool کردن در موارد مقتضی موجب کاهش مصرف حافظه می‌شود.

Context

دلیل مهم دیگر بروز نشت حافظه سوءاستفاده از وهله‌های Context است. Context صرفاً یک کلاس مجرد است و کلاس‌های زیادی (مانند Activity, Application, Service و غیره) وجود دارند که آن را بسط می‌دهند تا کارکردهای خاص خود را ارائه دهند.

اگر می‌خواهید کارها را در اندروید انجام دهید، شیء Context بهترین همراه شما است. اما بین این Context-ها تفاوت وجود دارد. درک تفاوت بین Context سطح اکتیویتی و Context سطح اپلیکیشن و این که کدام یک در کدام شرایط استفاده می‌شود، بسیار حائز اهمیت است.

استفاده از Context اکتیویتی در مکان نادرست باعث می‌شود که ارجاعی به کل اکتیویتی نگهداری شود و موجب بروز نشت‌های حافظه بالقوه می‌شود.

سخن پایانی

اینک باید متوجه شده باشید که Garbage Collector چگونه کار می‌کند، نشت حافظه چیست و چگونه می‌تواند تأثیر عمده‌ای روی عملکرد اپلیکیشن داشته باشد. همچنین با روش تشخیص و اصلاح این نشت‌های حافظه آشنا شده‌اید. بنابراین دیگر عذر موجهی ندارید و از این پس باید شروع به ساخت اپلیکیشن‌های اندرویدی با کیفیت خوب و عملکرد بالا بکنید. تشخیص و اصلاح کردن نشت‌های حافظه نه تنها موجب می‌شود که تجربه کاربری بهتری پدید آید، بلکه شما را نیز به توسعه‌دهنده بهتری تبدیل می‌کند.

منبع: فرادرس


نشت حافظه در اپلیکیشن‌ های اندروید — از صفر تا صد

 برنامه نویسی  55 بازدید

ساخت یک اپلیکیشن اندروید کار آسانی است، اما ایجاد یک اپلیکیشن اندروید با کیفیت کاملاً بالا که از نظر حافظه کارآمد باشد چنین نیست. برخی توسعه‌دهندگان علاقه زیادی به تمرکز و یافتن قابلیت‌ها، کارکردها و اجزای UI جدید در اپلیکیشن‌های خود دارند. برخی برنامه‌نویسان تمایل دارند روی چیزی کار کنند که تأثیر دیداری بیشتری داشته باشد تا این که زمان خود را صرف چیزهایی بکنند که اغلب افراد در نگاه نخست ممکن است حتی متوجه آن نشوند. اما این رویه خوبی نیست، شما باید این عادت اولویت‌بندی پایین‌تر موارد مرتبط با بهینه‌سازی اپلیکیشن را ترک کنید. یکی از مهم‌ترین گام‌های بهینه‌سازی اپلیکیشن نیز اصلاح موارد نشت حافظه است.

این وضعیت به صورت طبیعی منجر به تشکیل یک بدهی فنی می‌شود که در بلندمدت، شروع به تأثیرگذاری روی کیفیت اپلیکیشن‌ها می‌کند. شما باید خود را عادت بدهید که به تدریج ذهنیت فوق را که اشاره کردیم کنار بگذارید و بیشتر روی عملکرد اپلیکیشن‌های خود متمرکز شوید.

مفهوم نشت حافظه برای بسیاری از توسعه‌دهندگان آزاردهنده است، چون آن را دشوار، زمان‌بر، خسته‌کننده و غیرضروری می‌پندارند، اما خوشبختانه هیچ کدام از این موارد صحت ندارند. زمانی که شروع به بررسی آن بکنید، متوجه می‌شوید که موضوع کاملاً زیبا و جذابی است.

در این مقاله تلاش می‌کنیم این موضوع را تا حد امکان ساده بکنیم، به طوری که حتی توسعه‌دهندگان تازه‌کار نیز بتوانند از همان آغاز مسیر حرفه‌ای خود، شروع به ساخت اپلیکیشن‌های اندرویدی با کیفیت خوب و عملکرد بالا بکنند.

Garbage Collector دوست شما است؛ اما نه همیشه

جاوا زبان پایداری است. در اندروید کدی به زبان‌های C یا ++C نوشته نمی‌شود (البته به جز برخی موارد نادر). بدین ترتیب مجبور نیستیم که کل فرایند تخصیص حافظه و آزادی سازی آن را شخصاً مدیریت کنیم. خوشبختانه جاوا به خوبی از عهده انجام این امور برمی‌آید.

پس اینک نخستین سؤالی که به ذهن می‌آید این است که آیا جاوا یک سیستم مدیریت حافظه اختصاصی درونی دارد که می‌تواند به صورت خودکار حافظه را در صورت استفاده نشدن پاک کند؟ در این صورت چرا ما به عنوان توسعه‌دهنده باید در مورد این موضوع دغدغه داشته باشیم؟ آیا Garbage Collector مستعد خطا است؟

پاسخ سؤال فوق منفی است. Garbage Collector دقیقاً همان طور که طراحی شده است کار می‌کند، اما این اشتباه‌های برنامه‌نویسی خود ما است که برخی اوقات Garbage Collector را از گردآوری بخش‌های استفاده نشده حافظه بازمی‌دارد.

بنابراین باید گفت که اساساً این خطای ما است که منجر به بروز مشکل در حافظه می‌شود. Garbage Collector یکی از برترین دستاوردهای جاوا محسوب می‌شود و از این رو شایسته احترام است.

توضیحی در خصوص Garbage Collector

پیش از آن که در خصوص موضوع مقاله بیشتر توضیح بدهیم، باید اندکی در مورد طرز کار عملی Garbage Collector بدانیم. تئوری کارکرد Garbage Collector کاملاً ساده است، اما آن چه در پشت صحنه اتفاق می‌افتد در پاره‌ای موارد کاملاً پیچیده است. در هر حال جای نگرانی نیست، چون ما به طور عمده روی بخش‌های ساده آن تمرکز خواهیم داشت.

نشت حافظه

هر اپلیکیشن اندروید (یا جاوا) باید یک نقطه آغازین داشته باشد که اشیا از آنجا شروع به وهله‌سازی می‌کنند و متدها فراخوانی می‌شوند. بنابراین می‌توانیم این نقطه آغازین را به عنوان ریشه یا root درخت حافظه تصور کنیم. برخی اشیا مستقیماً یک ارجاع به root دارند و برخی دیگر از آن‌ها وهله‌سازی می‌شوند و ارجاع خود را به این اشیا نگه می‌دارند و همین طور ادامه می‌یابد.

از این رو یک زنجیره از ارجاعات شکل می‌گیرد که درخت حافظه را تشکیل می‌دهند. بنابراین Garbage Collector از ریشه‌های GC کار خود را آغاز می‌کند و اشیا را مستقیماً پیمایش می‌کند یا به صورت غیرمستقیم تا root می‌پیماید. در انتهای این فرایند، برخی اشیا وجود خواهند داشت که هرگز از سوی GC ملاقات نشده‌اند.

این موارد همان Garbage (یا اشیای مرده) هستند و همین‌ها هستند که باید از سوی Garbage Collector دوست‌داشتنی ما گردآوری شوند. تا به اینجا داستان شبیه به یک افسانه ساده کودکان بوده است، اما در ادامه کمی عمیق‌تر می‌شویم تا با جذابیت واقعی کارکرد Garbage Collector آشنا شویم.

نشت حافظه چیست؟

تا به اینجا ایده خلاصه‌ای از ماهیت Garbage Collector و طرز کار عملی مدیریت حافظه در اپلیکیشن‌های اندرویدی به دست آورده‌ایم. اکنون نوبت آن رسیده است که روی موضوع نشت حافظه با جزییات بیشتری تمرکز کنیم.

به بیان ساده نشت حافظه زمانی رخ می‌دهد که یک شیء را مدت‌ها پس از آن که هدف خود را برآورده ساخت نگهداری کنیم. مفهوم واقعی موضوع به همین سادگی است. هر شیء طول عمر خاص خود را دارد که در زمان پایان یافتن، باید از آن خداحافظی کرد و در نتیجه حافظه را ترک می‌کند. اما اگر برخی شیء (-های) دیگر به صورت مستقیم یا غیرمستقیم این شیء را نگه دارند، در این صورت Garbage Collector نمی‌تواند آن را گردآوری کند. این وضعیت همان نشت حافظه است.

اما خبر خوب این است که لازم نیست در مورد وقوع رخداد منفرد نشت حافظه در اپلیکیشن نگران باشید، چون همه نشت‌های حافظه باعث از کار افتادن برنامه شما نمی‌شوند.

برخی نشت‌ها هستند که کاملاً کوچک محسوب می‌شوند (نشت چند کیلوبایت حافظه)، برخی از نشت‌ها نیز در خود فریمورک اندروید رخ می‌دهند که شما نمی‌توانید و نباید آن‌ها را رفع کنید. این موارد عموماً تأثیر اندکی روی عملکرد اپلیکیشن شما دارند و می‌توان آن‌ها را به صورت امنی نادیده گرفت.

اما موارد دیگری هستند که می‌توانند اپلیکیشن را از کار بیندازند و آن را به مقدار زیادی کُند سازند به ترتیبی که به زانو درآید. این نشت‌ها آن‌هایی هستند که باید مراقبشان باشیم.

چرا باید نشت‌های حافظه را رفع کرد؟

هیچ کس نمی‌خواهد از اپلیکیشنی استفاده کند که کُند و دارای وقفه است و پس از چند دقیقه مقدار زیادی از حافظه را مصرف می‌کنند. این وضعیت می‌تواند تجربه واقعاً بدی برای کاربر ایجاد کند و اگر برای مدت زیادی طول بکشد، در این صورت احتمال بالایی وجود دارد که آن کاربر را برای همیشه از دست بدهید.

نشت حافظه

زمانی که کاربر به استفاده از اپلیکیشن شما ادامه می‌دهد، حافظه heap نیز شروع به افزایش می‌کند و اگر نشت حافظه در اپلیکیشن رخ بدهد، در این صورت حافظه استفاده نشده در heap نمی‌تواند از سوی GC رهاسازی شود. بنابراین حافظه heap در اپلیکیشن به صورت مداوم افزایش می‌یابد تا این که به نقطه مرگ می‌رسد و حافظه دیگری را نمی‌توان به اپلیکیشن تخصیص داد. در این مرحله خطای OutOfMemoryError اعلام شده و در نهایت اپلیکیشن از کار می‌افتد.

همچنین باید به خاطر داشته باشید که گردآوری garbage یک فرایند سنگین است و از این رو هر چه garbage collector کمتر اجرا شود، برای اپلیکیشن بهتر است.

زمانی که اپلیکیشن مورد استفاده قرار می‌گیرد و حافظه heap شروع به افزایش می‌کند، یک GC کوتاه اجرا می‌شود و شروع به پاکسازی بی‌درنگ شیءهای مرده می‌کند. در این مرحله این GC-ها به صورت همزمان (روی نخ مجزا) اجرا می‌شوند و اپلیکیشن شما را کُند نمی‌سازند و در کل یک مکث 2 تا 5 میلی‌ثانیه‌ای خواهد داشت.

اما اگر اپلیکیشن با نشت‌های حافظه جدی مواجه باشد که در پشت صحنه پنهان شده باشند، در این صورت GC-های کوتاه قادر به آزادسازی حافظه نخواهند بود و هیپ شروع به افزایش می‌کند، بدین ترتیب نیاز به یک GC بزرگ‌تر وجود خواهد داشت که عموماً موجب یک مکث از نوع «توقف کامل» (stop-the-world) در نخ اصلی اپلیکیشن می‌شوند. این مکث حدود 50 تا 100 میلی‌ثانیه زمان طول می‌کشد و بدین ترتیب باعث می‌شود که اپلیکیشن دچار وقفه جدی شود و برای مدت زمانی تقریباً غیر قابل استفاده شود.

بدین ترتیب اکنون تأثیر این نشت‌های حافظه را که بر روی اپلیکیشن واقع می‌شوند و همچنین علت این که چرا باید به سرعت آن‌ها را رفع کنیم را می‌دانیم. بدین ترتیب کاربران بهترین تجربه کاربری را که شایسته‌اش هستند به دست می‌آورند.

چگونه نشت حافظه را تشخیص دهیم؟

تا به اینجا احتمالاً به طور کامل قانع شده‌اید که باید این نشت‌های حافظه را که به صورت پنهان درون اپلیکیشن شما وجود دارند رفع کنید. اما سؤال این است که چگونه می‌توان آن‌ها را تشخیص داد؟

خبر خوب این است که اندروید استودیو ابزار بسیار مفید و قدرتمندی به این منظور دارد که Monitors نام دارد. در واقع مانیتورهای منفردی وجود دارند که نه تنها برای نظارت بر مصرف حافظه بلکه برای نظارت بر مصرف CPU و GPU نیز استفاده می‌شوند.

نشت حافظه

در زمان استفاده و دیباگ کردن اپلیکیشن می‌توانید نگاهی به این مانیتور حافظه داشته باشید. نخستین نشانه‌های نشت حافظه هنگامی رخ می‌دهند که وقتی به استفاده از اپلیکیشن ادامه می‌دهید، گراف مصرف حافظه به سرعت رو به فزونی می‌گذارد و هرگز حتی هنگامی که اپلیکیشن در پس زمینه اجرا می‌شود، پایین نمی‌آید.

Allocation Tracker در مواردی که بتوانید از آن برای بررسی درصد حافظه تخصیص‌یافته به انواع شیءها در اپلیکیشن استفاده کنید بسیار کارآمد خواهد بود. بدین ترتیب می‌توان ایده روشنی از این که کدام اشیا بخش زیادی از حافظه را اشغال کرده‌اند و باید به طور خاص مورد رسیدگی قرار گیرند، به دست آورد.

اما این کار به تنهایی کافی نیست، چون اکنون باید از گزینه Dump Java Heap برای ایجاد یک heap dump استفاده کنید که در عمل نماینده یک اسنپ‌شات از حافظه در نقطه خاصی از زمان است. چنان که می‌بینید این وضعیت نیازمند مقدار زیادی کار خسته‌کننده و تکراری است.

نشت حافظه

ما مهندسان غالباً از کارهای دشوار اجتناب می‌کنیم و این دقیقاً همان موقعیتی است که LeakCanary (+) به کمک ما می‌آید. این کتابخانه همراه با اپلیکیشن اجرا می‌شود و حافظه را در زمان‌های مورد نیاز dump می‌کند و به دنبال موارد احتمالی نشت حافظه می‌گردد تا به همراه یک رد پشته تمیز و مفید به شما اعلان کند و بدین ترتیب می‌توانید ریشه نشت حافظه را پیدا کنید. LeakCanary امکان تشخیص نشت حافظه را برای همه افراد به طور آسانی فراهم ساخته است.

برخی سناریوهای رایج نشت حافظه و روش اصلاح آن‌ها

برخی سناریوهای بسیار رایج نشت حافظه وجود دارند که بروز آن‌ها بسیار محتمل است و اغلب در تجربه توسعه روزمره اندروید با این سناریوها مواجه می‌شویم. زمانی که بدانید چه هنگام، کجا و چگونه نشت‌های حافظه رخ می‌دهند، می‌توانید بدون دشواری اقدام به اصلاح آن‌ها بکنید.

شنونده‌های ثبت نشده

موقعیت‌های زیادی وجود دارند که شما یک «شنونده» (Listener) را در اکتیویتی (یا فرگمان) خود ثبت می‌کنید، اما فراموش می‌کنید آن را از فهرست ثبت نام خارج کنید. در صورتی که خوش‌شانس نباشید، این وضعیت به سادگی منجر به نشت حافظه زیادی می‌شود. به طور کلی این شنونده‌ها همدیگر را متعادل می‌سازند و از این رو اگر جایی در اپلیکیشن ثبت شوند باید، آن‌ها را دقیقاً همان جا از فهرست ثبت خارج کنید.

اینک نوبت آن رسیده است که یک مثال ساده را بررسی کنیم. فرض کنید می‌خواهید به‌روزرسانی‌های مکان را در اپلیکیشن خود به دست آورید و از این رو باید سرویس سیستم LocationManager را استفاده کرده و یک شنونده برای به‌روزرسانی موقعیت ثبت کنید.

شما این اینترفیس شنونده را در اکتیویتی خود پیاده‌سازی می‌کنید و از این رو LocationManager یک ارجاع به آن نگهداری می‌کند. اکنون اگر زمان آن رسیده باشد که اکتیویتی متوقف شده و فریمورک اندروید متد ()onDestroy را روی آن فراخوانی کند، اما garbage collector نخواهد توانست وهله‌ای از آن را از حافظه پاک کند، زیرا LocationManager همچنان یک ارجاع قوی برای آن نگهداری می‌کند.

راه‌حل بسیار ساده است. کافی است شنونده را در متد ()onDestroy ثبت کنید و بدین ترتیب مشکل حل می‌شود. این جزییاتی است که اغلب ما فراموش می‌کنیم و یا شاید حتی نمی‌دانیم.

کلاس‌های داخلی

کلاس‌های داخلی در جاوا بسیار رایج هستند و به دلیل سادگی‌شان از سوی بسیاری از توسعه‌دهندگان اندروید برای وظایف مختلفی استفاده می‌شوند. اما اگر به صورت درستی از آن‌ها استفاده نمی‌کنید، همین کلاس‌های داخلی می‌توانند منجر به نشت‌های حافظه بالقوه‌ای شوند.

در ادامه به کمک یک مثال ساده به بررسی این وضعیت می‌پردازیم.

این یک اکتیویتی کاملاً ساده است که یک وظیفه با اجرای بلندمدت را در نخ پس زمینه آغاز می‌کند. این وظیفه می‌تواند یک کوئری پایگاه داده یا یک فراخوانی کُند شبکه باشد. پس از این که وظیفه پایان یابد، نتیجه در یک TextView نمایش می‌یابد. ظاهر همه چیز خوب است.

البته مشکلی وجود دارد. مشکل این است که در این کد کلاس داخلی غیر استاتیک یک ارجاع صریح به کلاس احاطه کننده بیرونی خود (که همان اکتیوتی است) نگهداری می‌کند. اکنون اگر صفحه را بچرخانید یا اگر وظیفه بلندمدت، بیشتر از عمر اکتیویتی طول بکشد، garbage collector نخواهد توانست وهله‌ای از یک اکتیوتی کامل را از حافظه پاک کند. بدین ترتیب یک خطای ساده منجر به نشت حافظه بزرگی می‌شود.

اما در این مورد نیز راه‌حل کار آسان است و کافی است نگاهی به کد زیر بیندازید تا آن را تشخیص دهید:

آن چنان که می‌بینید، ما کلاس داخلی غیر استاتیک را به یک کلاس داخلی استاتیک تبدیل کرده‌ایم، چون کلاس داخلی استاتیک هیچ ارجاع صریحی به کلاس محاط بیرونی خود نگه نمی‌دارد. اما اینک از یک کانتسکت استاتیک دیگر به متغیرهای غیر استاتیک (مانند TextView) در کلاس بیرونی دسترسی نداریم و از این رو مجبور هستیم ارجاع شیءهای مورد نیاز را از طریق سازنده به کلاس درونی ارسال کنیم.

قویاً توصیه می‌شود که این ارجاع‌های شیء را در یک WeakReference قرار دهیم تا از بروز نشت‌های حافظه بیشتر جلوگیری کنیم. بدین ترتیب شما باید با انواع مختلف ارجاع‌های موجود در جاوا آشنا باشید و بدانید که چگونه می‌توانید بهترین استفاده از آن‌ها را برای جلوگیری از بروز نشت حافظه داشته باشید.

کلاس‌های بی نام

کلاس‌های بی نام در میان اغلب توسعه‌دهندگان محبوب هستند، زیرا روشی که برای تعریف آن‌ها استفاده می‌شود موجب می‌شود که نوشتن کد با استفاده از آن‌ها بسیار آسان و فشرده باشد. اما برخی از توسعه دهنگان نیز بر این باور هستند که این کلاس‌های بی نام مهم‌ترین دلیل بروز نشت‌های حافظه هستند.

کلاس‌های بی نام چیزی به جز کلاس‌های داخلی غیر استاتیک نیستد که موجب بروز نشت‌های حافظه بالقوه می‌شوند و در مورد دلیل این مسئله در بخش قبلی توضیح دادیم. شما ممکن است در چند جای مختلف اپلیکیشن از آن‌ها استفاده کنید اما شاید این موضوع را ندانید که اگر روش استفاده از آن‌ها نادرست باشد می‌تواند تأثیر شدیدی بر روی عملکرد اپلیکیشن داشته باشد.

در کد فوق ما از یک کتابخانه بسیار محبوب به نام Retrofit (+) برای ایجاد یک فراخوانی شبکه و نمایش نتیجه در یک TextView استفاده می‌کنیم. کاملاً روشن است که این شیء قابل فراخوانی نیز یک ارجاع به کلاس اکتیویتی محاط خود نگه می‌دارد.

اکنون اگر فراخوانی شبکه روی یک اتصال بسیار کُند اجرا شود و پیش از فراخوانی پایان گیرد، اگر اکتیویتی به نوعی دچار چرخش یا تخریب شود، در این صورت کل وهله اکتیویتی نشت خواهد یافت.

استفاده از کلاس‌های داخلی استاتیک به جای کلاس‌های بی نام در همه مواردی که ممکن یا ضروری باشد، یک توصیه کاملاً مفید محسوب می‌شود. البته به این آن معنی نیست که توصیه کنیم استفاده از کلاس‌های بی نام را بی‌درنگ و به طور کامل متوقف کنید، بلکه باید در مورد این که چه زمان استفاده از آن‌ها امن است و چه زمان چنین نیست، درک مناسبی داشته و در مورد آن قضاوت کنید.

Bitmap

هر تصویری که در اپلیکیشن می‌بینید چیزی به جز اشیای Bitmap نیست که شامل داده‌های پیکسلی یک تصویر است. این اشیای بیت‌مپ عموماً کاملاً سنگین هستند و اگر به درستی با آن‌ها برخورد نشود، منجر به نشت قابل توجهی در حافظه می‌شوند و می‌توانند در نهایت باعث از کار افتادن اپلیکیشن به دلیل خطای OutOfMemoryError شوند. حافظه بیت‌مپ مرتبط با منابع تصویر که در اپلیکیشن استفاده می‌کنید همیشه به صورت خودکار از سوی فریمورک مدیریت می‌شوند، اما اگر بیت‌مپ‌ها را به صورت دستی مدیریت کنید، باید مطمئن شوید که آن‌ها را پس از استفاده ()recycle می‌کنید.

همچنین باید بدانید که چگونه می‌توان بیت‌مپها را به درستی مدیریت کرد. بارگذاری بیت‌مپ‌های بزرگ به وسیله مقیاس‌بندی کردن آن‌ها و استفاده از کش کردن بیت‌مپ و pool کردن در موارد مقتضی موجب کاهش مصرف حافظه می‌شود.

Context

دلیل مهم دیگر بروز نشت حافظه سوءاستفاده از وهله‌های Context است. Context صرفاً یک کلاس مجرد است و کلاس‌های زیادی (مانند Activity, Application, Service و غیره) وجود دارند که آن را بسط می‌دهند تا کارکردهای خاص خود را ارائه دهند.

اگر می‌خواهید کارها را در اندروید انجام دهید، شیء Context بهترین همراه شما است. اما بین این Context-ها تفاوت وجود دارد. درک تفاوت بین Context سطح اکتیویتی و Context سطح اپلیکیشن و این که کدام یک در کدام شرایط استفاده می‌شود، بسیار حائز اهمیت است.

استفاده از Context اکتیویتی در مکان نادرست باعث می‌شود که ارجاعی به کل اکتیویتی نگهداری شود و موجب بروز نشت‌های حافظه بالقوه می‌شود.

سخن پایانی

اینک باید متوجه شده باشید که Garbage Collector چگونه کار می‌کند، نشت حافظه چیست و چگونه می‌تواند تأثیر عمده‌ای روی عملکرد اپلیکیشن داشته باشد. همچنین با روش تشخیص و اصلاح این نشت‌های حافظه آشنا شده‌اید. بنابراین دیگر عذر موجهی ندارید و از این پس باید شروع به ساخت اپلیکیشن‌های اندرویدی با کیفیت خوب و عملکرد بالا بکنید. تشخیص و اصلاح کردن نشت‌های حافظه نه تنها موجب می‌شود که تجربه کاربری بهتری پدید آید، بلکه شما را نیز به توسعه‌دهنده بهتری تبدیل می‌کند.

منبع: فرادرس


طراحی شبکه تصاویر واکنش گرا با CSS Grid Layout — از صفر تا صد

شما به عنوان یک توسعه‌دهنده فرانت‌اند حتماً تاکنون تجربیاتی در زمینه CSS داشته‌اید، اما اغلب ما این روزها کار با CSS را از طریق فریمورک‌هایی مانند Bootstrap انجام می‌دهیم. برخی موقعیت‌ها وجود دارند که مجبور خواهیم بود مستقیماً با CSS کار کنیم و یکی از آن‌ها مواردی است که بخواهیم با سیستم Grid Layout کار کنیم. در این مقاله به طور عمده در مورد شیوه استفاده از CSS Grid Layout جهت ساختن یک شبکه از تصاویر صحبت خواهیم کرد.

ما قصد نداریم همه مشخصه‌های Grid Layout را بررسی کنیم، بلکه روی آن مشخصه‌هایی متمرکز می‌شویم که برای اجرای وظیفه فوق مورد نیاز هستند.

CSS Grid Layout به چه معنی است؟

Grid Layout در CSS یک سیستم طرح‌بندی دوبُعدی برای وب است. شبکه‌ها امکان سازماندهی محتوا در ردیف‌ها و ستون‌ها را به ما می‌دهند. طرح‌بندی یک صفحه وب با یک هدر، یک نوار کناری، ناحیه محتوای اصلی و یک فوتر (مانند تصویر 1 زیر) را تصور کنید. این اجزای صفحه وب نیازمند طرح‌بندی صحیحی روی صفحه هستند. Grid در CSS به ما کمک می‌کند که این کار را چنان که با بررسی یک مثال از شبکه تصاویر خواهیم دید، انجام دهیم.

CSS Grid Layout
تصویر 1: نمونه‌ای از Grid در CSS برای طرح‌بندی یک صفحه وب

در تصویر زیر شبکه تصاویری که از آن صحبت کردیم و می‌خواهیم بسازیم را مشاهده می‌کنید.

CSS Grid Layout

خوب هر چه تا اینجا در مورد تئوری صحبت کردیم کافی است. اینک نوبت کار عملی فرا رسیده است.

یک پوشه روی سیستم خود ایجاد کرد و نامی برای آن تعیین کنید. ما پوشه خودمان را Photogrid می‌نامیم. پوشه را در ویرایشگر متنی محبوب خود باز کنید. ما از VSCode استفاده می‌کنیم. 2 فایل ایجاد کنید که نام یکی index.html و دیگری main.css است. ما استایل های مورد نیاز را در فایل main.css می‌نویسیم. کد زیر را به فایل index.html کپی کنید:

همان طور که در قطعه کد فوق می‌بینید، 13 div ایجاد کرده‌ایم که هر کدام یک تصویر دارد و از سرویس عکس Unsplash واکشی می‌شود. div کانتینر کلاسی از نوع container. دارد. توجه کنید که برخی از فرزندان div کانتینر، دارای کلاس‌هایی مانند big ،.vertical. و horizontal. هستند. ما این div-ها را به طرز متفاوتی سبک‌بندی خواهیم کرد. اینک نوبت به استایل‌دهی شبکه تصاویر رسیده است.

ایجاد استایل برای شبکه تصاویر

در فایل main.css استایل‌هایی برای شبکه تصاویر خود ایجاد می‌کنیم و کار خود را با کلاس container آغاز می‌کنیم.

در ادامه به اختصار در مورد کارکرد هر یک از مشخصه‌های فوق صحبت می‌کنیم.

توضیح مشخصه‌های استایل

برای این که با هر کانتینر مانند یک کانتینر شبکه رفتار شود، باید نوع display به صورت grid و یا grid-inline برای شبکه‌های درون‌خطی تعریف شده باشد. مشخصه grid-template-columns به تعریف ستون‌هایی از کانتینر شبکه می‌پردازد. شما می‌توانید عرض ستون را با استفاده از یک کلیدواژه مانند auto-fit یا یک طول مانند 50px تعریف کنید. در مورد مثال فوق ما مقدار grid-template-columns را درون یک متد ()repeat تعریف می‌کنیم.

متد repeat نشان دهنده یک فرگمان تکراری از یک tracklist است. بنابراین یک مقدار مانند (repeat(3، 80px سه ستون ایجاد می‌کند که هر یک عرضی برابر با 80 پیکسل دارند. کلیدواژه auto-fit به مدیریت اندازه‌های ستون می‌پردازد. بدین ترتیب می‌توانیم بیشتری تعداد ممکن ستون‌ها را در ردیفی با طول مفروض قرار دهیم. برای نمونه یک مقدار grid-template-columns به صورت (repeat(auto-fit، 100px بیشترین تعداد ستون‌هایی که در div-های کانتینر شبکه وجود دارند با تنظیمات عرض 80 پیکسل تولید می‌کند. در نهایت تابع minmax به تعریف کمینه و بیشینه عرض هر ستون می‌پردازد. minmax برای ایجاد صفحه‌های واکنش‌گرا بسیار مفید است.

The grid-auto-rows اندازه یک ردیف شبکه را که به صورت صریح ایجاد شده است تعیین می‌کند. بنابراین بر اساس قطعه کد CSS فوق این بدان معنی است که هر div که در کانتینر شبکه داریم ارتفاعی برابر با 200 پیکسل خواهد داشت.

grid-gap اندازه فاصله بین ستون‌ها و ردیف‌ها را تعیین می‌کند. در مثال مورد بررسی، grid-gap آن مقدار 5 پیکسل هم برای فاصله بین ستون‌ها و هم بین ردیف‌ها است.

مشخصه grid-auto-flow به کنترل طرز کار الگوریتم auto-placement می‌پردازد و دقیقاً تعیین می‌کند که آیتم‌های با جایگذاری خودکار چگونه در شبکه جابجا می‌شوند. در مثال مورد بررسی، ما از الگوریتم بسته‌بندی dense استفاده کرده‌ایم که تلاش می‌کند آیتم‌های کوچکی را که در ادامه می‌آیند، ابتدا در جاهای خالی شبکه پر کند. کامنت کردن آن خط موجب بروز برخی فضاهای خالی در شبکه ما خواهد شد.

تکمیل کد شبکه تصاویر

همان طور که می‌بینید توضیح‌های فوق برای چند خط کد کمی زیاد محسوب می‌شوند، اما خوشبختانه شما اینک با فرایند کار به خوبی آشنا شده‌اید. اکنون باید پا را فراتر گذاشته و شبکه تصاویر را تکمیل کنیم. ابتدا باید مطمئن شویم که همه تصاویر به طور صحیحی در div-ها قرار می‌گیرند. به این منظور کد زیر را در فایل CSS پس از کلاس container. قرار دهید.

این کد به تعیین عرض و ارتفاع همه تصاویر در شبکه بر اساس 100% کانتینرهایشان می‌پردازد. در نهایت به استایل‌دهی div-ها با کلاس‌های verical ،.horizontal. و .big می‌پردازیم.

در این بخش به صحبت در مورد مشخصه‌های CSS در قطعه کد فوق می‌پردازیم.

مشخصه CSS به نام grid-column یک مشخصه اختصاری برای grid-column-start و grid-column-end است که اندازه شبکه و موقعیت درون شبکه را تعیین می‌کند. کلیدواژه span تعداد ردیف‌ها یا ستون‌هایی که یک grid-column یا grid-row باید پوشش دهد تعیین می‌کند.

در مثال فوق، برای این که طول برخی تصاویر دو برابر بزرگ‌تر باشد، مقدار grid-column را برای کلاس horizontal. برابر با span 2 و برای کلاس vertical. نیز برابر با span 2 تعیین می‌کنیم تا ارتفاع برخی تصاویر دو برابر از بقیه باشد. div-های دارای کلاس big. در هر دو گستره ردیف و ستون اندازه‌ای دو برابر معمول دارند. اینک فایل index.html را در یک مرورگر باز کنید و خروجی را مشاهده کنید.

اگر علاقه‌مند هستید کد کامل را بینید، می‌توانید به فایل‌های زیر رجوع کنید:

فایل index.html

فایل main.css

نسخه نهایی باید چیزی مانند تصویر زیر باشد:

CSS Grid Layout

توجه داشته باشید که CSS Grid Layout مشخصه‌های زیاد دیگری دارد که احتمالاً مورد توجه شما قرار خواهند گرفت. بنابراین می‌توانید بررسی آن را با مطالعه مستندات (+) آغاز کنید.

ا

منبع: فرادرس


گزاره break و continue در ++C — راهنمای کاربردی

دو گزاره به نام گزاره break و continue در زبان برنامه‌نویسی ++C وجود دارند که به طور خاص برای تغییر در گردش نرمال یک برنامه استفاده می‌شوند. در برخی موارد می‌خواهیم که اجرای یک حلقه برای یک شرایط تست خاص رد شود و یا بدون بررسی شرط حلقه بی‌درنگ خاتمه یابد. برای مطالعه بخش قبلی این سری مقالات آموزشی به لینک زیر مراجعه کنید:

حلقه while و do…while در ++C — راهنمای کاربردی

برای نمونه ممکن است بخواهیم روی داده‌های افرادی با سنین مختلف به جز سنین بالاتر از 65 حلقه‌ای تعریف کنیم. همچنین ممکن است بخواهیم نخستین فردی که 20 سال سن دارد را بیابیم. در چنین مواردی از گزاره‌های ;continue و ;break استفاده می‌کنیم.

گزاره break در ++C

گزاره break در ++C موجب خاتمه بی‌درنگ یک حلقه می‌شود. این حلقه می‌تواند هر نوعی از قبیل for ،while و do..while و همچنین گزاره‌ی switch شود.

ساختار break

در استفاده‌های عملی گزاره break تقریباً همواره درون بدنه یک گزاره شرطی یعنی if…else در حلقه استفاده می‌شود.

طرز کار گزاره break چگونه است؟

گزاره break

مثال 1: break در ++C

برنامه ++C برای افزودن همه اعداد وارد شده از سوی کاربر تا زمانی که کاربر عدد 0 وارد نماید:

خروجی

Enter a number: 4
Enter a number: 3.4
Enter a number: 6.7
Enter a number: -4.5
Enter a number: 0
Sum = 9.6

در برنامه فوق، عبارت تست همواره صحیح است. از کاربر تقاضا می‌شود که عدد دیگری را وارد کند هنگامی که کاربر مقدار 0 وارد می‌کند، عبارت تست درون گزاره if نادرست است و بدنه else اجرا می‌شود که موجب خاتمه حلقه می‌شود. در نهایت مجموع نمایش پیدا می‌کند.

گزاره continue در ++C

در برخی موارد ضروری است که از شرایط تست خاصی درون یک حلقه رد شویم. در چنین مواردی گزاره continue در زبان برنامه‌نویسی ++C استفاده می‌شود.

ساختار continue

در عمل گزاره ;continue تقریباً همیشه درون یک گزاره شرطی استفاده می‌شود.

کار با گزاره continue

گزاره break

مثال 2: گزاره continue در ++C

برنامه ++C برای نمایش عدد صحیح از 1 تا 10 به جز 6 و 9.

خروجی

1 2 3 4 5 7 8 10

در برنامه فوق، زمانی که i برابر با 6 یا 9 باشد، اجرای گزاره زیر درون حلقه با استفاده از گزاره ;Continue رد می‌شود:

cout << i << "\t";

بدین ترتیب به پایان بخش دیگری از مطالب راهنمای مفاهیم برنامه‌نویسی زبان ++C می‌پردازیم. برای مشاهده بخش بعدی این سری مطالب به لینک زیر رجوع کنید:

آموزش سوئیفت (Swift): کاربرد Enum با ژنریک و بستار – بخش پانزدهم

در این بخش از سری مقالات آموزش برنامه‌نویسی سوئیفت قصد داریم به صورت فشرده برخی از مفاهیم مهم این زبان برنامه‌نویسی شامل استفاده از Enum به همراه ژنریک و بستارها را با هم ترکیب کنیم و با روش عملی استفاده از آن‌ها در کدنویسی آشنا شویم.

در بخش قبلی با مبانی روش‌های ایجاد خطاهای سفارشی و استفاده از آن‌ها در کد برای جلوگیری از کرش کردن برنامه آشنا شدیم. برای مطالعه بخش قبلی می‌توانید به لینک زیر مراجعه کنید:

Enum به همراه ژنریک و بستار

در بخش‌های قبلی این سری آموزشی در مورد Enum-ها صحبت کردیم و گفتم که Enum گزینه‌های مختلفی در اختیار شما قرار می‌دهد که می‌توانید از میان آن‌ها انتخاب کنید و به نوعی حالت‌های مختلف را منحصر به آن گزینه‌ها بکنید. ما می‌توانیم از مقادیر متناظر با حالت‌های Enum برای تعریف کردن نوعی که در زمان استفاده از Enum وهله‌سازی خواهد شد استفاده کنیم.

چنان که می‌بینید امکان بسیار جالبی است. اگر ندانیم چه نوعی وارد خواهد شد، می‌توانیم از ژنریک استفاده کنیم.

بدین ترتیب می‌توانیم در زمان ایجاد یک address یا coordinate از هر نوع که می‌خواهیم، استفاده کنیم.

Enum

اکنون به بررسی روش استفاده ترکیبی از Enum به همراه ژنریک و بستار می‌پردازیم. این ترفند جالبی است که برنامه‌نویسان حرفه‌ای از آن در کدهای خود استفاده می‌کنند.

البته اگر بگوییم درک طرز کار این روش آسان است، دروغ گفته‌ایم. حتی با وجود تورفتگی‌ها، می‌بینیم که درک کد فوق دشوار است. اما جای نگرانی نیست، زیرا در ادامه، همه این موارد را جزء به جزء توضیح می‌دهیم.

Struct Download

این Struct منطق ما را نگهداری می‌کند. این Struct می‌تواند یک کلاس باشد، زیرا وظیفه اجرای فراخوانی‌های شبکه را بر عهده دارد.

بدین ترتیب دو گزینه در اختیار ما قرار می‌گیرد که یکی (success(anything. و دیگری (failure(someError. است.

این متدی است که یک تابع می‌گیرد. آن تابع یک حالت را از Enum به نام Result می‌گیرد و چیزی هم بازگشت نمی‌دهد.

let session

این دستور یک «نشست» (Session) از URLSession با یک پیکربندی ephemeral می‌سازد. منظور از ephemeral این است که تنها در حافظه وجود دارد و به عبارتی معادل مرور خصوصی وب است.

let url

این دستور یک URL از رشته‌ای که به آن ارسال کرده‌ایم، می‌سازد. این رشته می‌تواند هر صفحه وبی که می‌خواهید از آن دانلود کنید باشد.

کد فوق وظیفه‌ای در اختیار ما قرار می‌دهد که با آن می‌توانیم داده‌های مورد نظر خود را دانلود کنیم. آن را می‌توان مانند اسبی تصور کرد که می‌توانیم آن را به هر کجا که می‌خواهیم برانیم. Data شامل داده‌های باینری (0 و 1) است که دریافت می‌کنیم. response هدرهای پاسخی است که دریافت می‌شود و در ادامه در مورد آن بیشتر توضیح می‌دهیم.

error در صورت ناموفق بودن درخواست بازگشت می‌یابد و درک این نکته مهم است. چون در صورت دریافت یک خطای 404 (صفحه یافت نشد) در فراخوانی، می‌توانید اطلاعات مربوطه را از response دریافت کنید. حتی اگر خطای 500 دریافت شود که به معنی ناموفق بودن چیزی در سرور است همچنان می‌توان آن را در response مشاهده کرد. error برای ما به این معنی است که نتوانسته‌ایم آن کاری را که می‌خواستیم اجرا کنیم و حتی درخواست را مقداردهی کنیم. بنابراین error به معنی خطای ما و نه خطای دیگران است.

این یک بررسی قبل از اجرا محسوب می‌شود. اگر خطایی دریافت شود، دیگر نیازی به اجرا نخواهد بود و کافی است با بازگشت خطا از همین‌جا خارج شویم.

ما قبلاً در مورد DispatchQueue.main.async صحبت کرده‌ایم، بنابراین در اینجا می‌خواهیم فقط کد زیر را توضیح دهیم:

اعتبارزدایی

این کد اقدام به اعتبارزدایی و لغو می‌کند، یعنی هر کاری که انجام می‌دادید را متوقف کرده و session را پاک می‌کند، چون دیگر نیازی به آن نداریم. اما اگر بخواهیم defer را توضیح دهیم، باید بگوییم که defer برای اجرا کردن یک قطعه کد استفاده می‌شود و مهم نیست که چه کاری انجام می‌یابد، صرفاً باید قبل از اجرا شدن، تا زمانی که متد پایان می‌یابد صبر کند. در واقع شبیه به یک deinit برای متدها، تابع‌ها و بستارها است.

سپس از (!completion(.failure(error استفاده می‌کنیم. completion از نام پارامتر در start می‌آید. failure. حالتی از Enum با نام Result و !error خطای به اجبار باز شده است که از بستار دریافت شده است. در این موقعیت این به‎کارگیری اجبار، کار درستی محسوب می‌شود، چون قبلاً تهی نبودن آن را بررسی کرده‌ایم و از آنجا که این کد اجرا می‌شود، به این معنی است که تهی نبوده است. در ادامه بررسی‌های دیگری را نیز اجرا می‌کنیم.

دسترسی مستقیم به حالت

متأسفانه سوئیفت دسترسی مستقیم به «حالت» (State) کد ایجاد نمی‌کند؛ اما HTTPURLResponse چنین امکانی در اختیار ما قرار می‌دهد و می‌توانیم نوع پاسخ را به یک HTTPURLResponse تغییر دهیم و باید موفق باشد. در این حالت بی‌درنگ بررسی می‌کنیم که آیا پاسخ موفقی به صورت زیر داریم یا نه:

اگر هر دوی آن‌ها درست باشند، در این صورت می‌توانیم داده‌ها را به صورت امنی باز پس بفرستیم تا تجزیه شوند و یا هر کار دیگری که قصد انجام آن وجود دارد اجرا شود. ابتدا با استفاده از DispatchQueue.main.async مطمئن می‌شویم که این کار را روی صف اصلی انجام می‌دهیم و سپس از دستگیره completion استفاده می‌کنیم تا این کار را با ((!completion(.success(data به صورت باز کردن اجباری داده‌ها اجرا کنیم، چون هر سه پارامتر بستار، مقادیر غیر optimal هستند.

در انتهای تابع Start اقدام به فراخوانی ()task.resume می‌کنیم که وظیفه داده را اجرا می‌کند. زمانی که این فراخوانی پایان یافت، همه آن کد را که قبلاً بررسی کردیم اجرا می‌کنیم.

برای این که متوجه شوید همه این موارد در سمت دیگر که Start را فراخوانی می‌کنیم، چه طور به نظر می‌رسند، می‌توانید کد زیر را ملاحظه کنید:

سخن پایانی

بدین ترتیب در این مقاله با ارائه یک مثال با روش اجرای یک فراخوانی شبکه آشنا شدیم. روش استفاده از قدرت Enum-ها به همراه تابع‌ها و ژنریک ها برای کمک به بازگشت بستار نمایش یافت. همچنین نگاهی به escaping@ داشتیم و با طرز استفاده از آن بیشتر آشنا شدیم.

این راه‌حل شبکه یک راه‌حل بهینه نیست و صرفاً یکی از راه‌حل‌های ممکن محسوب می‌شود. روش‌های مختلفی برای اجرای این کار وجود دارد و بسته به شیوه استفاده از بستارها در Enum-ها ممکن است مسیرهای متفاوتی ایجاد شود.

با این که ممکن است در نگاه نخست کمی دشوار به نظر برسد، اما اگر چند بار آن را تمرین کنید با طرز کار آن آشنا می‌شوید. البته امکان تکمیل خودکار کد نیز در صورتی که به آن توجه داشته باشید کمک زیادی به این فرایند یادگیری می‌کند.

ما تا به این جا صحبت‌های زیادی در مورد انواعِ مقداری داشتیم. سوئیفت عاشق انواعِ مقداری خود است؛ اما نوع دیگری از داده‌ها به نام انواعِ ارجاعی نیز وجود دارند انواع ارجاعی فریبنده هستند و در صورتی که به طرز صحیحی استفاده نشوند می‌توانند خطرناک باشند. در بخش بعدی در مورد inout ،Lazy و Getters و Setters صحبت خواهیم کرد. inout به طور کامل در مورد ارجاع‌ها است، Lazy به ارتقای عملکرد کد کمک می‌کند و getters و setters موجب تغییر در شیوه دسترسی به داده‌ها می‌شوند. موارد فوق در موقعیت‌های مختلف برنامه‌نویسی مفید هستند. برای مطالعه بخش بعدی این نوشته به لینک زیر رجوع کنید: