توابع بینام
یکبار دیگه مثال قبل رو ببینید:
func whatToAnimate() { // self.myButton is a button in the interface
self.myButton.frame.origin.y += 20
}
func whatToDoLater(finished:Bool) {
print("finished: (finished)")
}
UIView.animate(withDuration:0.4, animations:whatToAnimate, completion:whatToDoLater)
این کد یکم زشته! من تابع whatToAnimate و whatToDoLater رو فقط بخاطر اینکه میخواستم توی خط آخر اونها رو ارسال کنم تعریف کردم و درحقیقت هیچ نیازی به «اسامی« whatToAnimate و whatToDoLater ندارم، بجز اینکه از اونها توی خط آخر برای ارجاع به توابع استفاده میکنیم و نه اسم این توابع و نه خودشون هیچجای دیگه بکار نرفتن. بنابراین، خیلی خوب میشه اگه بتونیم فقط بدنهی این توابع رو بدون تعریف اسم براشون، بفرستیم.
به این قابلیت، تابع «بینام» (Anonymous Function) میگن و توی Swift قانونی و کاملاً رایجه. برای ایجاد یه تابع بینام باید دو کار انجام بدین:
- بدنهی تابع رو بسازین (شامل آکولادهای باز و بسته، ولی بدون تعریف تابع).
- اگه لازم بود، فهرست پارامترها و نوع بازگشتی تابع رو توی خط اول بدنهی تابع (داخل آکولادهای باز و بسته) بنویسین و بعدش کلمهی کلیدی in رو ذکر کنید.
اجازهبدین با تبدیل تابع نامدار خودمون به تابع بینام، این موضوع رو تمرین کنیم. در اینجا یه تابع نامدار بهاسم whatToAnimate تعریف شده:
func whatToAnimate() {
self.myButton.frame.origin.y += 20
}
حالا تابع بینام معادل همون تابع رو ببینید. دقتکنید که من فهرست پارامترها و نوع بازگشتی رو به داخل بدنهی تابع منتقل کردم:
{
() -> () in
self.myButton.frame.origin.y += 20
}
حالا تابع whatToDoLater رو درنظر بگیرین:
func whatToDoLater(finished:Bool) {
print("finished: (finished)")
}
و حالا معادل بینام اون رو ببینید:
{
(finished:Bool) -> () in
print("finished: (finished)")
}
حالا که فهمیدیم چطور توابع بینام بسازیم، بگذارین ازشون استفاده کنیم. نقطهای که نیاز به توابع داریم، همونجایی هست که آرگومانها رو برای تابع
animate(withDuration:animations:completion:) میفرستیم. میتونیم توابع بینام رو مستقیماً در همون نقطه تعریف کنیم و بفرستیم. درست مثل این کد:
UIView.animate(withDuration:0.4,
animations: {
() -> () in
self.myButton.frame.origin.y += 20
},
completion: {
(finished:Bool) -> () in
print("finished: (finished)")
}
)
میتونیم همینروش رو در جایی که تابع imageOfSize رو در پست قبلی صدا زدیم هم بکار ببریم. قبلاً تابع رو اینطوری صدا میزدیم:
func drawing(_ w:Int, _h:Int) {
let p = UIBezierPath(roundedRect: CGRect(x:0, y:0, width:w, height:h), cornerRadius:8)
p.stroke()
}
let image = imageOfSize(45, 20, drawing)
الآن میدونیم که نیاز نیست تابع drawing رو جداگانه تعریف کنیم. میتونیم imageOfSize رو با یه تابع بینام صدا بزنیم:
let image = imageOfSize(45, 20, {
(_ w:Int, _ h:Int) -> () in
let p = UIBezierPath(roundedRect: CGRect(x:0, y:0, width:w, height:h), cornerRadius:8)
p.stroke()
})
توابع بینام خیلی زیاد توی سویفت بکار رفته، بنابراین سعی کنید مطمئن بشین که این قابلیت رو خوب یاد گرفتین و چنین کدهایی رو میتونین خیلی خوب و راحت بخونین و بنویسین! توابع بینام درحقیقت «اینقدر» رایج و مهم هستن که یکسری میانبر برای نوشتنشون هم ارائه شده:
- حذف نوع بازگشتی: اگه مقدار بازگشتی تابع برای کامپایلر مشخص باشه، میتونین عملگر -> و مشخصکردن نوع مقدار بازگشتی رو نادیده بگیرین:
UIView.animate(withDuration:0.4, animations: {
() in
self.myButton.frame.origin.y += 20
}, completion: {
(finished:Bool) in
print("finished: (finished)")
})
- حذف خط in اگه پارامتری وجود نداشته باشه: اگه تابع بینام ما هیچ پارامتر ورودی نداشته باشه، و مقدار بازگشتی اون رو هم بتونیم طبق شرایط بند قبل نادیده بگیریم، میتونیم کلاً خط in رو ننویسیم:
UIView.animate(withDuration:0.4, animations: {
self.myButton.frame.origin.y += 20
}, completion: {
(finished:Bool) in
print("finished: (finished)")
})
- حذف نوع پارامترها: اگه تابع بینام پارامتر ورودی داشته باشه و نوعشون برای کامپایلر مشخص باشه، میتونیم نوعشون رو اعلام نکنیم:
UIView.animate(withDuration:0.4, animations: {
self.myButton.frame.origin.y += 20
}, completion: {
(finished) in
print("finished: (finished)")
})
- حذف پرانتزها: اگه نوع پارامترها حذف بشه، پرانتزهای دور پارامترها رو هم میشه حذف کرد:
UIView.animate(withDuration:0.4, animations: {
self.myButton.frame.origin.y += 20
}, completion: {
finished in
print("finished: (finished)")
})
- حذف خط in وقتی که پارامتر وجود داره: اگه نوع بازگشتی رو بتونیم حذف کنیم و نوع پارامترها برای کامپایلر مشخص باشه، میتونین خط in رو حذف کنین و مستقیماً به پارامترها در بدنهی تابع با کمک ترتیب اونها و با اسامی خاصی مثل $0 و $1 و... دسترسی پیدا کنین:
UIView.animate(withDuration:0.4, animations: {
self.myButton.frame.origin.y += 20
}, completion: {
print("finished: ($0)")
})
- حذف اسامی پارامترها: اگه بدنهی تابع بینام نیاز به استفاده از پارامتری نداشته باشه، میتونین بجای اسم اون پارامتر، یه کارکتر خطزیر (Underscore) توی فهرست پارامترها در خط in بنویسین. درحقیقت اگه تابع بینام نیاز به استفاده از «هیچ» پارامتری نداشته باشه، میتونین بجای کل پارامترها، فقط یک خطزیر بنویسین:
UIView.animate(withDuration:0.4, animations: {
self.myButton.frame.origin.y += 20
}, completion: {
_ in
print("finished!")
})
ولی دقتکنید که اگه تابع بینام پارامتر میگیره، «باید» اونها رو یهجوری بشناسین. میتونین خط in رو نادیده بگیرین و از پارامترها با کمک اسامی خاص $0 و... استفاده کنین یا میتونین خط in رو بنویسین و پارامترها رو با کارکتر خطزیر نادیده بگیرین ولی نمیتونین همزمان هم خط in رو حذف کنین و هم از پارامترها با کمک اسامی ترتیبی اونها استفاده نکنید! اگه چنینکاری انجام بدین، کدتون کامپایل نمیشه.
- حذف برچسب آرگومان تابع: اگه همونطور که در اغلب مواقع پیش میاد، تابع بینام شما آخرین آرگومانی باشه که برای یه تابع دیگه ارسال میشه، میتونین فراخوانی تابع اصلی (که میخواین تابع بینام رو براش بفرستین، مستقیماً با قراردادن پرانتز بستهی فراخوانی، قبل از آخرین آرگومانش ببندین و بعد فقط تابع بدوننام خودتون رو «بدون برچسب» بنویسید (به این قابلیت، «تابع دنبالهرو» میگن) :
UIView.animate(withDuration:0.4, animations: {
self.myButton.frame.origin.y += 20
}) {
_ in
print("finished!")
})
- حذف پرانتزهای تابع فراخواننده: اگه از دستور زبان تابع دنبالهرو استفاده میکنین، و اگه تابع شما هیچ پارامتری بجز تابع بینام موردنظر شما نداره، میتونین پرانتزهای خالی رو از فراخوانی حذف کنین. این «تنها» وضعیتی هست که میتونین پرانتزها رو از فرخوانی یه تابع حذف کنین! برای اینکه متوجه بشین، به این مثال دقت کنید:
func doThis(_ f:()->()) {
f()
}
doThis { // no parentheses!
print("Hello")
}
- حذف کلمهی کلیدی return از تابع: اگه بدنهی تابع بینام شما «دقیقاً یک خط کد» داشته باشه و اون خط هم شامل برگردوندن یه مقدار با کلمهی کلیدی return باشه، میتونین کلمهی کلیدی return رو حذف کنین. اگه بخوام یهجور دیگه بگم، درصورتی که یه تابع بینام فقط شامل یه دستور باشه، Swift فرض میکنه که این دستور، شامل مقداری هست که باید از تابع بینام برگردونده بشه. برای مثال:
func greeting() -> String {
return "Hello"
}
func performAndPrint(_ f:()->String) {
let s = f()
print(s)
}
performAndPrint {
greeting() // meaning: return greeting()
}
وقتی توابع بینام مینویسین، مرتباً با شرایطی مواجه میشین که تمام قوانین حذف که در بالا گفتیم، مجاز هستن. بعلاوه اغلب اوقات «چیدمان» کدها (نه خود کد) رو با قراردادن کل تابع بینام و فراخوانیش «در یک خط» خلاصه میکنین. بنابراین کد Swift میتونه شامل توابع بینامی باشه که بهشدت فشرده شدن.
یه مثال عملی بزنیم تا این موضوع بهتر جا بیفته. ما با یه آرایه از مقادیر Int کار رو شروع میکنیم و یه آرایهی جدید از حاصلضرب اون مقادیر در 2 با کمک فراخوانی متد نمونهای
map(_:) میسازیم. متد
map(_:) از یک آرایه، یه تابع میگیره که یه پارامتر همنوع با عناصر آرایهی اصلی میگیره و یه مقدار جدید برمیگردونه. در اینجا آرایهی ما از مقادیر Int تشکیل شده و ما هم باید برای متد
map(_:) یه تابع بفرستیم که یه مقدار Int میگیره و یه Int هم برمیگردونه. میتونین این موضوع رو اینطوری بنویسیم:
let arr = [2, 4, 6, 8]
func doubleMe(i:Int) -> Int {
reutrn i * 2
}
let arr2 = arr.map(doubleMe) // [4, 8, 12, 16]
خوب این کد خیلی جذاب نیست. ما نیاز به doubleMe در جای دیگری نداریم و درنتیجه میتونیم با تابع بینام جایگزینش کنیم:
let arr = [2, 4, 6, 8]
let arr2 = arr.map({
(i:Int) -> Int in
return i * 2
})
خوب بهتر شد. اما الان میتونیم کدمون رو با قوانین حذفی که در بالا اشاره کردیم، فشرده و کوتاه کنیم. نوع پارامترها دقیقاً مشخصه، بنابراین لازم نیست اونها رو مشخص کنیم. نوع بازگشتی هم مشخصه. پس به اونم نیاز نیست. ازطرفی چون فقط یه پارامتر داریم و ازش استفاده هم میکنیم، نیاز نیست خط in رو بنویسیم چون میتونیم به پارامترمون بصورت
$0 اشاره کنیم. ضمناً بدنهی تابع ما فقط شامل یک خط هست که اونهم مقدار نتیجه رو داره برمیگردونه و بنابراین میتونیم کلمهی کلیدی return رو هم ننویسیم. نکتهی بعدی اینه که متد
map(_:) هیچ پارامتر دیگری هم نداره و درنتیجه میتونیم پرانتزها رو هم حذف کنیم و مستقیماً بعد از اسمش، از تابع دنبالهرو استفاده کنیم:
let arr = [2, 4, 6, 8]
let arr2 = arr.map {$0*2}
دیگه خوشگلتر و خلاصهتر از این نمیشه کدی نوشت واسه اینکار !!!