Closures در Swift بلوکهای کدی مستقل هستند که میتوان پارامترهایی به آنها داد تا محاسباتشان را انجام دهند و مقداری برگردانند. closureها همچنین مقادیری را از محیط خود برمیدارند و در محاسباتشان به کار میگیرند. چون مستقلاند، میتوان آنها را در برنامه مانند سایر مقادیر جابهجا کرد یا به ثابتها و متغیرها اختصاص داد.
closureها ممکن است بسیار شبیه توابع Swift به نظر برسند؛ واقعاً همینطور است و دلیل خوبی هم دارد. توابع در Swift حالت خاصی از closureها هستند که داشتن اسم برایشان الزامی است و با نحوهی نگارش کمی متفاوت تعریف میشوند.
هرچند توابع در Swift از نظر فنی closure محسوب میشوند، وقتی مردم در Swift از «closureها» حرف میزنند، منظورشان عبارتهای closure است.
عبارتهای closure به این شکل نوشته میشوند: فهرست پارامترها، سپس نوع بازگشتی، بعد کلیدواژهی in و در پایان بدنهی closure، که همهی اینها داخل یک جفت آکولاد قرار میگیرند:
{ (a: Double, b: Double) -> Double in
return (a + b) / 2.0
}
این قطعه یک عبارت closure از نوع (Double, Double) -> Double تعریف میکند و میتوان آن را همانند تابعی از همین نوع، با اعمال آن روی پارامترهایی از نوع مناسب، اجرا کرد:
{ (a: Double, b: Double) -> Double in
return (a + b) / 2.0
}(10.0, 15.0)
// returns 12.5
از آنجا که نوشتن کل عبارت closure هر بار که میخواهیم آن را اجرا کنیم چندان راحت نیست، میتوان closureها را مانند هر مقدار دیگری در Swift به یک اسم اختصاص داد. بعد میتوان آنها را با اعمال آن اسم روی پارامترها فراخوانی کرد:
let mean = { (a: Double, b: Double) -> Double in
(a + b) / 2.0
}
mean(13.0, 100.0)
// returns 56.5
همانطور که در بالا دیدیم، closureها هم مانند توابع معمولی میتوانند کلیدواژهی return را حذف کنند، اگر بدنهی closure تنها یک عبارت باشد.
بیشتر اوقات، closureها همراه با توابع مرتبه بالاتر به کار میروند: یا به عنوان پارامتر به آنها داده میشوند یا به عنوان نتیجه برگردانده میشوند:
[11, 75, 3, 99, 53].contains(where: { (x: Int) -> Bool in x > 100 })
// returns false
["apple", "ball", "carrot"].sorted(by: { (s1: String, s2: String) -> Bool in s1.count < s2.count })
// returns ["ball", "apple", "carrot"]
func makeAdder(base: Int) -> (Int) -> Int {
{ (x: Int) -> Int in base + x }
}
سلام دوباره، مأمور Double-Null0111.
نیروهای UMBRA دوباره دست به کار شدهاند و خنثیکردن نقشهی جدیدشان به عهدهی شماست. واحد اطلاعات ما خبر داده است که آن مینیونها قصد دارند در مهمانی فرماندار یک بمب بدبو منفجر کنند.
چون مینیونها بسیار دستوپاچلفتیاند، اغلب بهطور تصادفی بمبهای بدبو را در آزمایشگاهها و دفترهایشان مسلح میکنند. و چون مدام فراموش میکنند چطور بمبهای بدبو را خنثی کنند، سامانهای راه انداختهاند که دستورالعملهای خنثیسازی را تولید میکند؛ دستورالعملهایی که ترتیب بریدن سیمها را بر پایهی شمارهی شناسایی بمب فهرست میکنند.
کار شما این است که کدی بنویسید تا بتوانید بمب بدبو را در مهمانی، پیش از آنکه منفجر شود، خنثی کنید.
روی هر بمب بدبو سه سیم با رنگهای متفاوت وجود دارد.
یک closure بنویسید که یک «تاپل» (String, String, String) میگیرد و همان تاپل را با جابهجاشدن ترتیب دو عنصر اولش برمیگرداند.
این closure را به اسم flip تخصیص دهید.
نکتهی مهم این است که نوع آن باید ChangeClosure باشد (یا یک نوع Sendable)؛ تعریف این نوع در فایل اولیه آمده است.
در غیر این صورت کد کامپایل نمیشود.
flip(("red", "yellow", "blue"))
// Returns ("yellow", "red", "blue")
یک closure بنویسید که یک تاپل (String, String, String) میگیرد و همان تاپل را با چرخش ترتیب سیمها به سمت چپ برمیگرداند.
این closure را به اسم rotate تخصیص دهید.
نکتهی مهم این است که نوع آن باید ChangeClosure باشد (یا یک نوع Sendable)؛ تعریف این نوع در فایل اولیه آمده است.
rotate(("red", "yellow", "blue"))
// Returns ("yellow", "blue", "red")
برای اینکه بفهمید سیمهای بمب بدبو را به چه ترتیبی باید ببرید، باید تابعی پیادهسازی کنید که بر اساس شمارهی شناسایی بمب بدبو، تابع درهمریزی مناسب را تولید میکند.
این تابع را پیادهسازی کنید:
makeShuffle(
flipper: @escaping ((String, String, String)) -> (String, String, String),
rotator: @escaping ((String, String, String)) -> (String, String, String)
) -> ([UInt8], (String, String, String)) -> (String, String, String)
این تابع بهعنوان ورودی یک closure میگیرد که دو سیم را جابهجا میکند و یک closure که سه سیم را میچرخاند، و در نهایت یک closure برمیگرداند. closure بازگرداندهشده شمارهی شناسایی بمب بدبو و ترتیب سه سیم را میگیرد و سپس ترتیبی را محاسبه میکند که سیمها باید بر اساس آن بریده شوند. شمارهی شناسایی از ۸ بیت تشکیل شده است و هر بیت تعیین میکند که سیمها جابهجا شوند یا بچرخند.
برای هر بیت در شمارهی شناسایی، با شروع از راستترین بیت، اگر بیت 0 باشد closure flipper را روی تاپل سیمها اعمال میکنید و اگر 1 باشد closure rotator را اعمال میکنید تا حالت جدید سیمها به دست آید.
پس از آنکه closureهای مناسب برای همهی ۸ بیت شمارهی شناسایی اعمال شدند، حالت نهایی سیمها همان ترتیبی است که باید بریده شوند.
let shuffler = makeShuffle(flipper: flip, rotator: rotate)
// Returns ([UInt8], (String, String, String)) -> (String, String, String)
shuffler([1, 0, 0, 1, 1, 0, 1, 1], ("red", "yellow", "blue"))
// Returns ("red", "blue", "yellow")