클로저의 메모리 구조
- 컴파일된 명령어의 주소가 힙에 저장됨.
- 클로저(함수)가 실제 실행되는 건, 스택프레임에서 동작
- 함수의 직접적인 실행은 스택프레임에서 실행된다.
클로저의 캡처
- 클로저의 Capturing Value
- 클로저 외부에 존재하는 stored 변수를 계속 사용해야 함
- 캡쳐현상이 발생하니 유의해서 써야함
- 클로저에서 저장할 필요가 있는 값을 캡쳐해서 (인스턴스와 비슷한 방식으로) 값을 저장
- 값을 저장 또는 참조를 저장
var stored = 0
let closure = { (number: Int) -> Int in
// 클로저에서 stored의 주소를 가짐
stored += number
return stored
}
closure(3) // stored : 3
closure(4) // stored : 7
- 일반적인 함수
// 함수 내에서 함수를 실행하고, 값을 리턴하는 일반적인 함수
func calculate(number: Int) -> Int {
var sum = 0
func square(num: Int) -> Int {
sum += (num * num)
return sum
}
let result = square(num: number)
return result
}
calculate(number: 10) // return = 100
calculate(number: 20) // return = 400
calculate(number: 30) // return = 900
- 중첩함수에서 내부 함수를 리턴하는 함수의 구조
- 내부 함수를 리턴하는 경우(클로저와 동일한 캡처현상)
- ⭐️ 함수를 변수에 할당하거나
- ⭐️ 클로저를 사용하는 경우
- 힙(Heap)에 해당 메모리주소를 저장 및 (외부의) 필요한 변수를 캡처
func calculateFunc() -> ((Int) -> Int) {
var sum = 0
// square은 sum을 지속적으로 사용하기 위해 캡처함
func square(num: Int) -> Int {
sum += (num * num)
return sum
}
return square
}
// 변수에 저장하는 경우 (Heap 메모리에 유지)
var squareFunc = calculateFunc()
squareFunc(10) // 100
squareFunc(20) // 500
squareFunc(30) // 1400
// 변수에 저장하지 않는 경우 (Heap 메모리에 유지)
calculateFunc()(10) // 100
calculateFunc()(20) // 400
// dodoFunc도 squareFunc와 동일한 인스턴스를 가르킴
var dodoFunc = squareFunc
dodoFunc(200) // 1800 : 1400 + 400
@escaping, @autoclosure 키워드
- @escaping
- 탈출
- 클로저의 실행이 본래 함수를 벗어나서도 실행되도록 하는 키워드
- 내부 클로저를 외부 변수에 저장
- GCD (비동기코드 사용)
- @escaping 키워드가 필요 없는 경우 (굳이 Heap영역에 저장이 필요없음)
- 함수내부에서 단순하게 실행하고 종료할때
- [클로저를 힙에 저장할 필요 없음]
- @escaping 키워드 (Heap영역에 저장해서 사용해야함 - 더 오래 유지할 필요)
- 함수의 실행을 벗어나서도 사용 (메모리 관리도 필요)
- 나중에 변수에 담아놓고 사용하는 경우
var aSavedFunction: () -> () = { print("출력") }
func performEscaping2(closure: () -> ()) {
aSavedFunction = clousre
}
aSavedFunction() // 출력
performEscaping2(closure: { print("다르게 출력") })
aSavedFunction() // 다르게 출력
- @autoclosure 키워드
- 자동으로 closure을 만들어주는 역할
- 함수의 파라미터 중 클로저 타입에 @autoclosure 키워드를 붙이는 이유
- 클로저 앞에 @autoclosure 키워드 사용(파라미터가 없는 클로저만 가능)
- 일반적으로 클로저 형태로 써도되지만, 너무 번거로울때 사용
- 번거로움을 해결해주지만, 실제 콛가 명확해 보이지 않을 수 있으므로 사용 지양(애플 공식 문서)
- 잘 사용하지 않음. 읽기위한 문법
- 기본적으로 non-escaping 특성을 가지고 있음
func someFunction(closure: @autoclosure () -> Bool) {
if closure() {
print("참입니다.")
} else {
print("거짓입니다.")
}
}
var num = 1
//실제로 함수를 사용하려고 하면
// someFunction(closure: Bool)
someFunction(closure: num == 1)
// == someFunction(closure: { num > 1 })
func someAutoClosure(closure: @autoclosure @escaping () -> String) {
DispatchQueue.main.asyncAfer(deadline: .now() + 3) {
print("소개합니다: \(closure())")
}
}
someAutoClosure(closure: "제니")
클로저의 사용법
- 실제 프로젝트에서 많이 사용하는 방식의 이해
- [속성의 선언과 클로저 실행문]
- 실제 프로젝트에서 스토리보드가 아닌 코드로 프로젝트를 진행시 속성(변수) 선언을 클로저에 직접 실행해서 해당 타입을 반환하는 위와 같은 형식으로 많이 사용
- 코드로 프로젝트 작성시, 연관성이 있는 코드를 묶어놓음으로 가독성이 높아짐
- 클로저(함수)를 실행해서 해당 타입의 설정에 대한 내용까지 완료한 후에 반환한 후, 해당 타입에 담기
- 설정까지 완료된 UITextField 반환
- 실제 프로젝트에서 스토리보드가 아닌 코드로 프로젝트를 진행시 속성(변수) 선언을 클로저에 직접 실행해서 해당 타입을 반환하는 위와 같은 형식으로 많이 사용

- 고차 함수
- Higher-order function
- “함수”를 파라미터로 사용하거나, 함수실행의 결과를 “함수”로 리턴하는 함수
- map 함수
- 각 아이템을 매핑해서 변형할때 사용
- **배열.map { 클로저 }**
- 기존의 배열의 각 아이템을 새롭게 매핑해서 (매핑방식은 클로저가 제공) 새로운 배열을 리턴
- 클로저가 매핑될 방식을 정한다.
- 서버에서 데이터를 가져올 때 데이터를 쉽게 변경할 수 있음
let numbers = [1, 2, 3, 4, 5]
// numbers.map(T##transform: (Int) throws -> T##(Int) throws -> T)
var aaa = numbers.map { num in
return num * 1000
}
print(aaa) // [1000, 2000, 3000, 4000, 5000]
var newNumbers = numbers.map { (num) in
return "숫자: \(num)"
}
newNumbers = numbers.map { "숫자: \($0)" }
print(newNumbers) // ["숫자: 1", ... , "숫자: 5"]
- filter 함수
- 각 아이템을 필터링해서 걸러낼대 사용
- 배열.filter { 클로저 }
- 기존의 배열의 각 아이템을 클로저가 제공하는 논리조건에 맞추어 확인후, true(참)을 만족하는 아이템으로 (필터링하여) 새로운 배열을 리턴
let names = ["Apple", "Black", "Circle", "Dream", "Blue"]
// names.filter(isIncluded: (String) throws -> Bool)
// 참과 거짓을 판별하는 논리를 클로저에 구현해야 함
var newNames = names.filter { name in
return name.contains("B")
}
print(newNames)
// ["Black", "Blue"]
let arr = [1, 2, 3, 4, 5, 6, 7, 8]
var evenNumArr = arr.filter { num in
return num % 2 == 0
}
evenNumArr = arr.filter { $0 % 2 == 0 }
print(evenNumArr)
// [2, 4, 6, 8]
// 함수로 전달할 땐
func isEven(_ i: Int) -> Bool {
return i % 2 == 0
}
let evens = arr.filter(isEven)
// filter 2번 적용도 가능
evenNumsArr = arr.filter { $0 % 2 == 0 }.filter { $0 < 5 }
print(evenNumsArr) // [2, 4]
- reduce 함수
- 각 아이템을 결합해서 단 하나의 값으로 리턴
- 배열.reduce(0) { 클로저 }
- 기존 배열 등의 각 아이템을 클로저가 제공하는 방식으로 결합해서 마지막 결과값을 리턴 (초기값 제공할 필요 O)
var numbersArr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// numbersArr.reduce(initialResult: Result, nextPartialResult: (Result, Int) throws -> Result)
var resultSum = numbersArr.reduce(0) { (sum, num) in
return sum + num
}
print(resultSum) // 55
resultSum = numbersArray.reduce(100) { $0 - $1 }
print(resultSum) // 45
- map, filter, reduce의 활용
numbersArr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
var newResult = numbersArr.filter { $0 % 2 != 0 }
.map { $0 * $0 }
.reduce(0) { $0 + $1 }
print(newResult) // [1, 9, 25, 49, 81]을 다 더한 165
기타 고차함수
- forEach함수
- 각 아이템을 활용, 특정 작업을 실행
- 배열.forEach { 클로저 }
- 기존 배열의 각 아이템을 활용해서 특정작업을 실행(작업방식은 클로저가 제공)
let immutableArray = [1, 2, 3, 4, 5]
immutableArray.forEach { num in
print(num)
}
immutableArray.forEach { print("숫자: \($0)") }
- compactMap 함수
- 각 아이템을 매핑해서 변형하되, 옵셔널의 경우는 제거
- 배열.compactMap { 클로저 }
- 기존 배열의 각 아이템을 새롭게 매핑해서 변형하되 옵셔널 요소는 제거하고, 새로운 배열을 리턴(매핑방식은 클로저가 제공)
- 옵셔널 바인딩의 기능까지 내장
- map + 옵셔널제거
var stringArr = [String?] = ["A", nil, "B", nil, "C"]
var newStringArray = stringArr.compactMap { $0 }
print(newStringArray) // ["A", "B", "C"]
let numbers = [-2, -1, 0, 1, 2]
var positiveNumbers = numbers.compactMap { $0 >= 0 ? $0 : nil }
print(positiveNumbers)
// 사실 이런 경우는 filter로 가능
// numbers.filter { $0 >= 0 }
// 아래와 같이 compactMap 구현 가능
newStringArray = stringArr.filter { $0 != nil }.map { $0! }
print(newStringArray)
- flatMap 함수
- 내부 중첩을 제거
- 배열.flatMap { 클로저 }
- 중첩된 배열의 각 아이템(배열)을 새롭게 매핑하면서 (매핑방식은 클로저가 제공) 내부의 중첩된 배열을 제거하고 리턴
- 중첩배열을 flat하게 매핑
var nestedArray = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
print(nestedArray.flatMap { $0 }) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
var newNnestedArray = [[[1,2,3], [4,5,6], [7, 8, 9]], [[10, 11], [12, 13, 14]]]
var numbersArray = newNnestedArray
.flatMap { $0 }
.flatMap { $0 }
print(numbersArray) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
반응형
'iOS > Swift' 카테고리의 다른 글
Swift No.26 (0) | 2024.02.08 |
---|---|
Swift No.25 (0) | 2024.02.03 |
Swift No.23 (1) | 2024.01.31 |
Swift No.22 (0) | 2024.01.30 |
Swift No.21 (1) | 2024.01.28 |
클로저의 메모리 구조
- 컴파일된 명령어의 주소가 힙에 저장됨.
- 클로저(함수)가 실제 실행되는 건, 스택프레임에서 동작
- 함수의 직접적인 실행은 스택프레임에서 실행된다.
클로저의 캡처
- 클로저의 Capturing Value
- 클로저 외부에 존재하는 stored 변수를 계속 사용해야 함
- 캡쳐현상이 발생하니 유의해서 써야함
- 클로저에서 저장할 필요가 있는 값을 캡쳐해서 (인스턴스와 비슷한 방식으로) 값을 저장
- 값을 저장 또는 참조를 저장
var stored = 0
let closure = { (number: Int) -> Int in
// 클로저에서 stored의 주소를 가짐
stored += number
return stored
}
closure(3) // stored : 3
closure(4) // stored : 7
- 일반적인 함수
// 함수 내에서 함수를 실행하고, 값을 리턴하는 일반적인 함수
func calculate(number: Int) -> Int {
var sum = 0
func square(num: Int) -> Int {
sum += (num * num)
return sum
}
let result = square(num: number)
return result
}
calculate(number: 10) // return = 100
calculate(number: 20) // return = 400
calculate(number: 30) // return = 900
- 중첩함수에서 내부 함수를 리턴하는 함수의 구조
- 내부 함수를 리턴하는 경우(클로저와 동일한 캡처현상)
- ⭐️ 함수를 변수에 할당하거나
- ⭐️ 클로저를 사용하는 경우
- 힙(Heap)에 해당 메모리주소를 저장 및 (외부의) 필요한 변수를 캡처
func calculateFunc() -> ((Int) -> Int) {
var sum = 0
// square은 sum을 지속적으로 사용하기 위해 캡처함
func square(num: Int) -> Int {
sum += (num * num)
return sum
}
return square
}
// 변수에 저장하는 경우 (Heap 메모리에 유지)
var squareFunc = calculateFunc()
squareFunc(10) // 100
squareFunc(20) // 500
squareFunc(30) // 1400
// 변수에 저장하지 않는 경우 (Heap 메모리에 유지)
calculateFunc()(10) // 100
calculateFunc()(20) // 400
// dodoFunc도 squareFunc와 동일한 인스턴스를 가르킴
var dodoFunc = squareFunc
dodoFunc(200) // 1800 : 1400 + 400
@escaping, @autoclosure 키워드
- @escaping
- 탈출
- 클로저의 실행이 본래 함수를 벗어나서도 실행되도록 하는 키워드
- 내부 클로저를 외부 변수에 저장
- GCD (비동기코드 사용)
- @escaping 키워드가 필요 없는 경우 (굳이 Heap영역에 저장이 필요없음)
- 함수내부에서 단순하게 실행하고 종료할때
- [클로저를 힙에 저장할 필요 없음]
- @escaping 키워드 (Heap영역에 저장해서 사용해야함 - 더 오래 유지할 필요)
- 함수의 실행을 벗어나서도 사용 (메모리 관리도 필요)
- 나중에 변수에 담아놓고 사용하는 경우
var aSavedFunction: () -> () = { print("출력") }
func performEscaping2(closure: () -> ()) {
aSavedFunction = clousre
}
aSavedFunction() // 출력
performEscaping2(closure: { print("다르게 출력") })
aSavedFunction() // 다르게 출력
- @autoclosure 키워드
- 자동으로 closure을 만들어주는 역할
- 함수의 파라미터 중 클로저 타입에 @autoclosure 키워드를 붙이는 이유
- 클로저 앞에 @autoclosure 키워드 사용(파라미터가 없는 클로저만 가능)
- 일반적으로 클로저 형태로 써도되지만, 너무 번거로울때 사용
- 번거로움을 해결해주지만, 실제 콛가 명확해 보이지 않을 수 있으므로 사용 지양(애플 공식 문서)
- 잘 사용하지 않음. 읽기위한 문법
- 기본적으로 non-escaping 특성을 가지고 있음
func someFunction(closure: @autoclosure () -> Bool) {
if closure() {
print("참입니다.")
} else {
print("거짓입니다.")
}
}
var num = 1
//실제로 함수를 사용하려고 하면
// someFunction(closure: Bool)
someFunction(closure: num == 1)
// == someFunction(closure: { num > 1 })
func someAutoClosure(closure: @autoclosure @escaping () -> String) {
DispatchQueue.main.asyncAfer(deadline: .now() + 3) {
print("소개합니다: \(closure())")
}
}
someAutoClosure(closure: "제니")
클로저의 사용법
- 실제 프로젝트에서 많이 사용하는 방식의 이해
- [속성의 선언과 클로저 실행문]
- 실제 프로젝트에서 스토리보드가 아닌 코드로 프로젝트를 진행시 속성(변수) 선언을 클로저에 직접 실행해서 해당 타입을 반환하는 위와 같은 형식으로 많이 사용
- 코드로 프로젝트 작성시, 연관성이 있는 코드를 묶어놓음으로 가독성이 높아짐
- 클로저(함수)를 실행해서 해당 타입의 설정에 대한 내용까지 완료한 후에 반환한 후, 해당 타입에 담기
- 설정까지 완료된 UITextField 반환
- 실제 프로젝트에서 스토리보드가 아닌 코드로 프로젝트를 진행시 속성(변수) 선언을 클로저에 직접 실행해서 해당 타입을 반환하는 위와 같은 형식으로 많이 사용

- 고차 함수
- Higher-order function
- “함수”를 파라미터로 사용하거나, 함수실행의 결과를 “함수”로 리턴하는 함수
- map 함수
- 각 아이템을 매핑해서 변형할때 사용
- **배열.map { 클로저 }**
- 기존의 배열의 각 아이템을 새롭게 매핑해서 (매핑방식은 클로저가 제공) 새로운 배열을 리턴
- 클로저가 매핑될 방식을 정한다.
- 서버에서 데이터를 가져올 때 데이터를 쉽게 변경할 수 있음
let numbers = [1, 2, 3, 4, 5]
// numbers.map(T##transform: (Int) throws -> T##(Int) throws -> T)
var aaa = numbers.map { num in
return num * 1000
}
print(aaa) // [1000, 2000, 3000, 4000, 5000]
var newNumbers = numbers.map { (num) in
return "숫자: \(num)"
}
newNumbers = numbers.map { "숫자: \($0)" }
print(newNumbers) // ["숫자: 1", ... , "숫자: 5"]
- filter 함수
- 각 아이템을 필터링해서 걸러낼대 사용
- 배열.filter { 클로저 }
- 기존의 배열의 각 아이템을 클로저가 제공하는 논리조건에 맞추어 확인후, true(참)을 만족하는 아이템으로 (필터링하여) 새로운 배열을 리턴
let names = ["Apple", "Black", "Circle", "Dream", "Blue"]
// names.filter(isIncluded: (String) throws -> Bool)
// 참과 거짓을 판별하는 논리를 클로저에 구현해야 함
var newNames = names.filter { name in
return name.contains("B")
}
print(newNames)
// ["Black", "Blue"]
let arr = [1, 2, 3, 4, 5, 6, 7, 8]
var evenNumArr = arr.filter { num in
return num % 2 == 0
}
evenNumArr = arr.filter { $0 % 2 == 0 }
print(evenNumArr)
// [2, 4, 6, 8]
// 함수로 전달할 땐
func isEven(_ i: Int) -> Bool {
return i % 2 == 0
}
let evens = arr.filter(isEven)
// filter 2번 적용도 가능
evenNumsArr = arr.filter { $0 % 2 == 0 }.filter { $0 < 5 }
print(evenNumsArr) // [2, 4]
- reduce 함수
- 각 아이템을 결합해서 단 하나의 값으로 리턴
- 배열.reduce(0) { 클로저 }
- 기존 배열 등의 각 아이템을 클로저가 제공하는 방식으로 결합해서 마지막 결과값을 리턴 (초기값 제공할 필요 O)
var numbersArr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// numbersArr.reduce(initialResult: Result, nextPartialResult: (Result, Int) throws -> Result)
var resultSum = numbersArr.reduce(0) { (sum, num) in
return sum + num
}
print(resultSum) // 55
resultSum = numbersArray.reduce(100) { $0 - $1 }
print(resultSum) // 45
- map, filter, reduce의 활용
numbersArr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
var newResult = numbersArr.filter { $0 % 2 != 0 }
.map { $0 * $0 }
.reduce(0) { $0 + $1 }
print(newResult) // [1, 9, 25, 49, 81]을 다 더한 165
기타 고차함수
- forEach함수
- 각 아이템을 활용, 특정 작업을 실행
- 배열.forEach { 클로저 }
- 기존 배열의 각 아이템을 활용해서 특정작업을 실행(작업방식은 클로저가 제공)
let immutableArray = [1, 2, 3, 4, 5]
immutableArray.forEach { num in
print(num)
}
immutableArray.forEach { print("숫자: \($0)") }
- compactMap 함수
- 각 아이템을 매핑해서 변형하되, 옵셔널의 경우는 제거
- 배열.compactMap { 클로저 }
- 기존 배열의 각 아이템을 새롭게 매핑해서 변형하되 옵셔널 요소는 제거하고, 새로운 배열을 리턴(매핑방식은 클로저가 제공)
- 옵셔널 바인딩의 기능까지 내장
- map + 옵셔널제거
var stringArr = [String?] = ["A", nil, "B", nil, "C"]
var newStringArray = stringArr.compactMap { $0 }
print(newStringArray) // ["A", "B", "C"]
let numbers = [-2, -1, 0, 1, 2]
var positiveNumbers = numbers.compactMap { $0 >= 0 ? $0 : nil }
print(positiveNumbers)
// 사실 이런 경우는 filter로 가능
// numbers.filter { $0 >= 0 }
// 아래와 같이 compactMap 구현 가능
newStringArray = stringArr.filter { $0 != nil }.map { $0! }
print(newStringArray)
- flatMap 함수
- 내부 중첩을 제거
- 배열.flatMap { 클로저 }
- 중첩된 배열의 각 아이템(배열)을 새롭게 매핑하면서 (매핑방식은 클로저가 제공) 내부의 중첩된 배열을 제거하고 리턴
- 중첩배열을 flat하게 매핑
var nestedArray = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
print(nestedArray.flatMap { $0 }) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
var newNnestedArray = [[[1,2,3], [4,5,6], [7, 8, 9]], [[10, 11], [12, 13, 14]]]
var numbersArray = newNnestedArray
.flatMap { $0 }
.flatMap { $0 }
print(numbersArray) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
반응형
'iOS > Swift' 카테고리의 다른 글
Swift No.26 (0) | 2024.02.08 |
---|---|
Swift No.25 (0) | 2024.02.03 |
Swift No.23 (1) | 2024.01.31 |
Swift No.22 (0) | 2024.01.30 |
Swift No.21 (1) | 2024.01.28 |