روش های برقراری ارتباط بین کلاس ها در سوئیفت ۵ — به زبان ساده
زمانی که یک اپلیکیشن iOS ایجاد میکنید، در اغلب موارد نیاز است که بین کلاسهای Model ،View و Controller ارتباطهایی برقرار کنید. در این مقاله به بررسی روشهای مختلف برای برقراری ارتباط بین کلاس ها در سوئیفت 5 میپردازیم. تصویر زیر الگوی معماری MVC را نمایش میدهد که روشی برای طراحی کردن یک گردش داده در پروژهها محسوب میشود.
یکی از مزایای استفاده از یک الگوی معماری این است که کد شما به سادگی درک خواهد شد، ماژولار میشود و قابلیت نگهداری آن ارتقا مییابد. با تجزیه کلاسها به دستههای مختلف نیازمند یک روش مؤثر برای برقراری ارتباط در مسیر model -> controller -> view و برعکس خواهیم بود. ما در ادامه 3 روشی که برای نیل به این مقصود مورد نیاز است را مورد بررسی قرار دادهایم. این سه روش به صورت خلاصه شامل موارد زیر هستند:
- استفاده از نوتیفیکیشن
- استفاده از نمایندگی
- استفاده از callback
با ما همراه باشید تا با بررسی این موارد درکی مقدماتی از مزایا و معایب هر روش پیدا کنید و بتوانید راهحل مناسب خود را انتخاب نمایید.
نوتیفیکیشن
استفاده از نوتیفیکیشن زمانی مناسب خواهد بود که مشاهدهگرهای زیادی داشته باشیم که برای بهروزرسانی نیازمند «شنیدن» باشند. برای نمونه اگر 5 کلاس دارید که همگی دادههای ذخیره شده را بارگذاری میکنند میتوانید به یکباره به هر 5 کلاس هشدار دهید که یک درخواست موفق شبکه برقرار شده است.
ساختار مقدماتی چنین است:
1//Posting a notification
2NotificationCenter.default.post(name:
3NSNotification.Name("hello"), object: nil)
4//Receiving a notification
5NotificationCenter.default.addObserver(self, selector: #selector(helloReceived), name: NSNotification.Name("hello"), object: nil)
6//Calling a function using #selector
7@objc private func helloReceived() {
8 print("hello world!")
9}
نکته: تگ obj@ در ابتدای هر تابعی که از طریق selector# فراخوانی شود، ضروری خواهد بود.
گرچه امکان ارسال داده از طریق نوتیفیکیشن وجود دارد، اما به این منظور ابزار بهینهای محسوب نمیشوند. در هر حال، بهترین کاربرد آنها برای هشدار به یک کلاس است که دادههای جدیدی موجود شدهاند. فرض کنید مدل دادهای دارید که در آن دادههایی را از شبکه دانلود میکنید، زمانی که این دادهها در حافظه ذخیره شدند، به کنترلرهای «نما» (View) هشدار میدهید تا این دادههای اخیراً ذخیره شده را در نما وارد کنند. در ادامه مثالی از طرز کار ساختار نوتیفیکیشن را میبینید:
1class ViewController: UIViewController {
2 let model = Model()
3
4 override func viewDidLoad() {
5 super.viewDidLoad()
6 //post notification
7 NotificationCenter.default.post(name: NSNotification.Name("viewLoaded"), object: nil)
8 }
9}
10class Model {
11 init() {
12 //init notification receiver
13 NotificationCenter.default.addObserver(self, selector: #selector(viewWasLoaded), name: NSNotification.Name("viewLoaded"), object: nil)
14 }
15 //function that handles notification
16 @objc private func viewWasLoaded() {
17 print("Model was alerted about viewDidLoad!")
18 }
19}
نمایندگی
«نمایندگی» (Delegation) روشی است که در اغلب موارد برای برقراری ارتباط بین کلاسهای مدل، نما و کنترلر مورد استفاده قرار میگیرد. در الگوی طراحی نمایندگی از یک پروتکل برای تعریف متدها، مشخصهها و دیگر الزامات مورد نیاز برای پیادهسازی یک کلاس استفاده میشود. پروتکل را میتوان به عنوان یک قرارداد تصور کرد که با اجرای قرارداد موجود در پروتکل متعهد میشویم الزامات آن را پیادهسازی کنیم.
برای پیادهسازی الگوی نمایندگی باید یک پروتکل را راهاندازی کنیم:
1protocol ModelDelegate: class {
2 func didReceiveData(_ data: String)
3}
در کلاس متد، یعنی کلاسی که این نمایندگی را بر عهده دارد، باید یک ارجاع ضعیف به ModelDelegate داشته باشید.
نکته: ارجاع به نماینده باید ضعیف یا unowned باشد. با استفاده از یک ارجاع قوی، ممکن است متوجه شوید در حال ایجاد یک چرخه هستید که در آن نماینده ارجاعی به کلاس والد نگه میدارد و کلاس والد نیز به نماینده ارجاع دارد. با ایجاد چنین حلقه ارجاع قوی، هر دو شیء در حافظه به همدیگر ارجاع میدهند و از این رو ARC به محض «مقدارزدایی» (de-initialization) از شیء نمایندگی شده آن را تخصیصزدایی میکند.
1class Model {
2 weak var delegate: ModelDelegate?
3 func downloadData() {
4 let data = "Network request information."
5 delegate?.didReceiveData(data)
6 }
7}
اکنون یک وهله از Model در کلاس Controller ایجاد میکنیم و نماینده آن را به Self انتساب میدهیم.
1class ViewController: UIViewController {
2 let model = Model()
3
4 override func viewDidLoad() {
5 super.viewDidLoad()
6 model.delegate = self
7 model.downloadData()
8 }
9}
10extension ViewController: ModelDelegate {
11 func didReceiveData(_ data: String) {
12 print(data)
13 }
14}
برای انتساب نماینده مدل به Self، باید ViewController با پروتکل ModelDelegate هماهنگ باشد و توجه داشته باشید که در این مثال Self همان کلاس ModelDelegate است. ما میتوانیم این هماهنگی را بدین طریق به دست آوریم که یک بسط از کنترل نما که از این پروتکل استفاده میکند به طرحبندی تعریفشده در ModelDelegate اضافه کنیم. زمانی که مدل با موفقیت دادهها را دانلود کرد، تابع didReceiveData را درون ViewController فراخوانی خواهد کرد. زمانی که این تابع فراخوانی شد، میتوانید کلاس View را درون ViewController بهروزرسانی کنید.
Callback
راهاندازی Callback-ها ساده است. برخی برنامه نویسان از این روش کمتر استفاده میکنند چون پایداری الگوی نمایندگی را بیشتر میپسندند. با این حال، Callback-ها برای ایجاد ارتباطهای ساده مفید هستند.
تصور کنید در یک کلاس نما تابعی داریم که یک انیمیشن را اجرا میکند. ما میخواهیم به محض تکمیل شدن انیمیشن به ViewController خود هشدار دهیم که انیمیشن به پایان رسیده است.
1class View {
2 func runAnimation(completion: @escaping() -> ()) {
3 //run animation, upon completion send callback
4 completion()
5 }
6}
به این منظور در ViewController یک وهله از کلاس View ایجاد میکنیم که تابع ()runAnimation را فراخوانی میکند:
1class ViewController: UIViewController {
2 let view = View()
3 override func viewDidLoad() {
4 super.viewDidLoad()
5
6 view.runAnimation { [weak self] in
7 self?.animationFinished()
8 }
9 }
10 private func animationFinished() {
11 print("Animation in View was finished!")
12 }
13}
نکته: به «weak self in» درون بستار روی ()runAnimation توجه خاصی داشته باشید. با تعیین این self به صورت یک ارجاع ضعیف ما از ایجاد چرخههای پایدار جلوگیری میکنیم. اگر self را به صورت قوی در اختیار بگیریم، آن گاه بستار یک ارجاع قوی به self میگیرد و بنابراین امکان تخصیصزدایی از آن در حافظه از دست میرود.
همچنین امکان ارسال دادهها به صورت مستقیم از طریق Callback وجود دارد:
1class Model {
2 func getData(completion: ((_ data: String) -> Void)) {
3 let data = "Network data!"
4 completion(data)
5 }
6}
7class ViewController: UIViewController {
8 let model = Model()
9 override func viewDidLoad() {
10 super.viewDidLoad()
11 model.getData { [weak self] (data: String) in
12 self?.updateView(data)
13 }
14 }
15 private func updateView(_ data: String) {
16 print(data)
17 }
18}
نکته: کاراکتر زیرخط درون تعریف تابع به آن معنی است که نیازی به گنجاندن نام پارامتر در زمان فراخوانی یک متد نداریم.
سخن پایانی
شما با مطالعه این مقاله با برخی از گزینههای برقراری ارتباط در سوئیفت آشنا شدید. اغلب برنامهنویسان در پروژههای خود از انواع مختلفی از این روشهای برقراری ارتباط بین کلاسها استفاده میکنند، چون هر کدام از آنها کاربردهای خاص خود را دارند. از نمایندگی برای طرحبندی پایدار بین کلاسها و اجرای ارتباطهای زیاد استفاده میشود. از Callback برای کارکردهای سادهای مانند ارسال Callback در زمان پایان یافتن یک اکشن استفاده میشود. همچنین از نوتیفیکیشن برای ارسال هشدار به چند کلاس جهت اطلاع دادن دانلود شدن دادهها یا تغییر یافتن آنها استفاده میکنیم.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای برنامهنویسی
- آموزش برنامه نویسی Swift (سوئیفت) برای برنامه نویسی iOS
- مجموعه آموزشهای دروس علوم و مهندسی کامپیوتر
- برنامه نویسی تابعی در سوئیفت — راهنمای مقدماتی
- آموزش برنامه نویسی سوئیفت (Swift): متغیر، ثابت و انواع داده – بخش اول
==