سلام. اگر تجربه توسعه ی اپلیکیشن داشته باشید احتمالا میدونید که یکی از دغدغه های همیشگی توسعهدهندهها اینه که بتونن مدیریت رویدادهای مرتبط با هر بخش کد رو انجام بدن. برای مثال ممکنه شما نیاز داشته باشید در هنگام بروز یه رویداد (که جزئیاتش هم دست شما نیست) یک سری فرآیندهایی رو اجرا کنید.
گیجکننده بود؟ بذارید یه مثال بزنم. فرض کنید از یک کتابخونه استفاده میکنید که وظیفهش پخش کردن ویدیو هست. توی این کتابخونه یک متد دارین تحت عنوان ()play که پخش ویدیو رو انجام میده. حالا فکر کنید شما لازم دارین تا در پایان پخش ویدیو یک کاری رو انجام بدین. مثلا یک پیامی رو به کاربر نشون بدین. خب... شما که به کدهای داخل کتابخونه دسترسی ندارین. اصلا حتی اگر هم داشتین دستکاری کردن کدهای یه نفر دیگه قطعا براتون کار جذابی نبود. بود؟
اینجا دقیقا همون جاییه که مفهوم متدهای Delegate به کمک ما میاد. البته اگه شما برنامه نویس سوییفت نیستین و با زبانها و تکنولوژیهای دیگه کار کردین ممکنه اصطلاح دیگهای رو (مثل Listener یا ...) برای این مفهوم به کار ببرین. در نتیجه میتونم بگم اگه بخواین بعدا کتابخونهای رو توسعه بدین و اصلا حتی بخواین کد تمیزتر و خواناتری داشته باشین این مفهوم میتونه کمکتون کنه. بریم برای پیاده سازی؟
تعریف مسئله
مسئله ای که میخوام به عنوان نمونه معرفی کنم یه کلاس خیلی سادست. فرض کنید میخوایم یه کلاس شمارنده بسازیم. این کلاس در شروع برای ساخته شدن یه مقدار هدف (goal) از ما میگیره و یه متد Delegate داره که وقتی شمارنده به عدد هدف رسید اجرا میشه. خب پس بریم برای ساختن این کلاس خیلی ساده و آموزنده :))
برای تعریف Delegate ها از پروتکلها استفاده میکنیم. دلیلشم اینه که هر کلاسی میتونه از چندین پروتکل پیروی کنه و اینطوری هر کدوم از کنترلرهای ما میتونن چندین Delegate رو هندل کنن. خب پس ما اول میایم پروتکل CounterDelegate رو تعریف میکنیم. همونطور که گفتم وظیفه این کلاس تعریف متدهای دلیگیت هستش. من دوتا متد تعریف میکنم؛ یکی برای زمانی که مقدار شمارنده تغییر میکنه و یکی دیگه برای زمانی که به هدف میرسیم.
protocol CounterDelegate : class { func counter(valueChanged from: Int, to: Int) func counter(reachedToGoal goal: Int) }
اینجا من توی تعریف پروتکل (خط ۱) مشخص کردم که فقط کلاس ها میتونن از اون استفاده کنن. دلیلش رو یکم جلوتر میگم. خب حالا میخوایم خود کلاس شمارنده رو پیاده سازی کنیم. من کلاسم رو اینطوری تعریف میکنم.
class Counter { /// مقدار فعلی شمارنده private var index: int = 0 /// مقدار قبلی شمارنده private var lastIndex: int = 0 /// مقدار هدف public var goal: Int public weak var delegate: CounterDelegate? init(goal: Int) { self.goal = goal } /// کاهش مقدار شمارنده func decrease() { lastIndex = index index -= 1 callDelegateMethods() } /// افزایش مقدار شمارنده func increase() { lastIndex = index index += 1 callDelegateMethods() } private func callDelegateMethods() { } }
دقت کنید که متغیر delegate رو از نوع weak تعریف کردم تا از ایجاد strong reference cycle جلوگیری بشه. دلیل اینکه پروتکل دلیگیت رو هم فقط برای کلاس قابل استفاده در نظر گرفتم همین بود. وگرنه کامپایل ارور میگرفتم. برای اینکه بهتر درک کنید که سیکل ارجاع چیه اگه دوست داشتید این مقاله رو بخونید.
خب به نظر میرسه همه چی عالیه. حالا میخوام تابعی رو تعریف کنیم که بالاتر بدنه ش رو خالی گذاشتم. تابع callDelegateMethods وظیفه ش اینه که بعد از هر کدوم از عملیات اصلی کلاس (یعنی کاهش و افزایش) متدهای دلیگیت رو بر حسب نیاز فراخوانی کنه. من این تابع رو اینطوری تعریف میکنم.
private func callDelegateMethods() { delegate?.counter(valueChanged from: lastIndex, to: index) if goal == index { delegate?.counter(reachedToGoal goal: index) } }
دقت کنید دلیل اینکه پشت متغیر delegate علامت سوال قرار گرفته بخاطر اینه که من بالاتر این متغیر رو آپشنال تعریف کردم. چون زور که نیست شاید اصن یکی دلش نخواد از دلیگیت کلاس من استفاده کنه :))
تقریبا دیگه کار تمومه و میخوام یه کلاس بسازم که شمارنده استفاده کنه. اسمش رو میذارم MyClass و توش یه آبجکت از Counter میسازم. هدف شمارنده رو هم ۲ میذارم.
class MyClass { let counter = Counter(goal: 2) init() { counter.delegate = self } func useCounter() { counter.increase() counter.increase() counter.decrease() } }
برای اینکه کدم تمیزتر شه متدهای دلیگیت رو توی یک اکستشن از کلاسم تعریف میکنم:
extension MyClass: CounterDelegate { func counter(valueChanged from: Int, to: Int) { print("value changed from \(from) to \(to)") } func counter(reachedToGoal goal: Int) { print("counter reached to goal: \(goal)") } }
حالا کافیه از کلاس MyClass یه آبجکت بسازم و متد useCounter رو فراخوانی کنم:
let myClass = MyClass() myClass.useCounter()
نتیجه به صورت زیر خواهد بود:
value changed from 0 to 1 value changed from 1 to 2 counter reached to goal: 2 value changed from 2 to 1
سخن پایانی
خب. فکر کنم الان دیگه میدونید زمانی که یک دلیگیت رو استفاده میکنید چه اتفاقی میوفته. علاوه بر این همونطور که اول کار هم گفتم این مطلب میتونه کمکتون کنه تا هم کد تمیزتری داشته باشین و هم اگر روزی خواستین کتابخونه ای برای سوییفت بسازین، بدونید چجوری توابع دلیگیت رو پیاده سازی کنید.
اگر از این مطلب خوشتون اومد و مفید بود براتون لایک فراموش نشه لطفا ☺️?