در این مقاله نگاهی به شیوه ساخت انیمیشنهای اسکرول سفارشی از صفر تا صد با استفاده از SDK فلاتر خواهیم داشت. فلاتر یک ابزار پرقدرت برای ایجاد اپلیکیشنهای نیتیو موبایل است که به صورتی عالی اجرا میشوند و انعطافپذیری خارقالعادهای در زمینه خلق تجربیات کاربری غنی فراهم میسازد.

اگر به محیط فلاتر دسترسی ندارید به صفحه نصب (+) آن بروید.
در دمویی که در این مقاله میخواهیم بسازیم یک پروژه پیشفرض با استفاده از دستور flutter create ایجاد میکنیم و تنها از کلاسهایی استفاده میکنیم که مستقیماً درون فلاتر وجود دارند و لذا نیاز به افزودن هیچ وابستگی به پروژه نداریم. در اغلب موارد، یک وظیفه (مانند انیمیشن سفارشی) میتواند مستقیماً و بدون نیاز به کتابخانههای اضافی اجرا شود.
ایده اصلی این اپلیکیشن نمونه، ایجاد یک نمای لیستی ساده با چند آیتم است و زمانی که این نما به سمت پایین اسکرول میشود، در پسزمینه یک چرخدنده در جهتهای ساعتگرد یا پادساعتگرد میچرخد. این کار با کمک گرفتن از کنترلر اسکرول روی نمای لیستی و ارسال یک پسزمینه انیمیشنی به آن صورت میگیرد که یک ویجت سفارشی میسازد که همگام با موقعیت اسکرول به چرخش درمیآید.
کار خود را با فایل اصلی اپلیکیشن یعنی lib/main.dart آغاز میکنیم:
در فایل main.dart، برخی دستورهای ایمپورت برای کامپوننتهای زیر وجود دارند:
کلاس اپلیکیشن اصلی (AnimationDemo) یک اپلیکیشن ابتدایی میسازد که ویجت صفحه اصلی پیشفرض (MyHomePage) را پوشش میدهد. در کلاس MyHomePage یک مشخصه به نام controller_ وجود دارد که به یک وهله جدید از کلاس ScrollController مقداردهی میشود تا در ادامه به هر دو کامپوننت AnimatedBackground و ListView ارسال شود. کامپوننت AnimatedBackground به چرخاندن چرخ رنده در پسزمینه میپردازد و ListView نیز لیست اسکرول شونده از آیتمهای دمو را رندر میکند. یک مشخصه به نام cards_ نیز وجود دارد که با لیستی از اشیای Item آغاز میشود که از items.dart ایمپورت شدهاند و لیستی از ویجتهای DemoCard برای رندر شدن در ListView بازگشت میدهد.
نخستین فایل ایمپورت شده که به بررسی آن میپردازیم فایل lib/items.dart است:
کلاس Item ساختمان دادهای را ارائه میکند که همراه با وهلهای از DemoCard ارسال میشود تا در ListView رندر شود. در این مورد چیز خاصی زیادی برای تنظیم به جز یک نام، توضیح (که در حال حاضر در دمو بیاستفاده است)، رنگ و آیکون برای رندر شدن هر آیتم وجود ندارد. لیستی از اشیای آیتمها به عنوان لیستی از محتوای ساده ارائه میشود که درون ListView رندر میشوند.
فایل بعدی که بررسی میکنیم lib/demo-card.dart نام دارد:
کلاس DemoCard در سازنده خود یک آیتم دریافت میکند و یک ویجت کارت بازگشت میدهد که عناصر Text و Icon را رندر میکند که به نمایش نام و آیکون تعریفشده برای هر آیتم میپردازند. برخی استایلبندیهای ساده نیز با استفاده از Shadow ،TextStyle و RoundedRectangleBorder همراه با ارتفاع کارت (که روی 3 تنظیمشده) اعمال میشوند. ویجتهای Column و Row برای گسترش عناصر فرزند روی کارت استفاده میشوند.
ما بهترین بخش کار را برای انتها نگه داشتهایم. بنابراین در این بخش به بررسی فایل lib/animated-bg.dart میپردازیم:
در همان ابتدای فایل، کتابخانه dart:math ایمپورت شده تا به ثابت π دسترسی پیدا کنیم که برای اجرای محاسبات تبدیل چرخشی برای گردش چرخدنده مورد نیاز است. سازنده کلاس AnimatedBackground یک مقدار ScrollController میگیرد که اقدام به اجرای چرخش میکند. مشخصه offset_ در صورتی که کنترلر کلاینتهایی داشته باشد یعنی کنترلر به درستی مقداردهی شده باشد و به یک عنصر اسکرول شنونده واقعی مانند ListView قلاب شده باشد یک آفست بازگشت میدهد و در غیر این صورت مقدار صفر بازمیگرداند. متد build یک AnimatedBuilder بازگشت میدهد که کنترلر را میگیرد و یک OverflowBox میسازد که همراستا با چرخدنده و خارج از صفحه جای میگیرد.
مقادیر 4 و 3 موجب میشوند که چرخدنده در گوشه پایین-چپ دستگاه تست که یک شبیهساز آیفون XR است، قرار گیرند. در عمل مقادیر Alignment باید از مختصات عرض و ارتفاع صفحه دستگاه محاسبه شوند تا مقادیر دقیقی برای قرار گرفتن چرخدنده در موقعیت مطلوب در هر دستگاه به دست آید. با این حال ما در این مثال تلاش کردهایم همه چیز تا حد امکان ساده باشد.
آخرین بخش جایی است که خود انیمیشن در آن اتفاق میافتد و این همان متد استاتیک rotate در کلاس است. این کلاس یک مقدار angle و یک child میگیرد و فرزند به وسیله زاویه مورد نظر به چرخش درمیآید. در این دمو ما میخواهیم که چرخدنده به آرامی حرکت کند و بیشتر به فیزیک واقعی شبیه باشد تا چرخش سریع دیوانهوار که در زمان تعیین مقدار چرخش 1:1 به دست میآید. همچنین میخواهیم چرخدنده در جهت پادساعتگرد بچرخد، گرچه از نظر فیزیکی لیست را به حرکت درمیآورد بنابراین offset را در math.pi ضرب کرده و سپس حاصلضرب را بر 1024- تقسیم میکنیم.
این مقاله و پروژه نمونه صرفاً برخی از قابلیتهایی که فلاتر برای ایجاد تجربیات کاربری متحرک سفارشی ارائه میکنند را نشان دادند. ما با استفاده از تنها چند کلاس ابتدایی و مسیر ساده، یک انیمیشن کامل داریم که یک عنصر کاملاً ملالآور مانند صفحه تنظیمات را به چیزی کاملاً جذاب تبدیل میکند.
این مفاهیم میتوانند برای ایجاد «صفحههای آغازین» (splash screens)، انیمیشنهای بارگذاری، گذار صفحه، جلوههای نوتیفیکیشن یا هر چیز دیگری مورد استفاده قرار گیرند. تقریباً هر چیزی که یک مقدار Double به عنوان آرگومان میگیرد میتواند انیمیت شود و در نتیجه پیادهسازی سرراستی از جلوههای مختلف مانند چرخش در این مثال به دست میآید. همچنین میزان شفافیت، موقعیت و بسیاری مشخصههای دیگر نیز قابل تنظیم هستند.
منبع: فرادرس
فتگوی زنده یک روش پشتیبانی از مشتری است که سابقهای طولانی دارد. این روش سریع و کارآمد است چون در آن هر کارمند میتواند همزمان به مشتریان زیادی کمک بکند. بهترین نکته این است که میتوان در طی فرایند خرید به سریعترین روش ممکن به سؤالهای مشتری پاسخ داد و بدین ترتیب احتمال خرید مشتری بالاتر میرود.
در این مقاله به روش یکپارچهسازی یک گفتگوی زنده در اپلیکیشنهای React میپردازیم. قصد ما این است که شیوه یکپارچه قابلیت گفتگوی زنده در اپلیکیشن ریاکت را بدون نگرانی از نگهداری سرور گفتگو و معماری آن پیادهسازی کنیم. در ادامه تصویری از آن چه قصد داریم بسازیم را مشاهده میکنید.
ما برای راهاندازی بخش گفتگوی اپلیکیشن خود از خدمات CometChat Pro استفاده میکنیم. CometChat Pro یک API ارتباطی قدرتمند است که امکان افزودن قابلیتهای گفتگو را به اپلیکیشن فراهم میسازد. این API با قابلیت یکپارچهسازی آسان و مستندات منظم به شما کمک میکند تا ویژگی گفتگوی زنده را با نوشتن چند خط کد به اپلیکیشن خود اضافه کنید. به این منظور ابتدا باید یک حساب رایگان در این وبسایت (+) بسازید.
ما در این راهنما علاوه بر CometChat از فناوریهای زیر نیز استفاده خواهیم کرد:
پیشنهاد ما این است که تا انتهای این راهنما همراه باشید تا گامبهگام اپلیکیشن مورد نظر خود را بسازیم، اما اگر بیش از این شتاب دارید، میتوانید کد کامل این مقاله را در این صفحه گیتهاب (+) ملاحظه کنید.
چنان که پیشتر گفتیم برای گنجاندن قابلیت گفتگوی زنده در اپلیکیشن از CometChat استفاده میکنیم. با این وجود، پیش از یکپارچهسازی باید ابتدا اپلیکیشن CometChat را بسازیم.
برای ایجاد یک اپلیکیشن CometChat باید به داشبورد CometChat بروید و روی آیکون + کلیک کنید. ما اپلیکیشن خود را react-chat-widget مینامیم، اما شما میتوانید اپلیکیشن خود را با هر نامی که دوست دارید ایجاد کنید.
ما دو نوع کاربر داریم که به گفتگوی ما اتصال مییابند. یک دسته مشتریهایی هستند که ویجت گفتگو را باز میکنند و دیگری کارمندان بخش پشتیبانی هستند که به گفتگو دسترسی مییابند و از طریق داشبورد به پرسوجوها پاسخ میدهند. به طور کلی «کاربر» مفهومی بنیادی در CometChat محسوب میشود.
از آنجا که ما احتمالاً مشتریان زیادی خواهیم داشت، برای هر مشتری که به گفتگو وصل میشود، باید به صورت دینامیک یک کاربر CometChat ایجاد کنیم. با این وجود از آنجا که تنها یک کارمند وجود خواهد داشت، میتوانیم یک Agent را از پیش در داشبورد ایجاد کنیم.
به این منظور در داشبورد به برگه Users بروید و روی Create User کلیک کنید:
برای ID کاربر مقدار ecommerce-agent را وارد کنید و برای نام کارمند نیز مقدار Demo Agent را وارد کنید. اگر قصد دارید از مراحل این راهنما پیروی کنید، از همین نامها استفاده کنید، چون در غیر این صورت در ادامه با مشکل مواجه خواهید شد. ID کاربر را یادداشت کنید، زیرا در ادامه به آن ارجاع خواهیم داد.
پیش از آن که از داشبورد خارج شوید و به کدنویسی بپردازید، باید یک کلید دسترسی کامل CometChat نیز بسازید. در همین صفحه روی برگه API Keys و سپس روی Create API Key کلیک کنید:
ما کلید خود را react-chat-api نامیدهایم، اما نام آن اهمیت چندانی ندارد. کلید API و ID اپلیکیشن خود را نیز مانند ID کاربر در جایی یادداشت کنید، چون در ادامه لازم خواهد شد.
در گام قبلی یک کلید دسترسی کامل ساختیم که میتوانیم از آن برای ایجاد دینامیک CometChat استفاده کنیم. با این که میتوانیم این کار را در سمت کلاینت نیز اجرا کنیم، اما معنی آن این خواهد بود که کلید دسترسی کامل خصوصی خود را در معرض دسترس عموم قرار میدهیم که کار نادرستی است. برای جلوگیری از این مشکل یک سرور اکسپرس میسازیم که شرایط زیر را داشته باشد:
اینک نوبت آغاز کار است. ابتدا یک دایرکتوری خالی جدید برای اپلیکیشن اکسپرس خود ایجاد میکنیم و دستور npm init -y را اجرا میکنیم:
mkdir react-express-chat-widgetcd react-express-chat-widgetnpm init –y
سپس اکسپرس و axios را نصب میکنیم:
npm install express axios
سپس در فایلی به نام server.js کد زیر را وارد میکنیم:
در فایل فوق موارد زیر وجود دارند:
در همین فایل اکنون یک مسیر تعریف میکنیم تا ایجاد کاربران جدید CometChat را مدیریت کنیم. برای ایجاد یک کاربر جدید باید یک درخواست POST را با UID و نام کاربر ارسال کنیم.
در این راهنما، نام یکسانی را برای همه مشتریان به صورت hard-code مینویسیم، یعنی همه مشتریها را «customer» مینامیم، اما UID آنها باید یکتا باشد. برای UID میتوانیم از تابع POST برای ایجاد ID-های یکتا استفاده کنیم.
کد زیر را به فایل server.js اضافه کنید:
زمانی که این مسیر فراخوانی شود، اکسپرس کارهای زیر را انجام میدهد:
ما همچنین یک تابع به نام requestAuthToken میسازیم تا به واکشی کردن توکن احراز هویت کمک کند. سپس در همان فایل یک مسیر احراز هویت میسازیم که آن را برای ایجاد توکن جهت کاربران بازگشتی فراخوانی خواهیم کرد:
در نهایت یک تابع برای بازگشت لیستی از کاربران ایجاد کرده و agent یا همان کارمند پشتیبانی را از آن حذف میکنیم. ما این endpoint را متعاقباً از داشبورد فراخوانی میکنیم تا لیستی از کاربران را که agent میتواند با آنها صحبت کند نمایش دهیم. البته کارمند ما نمیتواند با خودش صحبت کند و از این رو خودش را از لیست فیلتر میکنیم:
در انتهای فایل server.js، سرور را اجرا میکنیم:
اگر از ابتدای این مقاله با ما همگام بوده باشید، اینک فایل Server.js باید به صورت زیر در آمده باشد:
در یک پنجره ترمینال دستور node server.js را اجرا کنید و منتظر باشید تا پیامی به صورت زیر نمایش یابد:
Listening on port 5000
اکنون باید زمان مناسبی برای تست endpoint-ها به همراه curl یا Postman باشد تا مطمئن شویم که کار میکنند و سپس به بخش کدنویسی کلاینت بپردازیم.
درون دایرکتوری خود دستور npx create-react-app را اجرا کنید تا ساختار اولیه یک اپلیکیشن ریاکت ایجاد شود:
npx create-react-app client
ساختار پوشه شما اینک باید مانند زیر باشد:
|-- express-react-chat-widget |-- package-lock.json |-- package.json |-- server.js |-- client |--.gitignore |-- package-lock.json |-- package.json |-- public |-- src
زمانی که اپلیکیشن ریاکت آماده شد، به دایرکتوری client بروید و ماژولهای زیر را نصب کنید:
cd clientnpm install @cometchat-pro/chat react-chat-widget react-router-dom bootstrap react-md-spinner
اپلیکیشن Create React برای bootstrap کردن یک اپلیکیشن ریاکت کاملاً مفید است، اما فایلهای زیادی تولید میکند که مورد نیاز ما نیستند و اینها شامل فایلهای تست و از این دست هستند. پیش از اقدام به کدنویسی، همه چیز را از دایرکتوری client/src حذف کنید تا از صفر کار خود را شروع کنیم. برای شروع یک فایل config.js با ID اپلیکیشن و UID کارمند در مسیر client/src/config.js با محتوای زیر بسازید:
این کد مبنایی است که میتوان برای ارجاع به اطلاعات احراز هویت CometChat از هر کجا مورد استفاده قرار داد. با این که ما آن را کد مبنا مینامیم، اما این فرصت را نیز داریم که یک فایل index.css بسازیم:
ما این فایل را در ادامه از داشبورد مورد ارجاع قرار میدهیم. اکنون در فایل با نام index.js کد زیر را درج کنید:
در این کد ما Bootstrap ،CometChat و فایل پیکربندی را که پیش از مقداردهی اولیه CometChat و رندر کردن App ساختهایم ایمپورت میکنیم. اگر در این راهنما با ما همگام بوده باشید، متوجه شدهاید که ما هنوز App را تعریف نکردهایم. بنابراین در این مرحله این کار را انجام میدهیم. در فایل به نام App.js کد زیر را درج کنید:
در این کد ما دو مسیر را تعریف کردیم:
در ادامه ابتدا کامپوننت در دید مشتری را بررسی میکنیم. ما آن را کامپوننت کلاینت مینامیم.
کامپوننت کلاینت ما دو مسئولیت عمده خواهد داشت:
یک فایل به نام Client.js بسازید و کد زیر را در آن درج کنید:
اگر فکر میکنید این کد حجم بالایی دارد جای نگرانی نیست چون در ادامه آن را جزء به جزء توضیح میدهیم.
تابع render به قدر کافی ساده است، در واقع وظیفه اصلی آن رندر کردن react-chat-widget است. بخش زیادی از کد اختصاص به مدیریت پیام جدید ارسالی از سوی مشتری در تابعی به نام handleNewUserMessage دارد.
به طور خلاصه، ابتدا باید بررسی کنیم که آیا UID مشتری در localStorage وجود دارد یا نه. اگر چنین باشد، از این UID برای لاگین کردن کاربر و ارسال پیام استفاده میکنیم. در غیر این صورت تابع ()createUser را فراخوانی میکنیم و از مقدار بازگشتی برای لاگین کردن کاربر استفاده میکنیم. این تابع createUser یک endpoint را فراخوانی میکند که قبلاً در همین راهنما تعریف کردیم.
در نهایت در یک تابع چرخه عمر ریاکت componentWillUnmount را فرا میخوانیم و به خاطر میسپاریم که شنونده پیام را حذف کنیم. پیش از ادامه باید به یک نکته کوچک اشاره کنیم. در کد فوق، به جای وارد کردن URL سرور و پورت آن (localhost:5000/users) یا چیزی مانند آن در فرانتاند، میتوانیم یک گزینه proxy به فایل package.json اضافه کنیم. بدین ترتیب میتوانیم به جای localhost:5000/users// صرفاً از /users استفاده کنیم:
"browserslist": [">0.2%"، "not dead"، "not ie <= 11"، "not op_mini all"]،"proxy": http://localhost:5000
در این مرحله اپلیکیشن مانند زیر خواهد بود:
چنان که مشاهده میکنید، میتوان پیامها را ارسال یا دریافت کرد، اما اگر صفحه را رفرش کنیم، پیامهای گفتگو ناپدید میشوند و این خوب نیست.
برای حل این مشکل یک متد componentDidMount تنظیم میکنیم که به دنبال UID مشتری در localStorage میگردد، به طوری که وقتی مشتریان صفحه را رفرش بکنند، میتوانند گفتگو را از همان جایی که مانده بود ادامه دهند.
زمانی که UID را پیدا کردیم، میتوانیم از آن برای مقداردهی اولیه یک زنجیره از متدها جهت login ،fetch کردن پیامهای قبلی و ایجاد listener برای پیامهای ورودی استفاده کنیم.
اکنون اگر صفحه را رفرش کنیم، اپلیکیشن تلاش خواهد کرد در CometChat لاگین کند و پیامهای قبلی را به صورت خودکار با گشتن به دنبال UID مشتری در localStorage بارگذاری کند و این وضعیت مناسبی محسوب میشود.
اما همچنان مشکل کوچکی وجود دارد. چنان که مشخص شد، هنوز راهی برای کارمند پشتیبانی وجود ندارد که به پیام مشتری پاسخ دهد. ما این مشکل را از طریق ساختن داشبورد کارمند حل میکنیم. در این حالت کارمند میتواند به پیامهای گفتگو که از سوی مشتریان میرسند پاسخ دهد. بدن ترتیب کار ما در فایل Client.js به پایان میرسد و در ادامه به ساخت فایل Agent.js میپردازیم.
کارکرد اصلی داشبورد agent دریافت همه مشتریان از CometChat Pro و نمایش تمام پیامهای ورودی از مشتریان جدید در لیست گفتگوی مشتریان کارمند است تا بتواند روی آنها کلیک کرده و پاسخ دهد. در واقع کارکرد اصلی بسیار شبیه به کلاینت است.
هنگام استفاده از CometChat بهراحتی میتوان چندین کارمند ایجاد کرد، اما برای این که همه چیز ساده بماند و درگیر مدیریت کاربر نشویم، در این راهنما از یک کارمند (agent) استفاده میکنیم که قبلاً آن را ایجاد کردهایم. یک کامپوننت به نام Agent.js ایجاد کنید و حالت اولیه آن را به صورت زیر تعیین کنید:
در همان فایل، یک متد به نام componentDidMount ایجاد کنید:
در کد فوق کارهای زیادی انجام مییابند. در ادامه آنها را به اختصار مرور میکنیم.
احتمالاً به خاطر دارید که API اکسپرس را قبلاً برای دریافت لیستی از کاربران ثبت نام کرده ساختیم. از این API برای تشکیل لیستی از کاربران در سمت چپ داشبورد استفاده میکنیم. این لیست را با استفاده از ترکیب کلاسهای بوتاسترپ و فایل index.css که قبلاً تعریف کردیم، در سمت چپ داشبورد قرار میدهیم.
سپس یک تابع رندر میسازیم. این تابع اینترفیس گفتگو را رندر میکند و آن را با استفاده از بوتاسترپ استایلدهی میکند. برای این که خواندن کد آسانتر شود، CustomerList و ChatBox را در کامپوننتهای خاص خود قرار میدهیم که میتوانند در فایل یکسانی تعریف شوند:
بدین ترتیب مبنایی برای UI ایجاد میشود، اما هنوز امکان ارسال پیام وجود ندارد. برای ارسال پیامها باید یک دستگیره برای مواردی که کارمند یک پیام جدید را تحویل میدهد داشته باشیم. شیوه ارسال پیامها اینک باید برای شما روشن باشد، زیرا از همان فراخوانی sendMessage که در سمت کلاینت استفاده کردیم بهره بخواهیم گرفت.
همچنین میخواهیم کارمند را قادر سازیم تا سوابق پیامها را مانند حالتی که برای مشتری ایجاد کردیم، ببیند:
به خاطر داشته باشید که شنونده پیام را در زمان حذف کامپوننت پاک کنید:
محصول نهایی به صورت زیر خواهد بود:
اگر کنجکاو هستید که کاربران Superhero از کجا میآیند باید اشاره کنیم که این کاربران به صورت خودکار از سوی CometChat Pro در زمان ایجاد اپلیکیشن جدید ایجاد میشوند. پیش از استفاده عملیاتی از اپلیکیشن حتماً آنها را حذف کنید.
اکنون کارمند پشتیبانی و مشتریان آماده گفتگو با همدیگر هستند. شما میتوانید Client Home و Agent Dashboard را در پنجرههای جداگانهای باز کرده و این موضوع را بررسی کنید.
بدین ترتیب ما موفق شدیم ویجت گفتگوی زنده کاملاً اختصاصی خود را برای اپلیکیشن React در زمانی بسیار کوتاه بسازیم. در واقع، CometChat Pro به ما امکان میدهد که پیامها را با نوشتن تنها چند خط کد ارسال و دریافت کنیم. بدین ترتیب دیگر نیاز به راهاندازی سرور گفتگوی شخصی و معماری آن وجود ندارد. البته این سرویس قابلیتهای بسیار بیشتری از ساخت یک ویجت گفتگو دارد که بررسی همه آنها از حیطه این مقاله خارج است.
در این مقاله به توضیح روش اشتراک گذاری کد بین سیستمهای عامل اندروید و iOS با استفاده از کدنویسی در زبان ++C میپردازیم.

اغلب توسعهدهندگان حرفهای علاقهمند هستند که اپلیکیشنهای نیتیو بنویسند و به باور این دسته بهترین UX از طریق رابط کاربری روان و بومی به دست میآید. اگر چه غالباً ضروری است که لایه ارائه نیتیو باشد، به طور عکس منطق تجاری اپلیکیشن را میتوان به اشتراک گذاشت. زمانی که منطق تجاری اپلیکیشن به اشتراک گذارده میشود، میتوان کد آن را یک بار نوشت و از این رو هزینه نگهداری کاهش مییابد و امکان طراحی رفتار مشابهی بین سیستمهای عامل اندروید و iOS پدید میآید.
در اغلب پروژهها دستکم بخشی از کد هست که میتوان بین سیستمهای عامل مختلف به اشتراک گذاشت. این بخشها شامل منطق پایگاه داده، اعتبارسنجی کاربر و نظایر آن میشود. در اغلب این موارد، از ++C به عنوان یک زبان مشترک استفاده میشود. در این مقاله، ما با روش انجام این کار و مزایا و معایب استفاده از ++C برای توسعه اپلیکیشنهای موبایل آشنا میشویم.
سیستمهای عامل موبایل امروزه به دو دسته iOS و اندروید تقسیم میشوند.
++C در سیستم عامل iOS یک شهروند درجه یک محسوب میشود. اگر شما نیز این روزها همچنان به این زبان برنامهنویسی میکنید، میتوانید به سادگی با تغییر دادن فایل منبع از پسوند m. به nm. شروع به کدنویسی برای این سیستم عامل بکنید. البته این تغییر برخی ترفندهای جالب دیگر را نیز در سوئیفت در اختیار ما قرار میدهد. در سوی دیگر سوئیفت هیچ همکاری متقابلی با ++C ندارد. شما میتوانید تابعهای C و ++C را به هم مرتبط کنید؛ اما باید از یک پوشش ++Objective-C برای عرضه کلاسها بهره بگیرید.
اندروید نیز با استفاده از کیت توسعه نیتیو (NDK) میتواند از ++C در مواردی که به کدهای با عملکرد بالا نیاز هست یا لازم است کتابخانههایی بر مبنای کدهای موجود ساخته شود استفاده کند. اینترفیس نیتیو جاوا (JNI) شیوه تعامل بین این کد نیتیو و بایتکد نوشته شده در جاوا یا کاتلین را تعیین میکند. پس از راهاندازی زنجیره ابزار، این فرایند به سادگی تعریف کردن و استفاده از متدهای نیتیو در جاوا خواهد بود:
اعلان تابع در سمت پیادهسازی C نیز چنین است:
در روزهای آغازین اشتراک کد در ++C نوشتن چنین اعلانهای رمزآلودی، بخشی جداییناپذیر از فرایند توسعه محسوب میشد. خوشبختانه در سال 2014، Dropbox ابزاری به نام Djinni را منتشر ساخت که میتوانست هر دو اتصال Objective-C++ و JNI را تولید کند.
Djinni از یک زبان تعریف اینترفیس (IDL) ساده برای توصیف اینترفیسی که از سوی ++C عرضه میشود، استفاده کرده است. این IDL از رکوردها برای شیءهای مقدار خالص داده و از اینترفیسها برای اشیایی که در یکی از محیطهای ++C یا Objective-C/Java پیادهسازی میشوند بهره میگیرد.
Djinni از انواع مختلفی از داده از enum، بولی، عدد، رشته تا آرایه و نگاشت برای تعریف اینترفیس برای هر نوع اشتراک کدبیس پشتیبانی میکند و امکان پیادهسازی انواع داده سفارشی را نیز در صورت نیاز دارد. برای کسب اطلاعات بیشتر در مورد آن میتوانید به این صفحه (+) مراجعه کنید.
متأسفانه دراپباکس اخیراً اعلام کرده است که نگهداری از Djinni را متوقف میکند. اما این پروژه به قدر کافی بالغ و پایدار است که بتوان بر مبنای آن کدنویسی کرد و تصور نمیشود در طی زمان معقولی در آینده مشکل چندانی پیش بیاید.
هر پروژهای یک ریپازیتوری GIT دارد که شامل تعاریف اینترفیس و کد ++C اشتراکی است. متأسفانه Djinni تنها یک فایل IDL میپذیرد. از این رو باید برای هر IDL در ریپازیتوری یک فراخوانی به djinni/run داشته باشیم. در حال حاضر فرایند Make برای اندروید به صورت دستی آغاز میشود. روی iOS این فرایند به صورت یک Run Script Phase به فرایند build اضافه شده است.
روی هر دو پلتفرم پیادهسازی ++C به همراه فایلهای تولید شده مستقیماً به پروژه اضافه میشوند. همچنین یک کتابخانه استاتیک ساخته میشود، اما افزودن همه چیز به یک پروژه موجب میشود که فرایند توسعه و دیباگ کردن به مقدار زیادی آسانتر شود. روی اندروید، CMakeList را راهاندازی میکنیم که شامل همه فایلها در چند دایرکتوری است. اما روی iOS به صورت دستی فایلهای جدید را اضافه میکنیم.
در این بخش مراحلی را که برای افزودن یک قابلیت جدید به کدبیس اشتراکی نیاز داریم توضیح دادهایم. در این مثال، کار خود را از سمت Xcode/iOS آغاز میکنیم، اما شما میتوانید کار خود را از سمت اندروید نیز شروع کنید. ما میخواهیم فاصله بین دو رشته را محاسبه کنیم. این مسئله به نام «فاصله لوناشتاین» (Levenshtein Distance) مشهور است.
ابتدا باید یک فایل جدید IDL برای تعریف اینترفیس اضافه کنیم. Makefile ما انتظار دارد که یک فایل با پسوند.jinni دریافت کند. پیادهسازی آن کاملاً سرراست است. ما یک اینترفیس جدید تعریف میکنیم و c+ را نیز اضافه میکنیم تا به djinni اعلام کنیم که اینترفیس ++C را پیادهسازی خواهیم کرد. سپس یک متد استاتیک اضافه میکنیم که دو رشته را گرفته و یک عدد صحیح باز میگرداند.
پس از افزودن یک فایل جدید IDL پروژه خود را یک بار build میکنیم تا فایلهای bridging جدید تولید شوند. سپس آنها را به صورت دستی به پروژه Xcode اضافه میکنیم. برای این که فایلها در اختیار سوئیفت قرار گیرند، باید آنها را به هدر bridging نیز اضافه کنیم. چنان که پیشتر اشاره شد، باید Make را به صورت دستی فراخوانی کنیم تا فایلها را برای bridging مربوط به JNI اندروید تولید کند.
در نهایت میتوانیم شروع به پیادهسازی «منطق تجاری» (business logic) کوچک خود بکنیم. ابتدا یک فایل ++C میسازیم و آن را به پروژه Xcode اضافه میکنیم. در واقع ما صرفاً اعلان تابع را از هدر عمومی کپی کرده و به پیادهسازی حاصل از Rosettacode (+) اضافه کردیم.
کار به همین سادگی بود که شرح دادیم، ما فاصله را در ++C پیادهسازی کردیم و اینک میتوان آن را در Objective-C ،Swift ،Java یا Kotlin استفاده کرد.
آیا ++C زبان مناسبی برای نوشتن یک پروژه اپلیکیشن موبایل محسوب میشود؟ پاسخ این است که اگر تمایل به نوشتن کد ++C داشته باشید چنین است و این تنها شرط لازم و کافی برای چنین کاری محسوب میشود. یک پیکربندی با سازماندهی مناسب از djinni بخش عمدهای از دشواری پل زدن بین سیستمهای عامل را رفع میکند، اما موجب کاهش پیچیدگی نوشتن کدهای ++C نمیشود. وقتی به زبانهای Objective-C ،Swift ،Java یا Kotlin کدنویسی میکنید، احتمال بروز باگ خطرناک در اپلیکیشن کم است، اما در زمان کدنویسی ++C چنین امری کاملاً محتمل است. در هر حال، اگر تیم شما از قبل تجربهای در کدنویسی به زبان ++C دارد و آن را زبان مناسبی میداند، میتوانید با استفاده از این زبان به شدت سریع و بالغ و دارای پشتیبانی مناسب اقدام به اشتراک کد بین پلتفرمهای مختلف بکنید.
در سالهای اخیر بسیاری از اپلیکیشنهای بزرگ از این تکنیک اشتراک کد از طریق ++C و Djinni استفاده کردهاند. این تکنیک امکان بهرهبرداری حداکثری از سختافزار موجود، تکرارهای سریع بدون نگرانی از واگرایی و تمرکز روی نکتهای که برای همه توسعهدهندگان مهم است، یعنی ایجاد یک تجربه کاربری عالی را فراهم ساخته است.
در این راهنما با طرز کار مرتبسازی و روش پیادهسازی آن در جاوا آشنا میشویم. مرتبسازی هیپ یا Heap Sort چنان که از نامش برمیآید بر مبنای ساختمان داده هیپ اجرا میشود. برای درک صحیح هیپ ابتدا باید با ساختمان آن و روش پیادهسازیاش آشنا شویم.
هیپ یک ساختمان داده خاص مبتنی بر درخت است و از این رو هیپ را گرههایی تشکیل میدهند. ما عناصر هیپ را به این گرهها انتساب میدهیم. هر گره شامل دقیقاً یک عنصر است. ضمناً گرهها میتوانند فرزندانی داشته باشند. اگر یک گره هیچ فرزندی نداشته باشد آن را برگ مینامیم. آنچه هیپ را خاص میسازد دو چیز است:
به دلیل قاعده اول فوق کمترین عنصر همواره در ریشه درخت قرار میگیرد. روش الزام این قواعد نیز به نوع پیادهسازی وابسته است. هیپها عموماً برای پیادهسازی صفهای اولویت استفاده میشوند، زیرا هیپ یک پیادهسازی کاملاً کارآمد برای استخراج عنصر با کمترین (یا بیشترین) مقدار محسوب میشود.
هیپ گونههای بسیار مختلفی دارد که تنها تفاوت آنها از نظر برخی جزییات پیادهسازی با هم متفاوت هستند. برای نمونه آن چه در بخش فوق توصیف کردیم، یک Min-Heap یا هرم کمینه است، زیرا مقدار والد همواره کمتر از فرزندانش است. به طور جایگزین میتوان Max-Heap یا هرم بیشینه نیز داشت که در آن والد همواره بزرگتر از فرزندانش است. از این رو بزرگترین عنصر در گره ریشه قرار خواهد داشت.
ما میتوانیم از میان پیادهسازیهای مختلف درخت یکی را برای هیپ انتخاب کنیم. سرراستترین گزینه درخت دودویی است. در درخت دودویی هر گره میتواند حداکثر دو فرزند داشته باشد. ما آنها را برگ چپ و برگ راست مینامیم. سادهترین روش برای الزام به قاعده دوم بخش فوق استفاده از درخت دودویی کامل است. یک درخت دودویی کامل داری قواعد سادهای به شرح زیر است:
در مثالهای زیر نمونههایی از قواعد فوق را میبینید:
درختهای 1، 2، 4، 5 و 7 از قواعد فوق پیروی میکنند. درختهای 3 و 6 از قاعده 1 تخطی کردهاند. درختهای 8 و 9 از قاعده دوم تخطی کردهاند و درخت شماره 10 قاعده سوم را نقض میکند.
ما در این راهنما روی Min-Heap با پیادهسازی درخت دودویی متمرکز میشویم.
ما باید همه عملیات را به ترتیبی پیادهسازی کنیم که هیپ بدون تغییر بماند. بدین ترتیب میتوانیم هیپ را با استفاده از درجهای مکرر بسازیم. بنابراین در ادامه روی یک عمل درج منفرد متمرکز میشویم:
توجه کنید که گام 2 فوق، قاعده هیپ را نقض نمیکند، زیرا اگر مقدار یک گره را با مقدار کمتر عوض کنیم همچنان کمتر از فرزندانش خواهد بود.
در ادامه یک مثال عملی را بررسی میکنیم. فرض کنید میخواهیم مقدار 4 را در این هیپ درج کنیم:
نخستین گام این است که یک برگ جدید ایجاد میکنیم تا مقدار 4 را در آن وارد نماییم:
از آنجا که 4 کمتر از والد خود، 6 است، جای آنها را با هم عوض میکنیم:
اکنون بررسی میکنیم که آیا 4 کمتر از والد خود است یا نه. از آنجا که والد آن 2 است، در این مرحله متوقف میشویم. هیپ همچنان معتبر است و ما مقدار 4 را درج کردهایم.
اکنون تصور کنید میخواهیم مقدار 1 را در این هیپ درج کنیم:
ما باید جای 1 و 4 را تعویض کنیم:
اکنون باید جای 1 و 2 را عوض کنیم:
از آنجا که 1 به ریشه جدید تبدیل شده است در این مرحله متوقف میشویم.
از آنجا که از درخت دودویی کامل استفاده میکنیم، میتوانیم آن را با یک آرایه پیادهسازی کنیم. هر عنصر آرایه یک گره در درخت محسوب میشود. هر گره با اندیسهای آرایه از چپ به راست و از بالا به پایین به روش زیر نشانهگذاری میشود:
تنها کاری که باید انجام دهیم، این است که دقت کنیم چه تعداد عنصر باید در درخت ذخیره کنیم. بدین ترتیب اندیس عنصر بعدی که میخواهیم درج کنیم، اندازه آرایه خواهد بود.
با این روش اندیسگذاری میتوانیم اندیس گرههای والد و فرزند را محاسبه کنیم:
از آنجا که نمیخواهیم دردسر تخصیص مجدد آرایه را داشته باشیم، آن پیادهسازی را با بهرهگیری از ArrayList از آن چه که هست بازهم سادهتر میکنیم.
پیادهسازی یک درخت دودویی کامل چیزی مانند زیر است:
کد فوق تنها عنصر جدیدی به انتهای درخت اضافه میکند. از این رو باید عنصر جدید را در صورت لزوم به سمت بالا پیمایش کنیم. این کار را با کد زیر میتوانیم انجام دهیم:
دقت داشته باشید که چون نیاز داریم عناصر را مقایسه کنیم، باید آنها را با استفاده از java.util.Comparable پیادهسازی کنیم.
از آنجا که ریشه هیپ همواره شامل کوچکترین عنصر است، ایده اصلی مرتبسازی هیپ کاملاً ساده است: گرههای ریشه را تا زمانی که هیپ کاملاً خالی شود، حذف میکنیم. تنها کاری که باید انجام دهیم یک عملیات حذف است که هیپ را در حالت سازگار حفظ میکند. ما باید مطمئن شویم که ساختار درخت دودویی یا مشخصه هیپ نقض نمیشود.
برای این که ساختار حفظ شود نمیتوانیم هیچ عنصری را به جز برگ منتهیالیه سمت راست حذف کنیم. بنابراین ایده کار این است که گره ریشه را حذف کنیم و برگ سمت راست را در گره ریشه ذخیره کنیم. اما این عملیات قطعاً مشخصه هیپ را نقض میکند. بنابراین اگر ریشه جدید بزرگتر از هر یک از گرههای فرزندش باشد، آن را با کمترین فرزندش عوض میکنیم. از آنجا که گره کوچکترین فرزند، کوچکتر از همه گرههای فرزند دیگر است، مشخصه هیپ نقض نمیشود.
این کار تعویض را تا زمانی که عنصر به یک برگ تبدیل شود و یا کمتر از همه فرزندانش باشد، ادامه میدهیم. برای مثال، در هیپ زیر میخواهیم ریشه را از درخت حذف کنیم:
ابتدا برگ آخر را در ریشه قرار میدهیم:
سپس از آنجا که بزرگتر از هر دو فرزند خود است، آن را با کمترین فرزندش یعنی 2 عوض میکنیم:
4 کمتر از 6 است و بنابراین در این مرحله متوقف میشویم.
بر اساس همه آن چه تا به اینجا گفتیم، حذف کردن ریشه (popping) کاری مانند زیر است:
چنان که پیشتر گفتیم، مرتبسازی صرفاً به ایجاد هیپ و حذف کردن مکرر ریشه مربوط است:
کارکرد این الگوریتم را با تست زیر میتوانیم بررسی کنیم:
توجه کنید که امکان ارائه یک پیادهسازی که مرتبسازی درجا انجام دهد نیز وجود دارد. این کار بدان معنی است که نتیجه را در همان آرایهای که عناصر را در خود دارد ارائه کنیم. به علاوه در این روش به هیچ تخصیص حافظه آنی نیاز نداریم. با این حال، درک آن پیادهسازی کمی دشوارتر خواهد بود.
مرتبسازی هیپ دو مرحله کلیدی دارد که یک درج کردن عنصر و دیگری حذف گره ریشه است. هر دو مرحله دارای پیچیدگی زمانی (O(log n هستند. از آنجا که هر دو مرحله n بار تکرار میشوند، پیچیدگی مرتبسازی کلی برابر با (O(n log n خواهد بود.
دقت کنید که ما به هزینه تخصیص مجدد آرایه اشاره نکردیم، اما از آنجا که پیچیدگی آن (O(n است تأثیری روی پیچیدگی کلی نخواهد داشت. ضمناً چنان که پیشتر گفتیم، امکان پیادهسازی مرتبسازی به صورت درجا نیز وجود دارد. بدین ترتیب نیازی به تخصیص مجدد آرایه هم وجود نخواهد داشت. همچنین باید اشاره کنیم که در هیپ 50% از عناصر برگ هستند و 75% از آنها عناصری هستند که در پایینترین سطح قرار دارند. از این رو اغلب عملیات درج، به چیزی بیش از دو گام نیاز نخواهند داشت.
توجه داشته باشید که در دادههای دنیای واقعی، الگوریتم Quicksort کارآمدتر از مرتبسازی هیپ است. نکته اینجا است که الگوریتم مرتبسازی هیپ همواره سناریوی بدترین حالت یعنی پیچیدگی زمانی (O(n log n را دارد.
در این راهنما، یک پیادهسازی از هیپ دودویی و مرتبسازی هیپ را مورد برسی قرار دادیم. با این که پیچیدگی زمانی آن در اغلب موارد (O(n log n است، اما بهترین الگوریتم در دنیای واقعی محسوب نمیشود.