Урок 18. Closures

6 Марта 2023

Задача 18.1 📚

Создайте свойство с типом блока замыкания, без параметров и без возвращаемых значений. В блоке замыкания выведете фразу на консоль: “It’s first closure”. Создайте еще одно свойство и присвойте ему копию вашего клоужера и запустите его

				
					let closure = {
    print("Task 1: It's the first closure \n")
}

let closureCopy = closure
closureCopy()
				
			

Создаем константу closure, которая является замыканием без параметров и возвращаемого значения. Внутри замыкания выводится фраза “It’s first closure” на консоль при вызове.
Обратите внимание, что тип данных явно можно не прописывать, если вы прописали, это не считается ошибкой, но лишних действий лучше не делать.

Чтобы создать еще одно свойство и присвоить ему копию замыкания closure, мы можем использовать следующий код:

				
					let closureCopy = closure
closureCopy()
				
			

Этот код создает константу closureCopy, которая является копией замыкания closure. Затем мы вызываем closureCopy(), чтобы запустить копию замыкания и вывести фразу “It’s first closure” на консоль.

Обращайте внимание на именование свойств, если у вас нет контекста, то замыкание можно назвать closure, completion, action. 

Задача 18.2 📚

Создайте свойство с типом блока замыкания с двумя параметрами типа String и Int, и возвращаемым значением типа String. Должна вернуться фраза: “Name: <имя>, age: <возраст>”. Присвойте результат работы блока замыкания новому свойству и выведите его на консоль.

				
					let completion: (String, Int) -> String = { name, age in
    "Name: \(name), age: \(age) \n"
}

let result = completion("Tim", 60)
print(result)
				
			

Этот код определяет константу completion как замыкание, которое принимает два параметра типа String и Int, и возвращает строку. Затем создается константа result, которая является результатом вызова замыкания completion с параметрами “Tim” и 60.

При вызове замыкания completion с этими параметрами, возвращается строка “Name: Tim, age: 60”. Эта строка затем присваивается константе result. Затем мы выводим result на консоль, которая будет содержать строку “Name: Tim, age: 60”.

Обратите внимание на синтаксис, никаких лишних пробелов или их отсутствие. 

Задача 18.3 📚

Создайте функцию, которая будет принимать в качестве параметров числовой массив и блок замыкания. Данная функция должна находить максимальное значение из массива. Блок замыкания должен захватывать это максимальное значение и выводить его на консоль. Вызовите три раза данную функцию, при первом вызове в параметр замыкания передайте функцию (создайте ее сами), во второй вызов передайте в параметр свойство с типом блока замыкания, и в третий вызов просто раскройте комплишен и реализуйте вывод максимального значения на консоль.

				
					func getMaxValue(numbers: [Int], completionHandler: (Int) -> Void) {
	if let maxValue = numbers.max() {
		completionHandler(maxValue)
	}
}

// First call with func
func printMaxValue(number: Int) {
    print("Task 4 First call: the maximum value is \(number) \n")
}

getMaxValue(numbers: [1, 2, 3, 4, 5], completionHandler: printMaxValue)

// Second call with property
let closure: (Int) -> Void = {number in
    print("Task 4 Second call: the maximum value is \(number) \n")
}

getMaxValue(numbers: [2, 3, 4, 5, 6], completionHandler: closure)

// Third call with completion
getMaxValue(numbers: [3, 4, 5, 6, 7]) {number in
    print("Task 4 Third call: the maximum value is \(number) \n")
}
				
			

Давайте разбирать все по частям:

				
					func getMaxValue(numbers: [Int], completionHandler: (Int) -> Void) {
	if let maxValue = numbers.max() {
		completionHandler(maxValue)
	}
}
				
			

В этой функции мы используем метод max() для поиска максимального значения в переданном массиве numbers, это значение возвращается опциональным, поэтому мы используем опциональную привязку для извлечения. Внутри конструкции if мы вызываем переданный блок замыкания completionHandler и передаем в него найденное максимальное значение.

Теперь, чтобы вызвать эту функцию три раза, мы можем использовать следующий код:

				
					// First call with func
func printMaxValue(number: Int) {
    print("Task 4 First call: the maximum value is \(number) \n")
}

getMaxValue(numbers: [1, 2, 3, 4, 5], completionHandler: printMaxValue)

// Second call with property
let closure: (Int) -> Void = {number in
    print("Task 4 Second call: the maximum value is \(number) \n")
}

getMaxValue(numbers: [2, 3, 4, 5, 6], completionHandler: closure)

// Third call with completion
getMaxValue(numbers: [3, 4, 5, 6, 7]) {number in
    print("Task 4 Third call: the maximum value is \(number) \n")
}
				
			

В первом вызове мы создаем функцию printMaxValue, которая просто выводит значение максимума на консоль. Затем мы передаем эту функцию в качестве параметра completionHandler при вызове функции getMaxValue.

Во втором вызове мы создаем свойство closure, которое является замыканием, выводящим значение максимума на консоль. Затем мы передаем это свойство в качестве параметра completionHandler при вызове функции getMaxValue.

В третьем вызове мы передаем анонимное замыкание в качестве параметра completionHandler, которое также выводит значение максимума на консоль. Это позволяет нам не создавать отдельную функцию или свойство для вывода максимального значения, а просто определить замыкание на месте.

Задача 18.4 📚

Для вывода на консоль каждую букву используйте цикл for in для перебора массива символов. Подобная задача была рассмотрена на уроке 18 замыкающие выражения

Создайте функцию, которая принимает в качестве параметров строку и блок замыкания. Логика этой функции заключается в том чтобы из строки сделать массив символов. Блок замыкания должен захватывать тип [Character] и вывводить символы на консоль, каждый из которых будет на новой строке. При вызове функции раскройте комплишен.

Что бы сделать из коллекции (строка это тоже коллекция) массив используйте следующий код: Array(коллекция)

				
					func printCharacter(_ someString: String, action: ([Character]) -> Void) {
    let characters = Array(someString)
    action(characters)
}

printCharacter("someString") {characters in
    for character in characters {
        print(character)
    }
}
				
			

Напомню что Void это красивое обозначение круглых скобок, так сказать синтаксический сахар, вы сами выбираете что лучше использовать, мне же просто не приятно глазу видеть множество скобок, поэтому я использую Void, но если вы написали следующим образом, то это тоже отлично:

				
					func printCharacter(_ someString: String, action: ([Character]) -> ()) {
    let characters = Array(someString)
    action(characters)
}
				
			

Функция printCharacter принимает два параметра: строку someString и замыкание action. Сначала функция преобразует переданную строку в массив символов characters, а затем вызывает переданное замыкание action, передавая ему массив символов characters.

В вызове функции printCharacter(“someString”), мы передаем строку “someString” в качестве первого аргумента и замыкание { characters in … } в качестве второго аргумента. В замыкании мы перебираем каждый символ массива characters и выводим его на консоль с помощью функции print.

Таким образом, в результате выполнения данного кода, на консоль будут выведены все символы строки “someString” в отдельных строках.

Если вы не создали свойство characters а сразу передали массив в блок замыкания, это не будет ошибкой. 

Задача 18.5 📚

У функции будет два параметра один с типом словаря [String: Int] а второй с типом блока замыкания: ([Int]) -> (). Внутри функции необходимо сделать из значений словаря массив, используйте для этого перебор по словарю. Далее вы в параметр блока замыкания передаете полученный массив, то есть блок замыкания захватить нужное значение, и вернет это значение в блоке замыкания при вызове функции и в этом блоке замыкания необходимо сделать перебор по массиву

Создайте функцию, которая принимает в качестве параметров Словарь типа [String: Int] и блок замыкания. Данная функция должна создать массив из значений словаря. Блок замыкания должен захватывать массив типа Int и выводить на консоль сумму чисел массива. Вызовите три раза данную функцию, при первом вызове в параметр замыкания передайте функцию (создайте ее сами), во второй вызов передайте в параметр свойство с типом блока замыкания, и в третий вызов просто раскройте комплишен и реализуйте вывод суммы чисел массива. 

Для решения используйте следующий словарь:

				
					let fruitBasket = ["Apple": 25, "Orange": 10, "Pear": 13, "Banana": 9]
				
			
				
					func getSumDictionaryValues(_ values: [String: Int], closure: ([Int]) -> Void) {
	var valuesDict: [Int] = []
	
	for (_, value) in values {
		valuesDict.append(value)
	}
	
	closure(valuesDict)
}

// Функция в качестве замыкания
func findSumOfNumbers(_ numbers: [Int]) {
	var sum = 0
	
	for number in numbers {
		sum += number
	}
	print(sum)
}

// Замыкание в качестве свойства
var calculate: ([Int]) -> Void = { numbers in
	var sum = 0
	
	for number in numbers {
		sum += number
	}
	print(sum)
}

let fruitBasket = ["Apple": 25, "Orange": 10, "Pear": 13, "Banana": 9]

// Вызов функции с разными замыканиями
getSumDictionaryValues(fruitBasket, closure: findSumOfNumbers)
getSumDictionaryValues(fruitBasket, closure: calculate)
getSumDictionaryValues(fruitBasket) { numbers in
	var sum = 0
	
	for number in numbers {
		sum += number
	}
	print(sum)
}
				
			

Данный код содержит функцию getSumDictionaryValues, которая принимает словарь values типа [String: Int] и замыкание closure типа ([Int]) -> Void, которое захватывает массив целых чисел. Функция создает массив целых чисел из значений словаря values и передает его в качестве аргумента в замыкание closure.

Давайте подробней остановимся на самой функции:

				
					func getSumDictionaryValues(_ values: [String: Int], closure: ([Int]) -> Void) {
	var valuesDict: [Int] = []
	
	for (_, value) in values {
		valuesDict.append(value)
	}
	
	closure(valuesDict)
}
				
			

Многие из вас не смогут с ходу придумать такое решение, все дело в том что нет понимания того с чего нужно начать. В решении подобных задач вам поможет визуальное представление, того что нужно сделать.

Например вы пошли в магазин со списком товаров. Вы это функция, а список товаров это словарь, который вы приняли. В качестве ключа товар, а в качестве значения стоимость. После покупок вам нужно отчитаться перед семьей и предъявить им общую сумму затрат.  И вот вы идете по рынку и набираете товары (запускаете цикл for), вам нужно каждую из стоимостей запомнить, что бы в дальнейшем посчитать общую стоимость. Поэтому вы достаете чистый блокнот (пустой массив типа Int) что бы где-то фиксировать все свои затраты, то есть вы покупаете товар и в блокноте пишете стоимость купленного товара, через запятую.

Приходите домой с этим списком, садитесь за стол (вызов метода и раскрытие блока замыкания) у вас перед глазами список и вы берете каждую стоимость и вносите ее в калькулятор (свойство sum, некий счетчик). после того как расчеты будут произведены, вы показываете семье результат. 

Главную мысль которую вы должны вынести из моего жизненного примера, это то что, когда вам нужно из одного элемента сделать другой, например из массива общую сумму или из словаря массив, то вы должны для начала создать какое то хранилище для вашего нового значения (для массива или для общей суммы), то есть то место куда вы будете сохранять результат в памяти в нашем случае в качестве памяти выступал блокнот и калькулятор. 

Возвращаемся к разбору нашего решения и после функции у нас определены два разных варианта замыкания:

findSumOfNumbers – это функция, которая принимает массив целых чисел;

calculate – это замыкание, которое делает то же самое.

Далее, функция getSumDictionaryValues вызывается трижды с различными замыканиями в качестве параметра closure. На каждом вызове функция создает массив целых чисел из значений словаря fruitBasket и передает его в качестве аргумента в замыкание. Когда срабатывает блок замыкания на консоль мы выводим сумма чисел в массиве.

Развивайся вместе с нами

Развивайся вместе с нами