이름이 없는(익명) 함수
클로져와 함수는 기능은 동일 한테 형태만 다르다고 생각 하면됨
// 함수의 정의
func aFunction(str: String) -> String {
return "Hello, \(str)"
}
// 클로저의 형태
let _ = {(str: String) -> String in
return "Hello, \(str)"
}
// 함수의 형태
func add(a: Int, b: Int) -> Int {
let result = a + b
return result
}
// 클로저의 형태
let _ = {(a: Int, b: Int) -> Int in
let result = a + b
return result
}
func closureParamFunction(closure: () -> ()) {
print("프린트 시작")
closure()
}
func printSwiftFunction() { // 함수를 정의
print("프린트 종료")
}
let printSwift = { () -> () in // 클로저를 정의
print("프린트 종료")
}
closureParamFunction(closure: printSwiftFunction)
closureParamFunction(closure: printSwift)
closureParamFunction(closure: { () -> () in
print("프린트 종료") // 본래 정의된 함수를 실행시키면서, 클로저를 사후적으로 정의 가능
}) // (활용도가 늘어남)
closureParamFunction(closure: { () -> () in
print("프린트 종료 - 1")
print("프린트 종료 - 2")
})
func closureCaseFunction(a: Int, b: Int, closure: (Int) -> Void) {
let c = a + b
closure(c)
}
closureCaseFunction(a: 5, b: 2, closure: { (n) in // 사후적 정의
print("이제 출력할께요: \(n)")
})
closureCaseFunction(a: 5, b: 2) {(number) in // 사후적 정의
print("출력할까요? \(number)")
}
closureCaseFunction(a: 5, b: 3) { (number) in // 사후적 정의
print("출력")
print("출력")
print("출력")
print("값: \(number)")
}
func closureParamFunction(closure: () -> Void) {
print("프린트 시작")
closure()
}
// 2) 함수를 실행할때 클로저 형태로 전달
// 함수의 마지막 전달 인자(아규먼트)로 클로저 전달되는 경우, 소괄호를 생략 가능
closureParamFunction(closure: {
print("프린트 종료")
})
closureParamFunction(closure: ) { // 소괄호를 앞으로 가져오기
print("프린트 종료")
}
closureParamFunction() { // 아규먼트 생략가능
print("프린트 종료")
}
// 소괄호를 아예 생략할 수 있다.
// ==> 아래 형태가 함수를 실행하고 마지막 아규먼트로 클로저를 전달했다는 형태에 익숙해져야함
closureParamFunction {
print("프린트 종료")
}
vc.dismiss(animated: true) {
print("화면을 닫는 것을 완료했습니다.")
}
(클로저의 경계에서 코드가 헷갈릴 가능성이 있었음)
func multipleClosure(first: () -> (), second: () -> (), third: () -> ()) {
first()
second()
third()
}
// 기존 방식에서는 마지막 클로저만 트레일링 클로저로 쓸 수 있었음
// (클로저의 경계에서 코드가 헷갈릴 가능성이 있었음)
multipleClosure(first: {
print("1")
}, second: {
print("2")
}) {
print("3")
}
multipleClosure {
print("mutil-1")
} second: {
print("mutil-2")
} third: {
print("mutil-3")
}
// 아규먼트 레이블을 생략하는 경우
func multipleClosure2(first: () -> (), _ second: () -> (), third: () -> ()) {
first()
second()
third()
}
// 아큐먼트 레이블을 생략하지 못함
multipleClosure2 {
print("1")
} _: {
print("2")
} third: {
print("3")
}
구분 | 값형식 | 참조형식 |
---|---|---|
타입 | Value Type | Reference Type |
메모리상의 저장 위치 | 필요시에 항상 메모리의 값이 복사되어 전달 값의 저장; Stack | 필요시에 항상 메모리의 주소를 전달 값의 저장 ; Heap(주소를 Stack에 저장) |
메모리 관리 방식 | 값이 들어 있는 스택의 스코프가 종료되면 메모리에서 자동 제거 | RC(Reference Counting)을 통해 메모리를 관리 Swift에서 사용하는 ARC 모델 |
각 형식의 타입 예시 | 스위프트 기본 타입( Int, String ....) 튜플 , 구조체 , 열거형 , 컬렉션 등 | 클래스 , 클료져 |
함수 내에서 함수를 실행하고, 값을 리턴하는 일반적인 함수
아래와 같은 경우, 중첩함수로 이루어져 있고
내부 함수 외부에 계속 사용해야하는 값이 있기 때문에 캡처 현상이 발생
(함수/클로저를 변수에 저장하는 시점에 캡처) ==> 클로저도 레퍼런스 타입
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)
calculate(number: 20)
calculate(number: 30)
func calculateFunc() -> ((Int) -> Int) {
var sum = 0
func square(num: Int) -> Int {
sum += (num * num)
return sum
}
return square
}
// 변수에 저장하는 경우(Heap 메모리에 유지)
var squareFunc = calculateFunc()
squareFunc(10)
squareFunc(20)
squareFunc(30)
// 변수에 저장하지 않는 경우
// (Heap메모리에 유지하지 않음)
//calculateFunc()(10)
//calculateFunc()(20)
//calculateFunc()(30)
// 레퍼런스 타입
var dodoFunc = squareFunc
dodoFunc(20)
원칙적으로 함수의 실행이 종료되면 파라미터로 쓰이는 클로저도 제거됨
@escaping 키워드는 클로저를 제거하지 않고 함수에서 탈출시킴(함수가 종료되어도 클로저가 존재하도록 함)
클로저가 함수의 실행흐름(스택프레임)을 벗어날 수 있도록 함
클로저가 함수의 실행흐름(스택프레임)을 벗어날 수 있도록 함
클로저를 외부변수에 저장 (@escaping 필요)
어떤 함수의 내부에 존재하는 클로저(함수)를 외부 변수에 저장
GCD (비동기 코드의 사용)
var aSavedFunction: () -> () = { print("출력") }
//aSavedFunction()
func performEscaping2(closure: @escaping () -> ()) {
aSavedFunction = closure // 클로저를 실행하는 것이 아니라 aSavedFunction 변수에 저장
//closure()
}
//aSavedFunction()
performEscaping2(closure: { print("다르게 출력") })
//aSavedFunction()
func performEscaping1(closure: @escaping (String) -> ()) {
var name = "홍길동"
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { //1초뒤에 실행하도록 만들기
closure(name)
}
}
performEscaping1 { str in
print("이름 출력하기: \(str)")
}
클로저 앞에 @autoclosure 키워드 사용(파라미터가 없는 클로저만 가능)
일반적으로 클로저 형태로 써도되지만, 너무 번거로울때 사용
번거로움을 해결해주지만, 실제 코드가 명확해 보이지 않을 수 있으므로 사용 지양(애플 공식 문서)
잘 사용하지 않음. 읽기위한 문법
autoclosure는 기본적으로 non-ecaping 특성을 가지고 있음
// 클로저 앞에 @autoclosure 키워드 사용(파라미터가 없는 클로저만 가능)
func someFuction(closure: @autoclosure () -> Bool) {
if closure() {
print("참입니다.")
} else {
print("거짓입니다.")
}
}
var num = 1
// 실제로 함수를 사용하려고 하면
//someFuction(closure: <#T##Bool#>)
someFuction(closure: num == 1)
// autoclosure는 기본적으로 non-ecaping 특성을 가지고 있음
func someAutoClosure(closure: @autoclosure @escaping () -> String) {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
print("소개합니다: \(closure())")
}
}
someAutoClosure(closure: "제니")
함수 내에서 함수를 실행하고, 값을 리턴하는 일반적인 함수
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)
calculate(number: 20)
calculate(number: 30)
내부 함수 외부에 계속 사용해야하는 값이 있기 때문에 캡처 현상이 발생
(함수/클로저를 변수에 저장하는 시점에 캡처) ==> 클로저도 레퍼런스 타입
func calculateFunc() -> ((Int) -> Int) {
var sum = 0
func square(num: Int) -> Int {
sum += (num * num)
return sum
}
return square
}
// 함수를 변수에 할당하는 경우
// (Heap 메모리에 유지를 해야함. 즉, 함수라 하더라도 클로저 방식으로 동작)
var squareFunc = calculateFunc()
squareFunc(10)
squareFunc(20)
squareFunc(30)
파라미터가 없는 경우
{ [캡처리스트] in
}
파라미터가 있는 경우
{ [캡처리스트] (파라미터) -> 리턴형 in
}
var num = 1
let valueCaptureClosure = {
print("밸류값 출력(캡처): \(num)")
}
num = 7
valueCaptureClosure() // 몇을 출력할까요?
// 밸류타입의 참조(메모리주소)를 캡처함
// (즉, 값 자체를 복사해서 가지고 있는 것이 아니고, num의 주소를 캡처해서 계속 사용)
num = 1
valueCaptureClosure()
let valueCaptureListClosure = { [num] in // 캡처리스트에서 밸류(value) 타입 캡처
print("밸류값 출력(캡처리스트): \(num)")
}
num = 7
valueCaptureListClosure() // 몇을 출력할까요?
class SomeClass {
var num = 0
}
var x = SomeClass()
var y = SomeClass()
print("참조 초기값(시작값):", x.num, y.num)
let refTypeCapture = { [x] in
print("참조 출력값(캡처리스트):", x.num, y.num)
}
/**============================================
x - (참조타입) 주소값 캡처, x를 직접참조로 가르킴
y - 변수를 캡처해서, y변수를 가르킴(간접적으로 y도 동일)
==============================================**/
x.num = 1
y.num = 1
//x = SomeClass()
//y = SomeClass()
print("참조 초기값(숫자변경후):", x.num, y.num) // 1, 1
refTypeCapture() // 1, 1 (Not) 0, 1
print("참조 초기값(클로저실행후):", x.num, y.num) // 1, 1
var z = SomeClass()
let refTypeCapture1 = { [weak z] in
print("참조 출력값(캡처리스트):", z?.num)
}
refTypeCapture1() // Optional(0)
let refTypeCapture2 = { [unowned z] in
print("참조 출력값(캡처리스트):", z.num)
}
refTypeCapture2() // 0
var s = SomeClass()
let captureBinding = { [z = s] in // 내부에서 변수명 바꿔서 사용가능 (외부변수와 헷갈리는 것을 방지)
print("바인딩의 경우:", z.num)
}
let captureWeakBinding = { [weak z = s] in
print("Weak 바인딩 경우:", z?.num)
}
captureBinding()
captureWeakBinding()
class Dog {
var name = "초코"
func doSomething() {
// 비동기적으로 실행하는 클로저
// 해당 클로저는 오래동안 저장할 필요가 있음 ==> 새로운 스택을 만들어서 실행하기 때문
DispatchQueue.global().async {
print("나의 이름은 \(self.name)입니다.")
}
}
}
var choco = Dog()
choco.doSomething()
class Person {
let name = "홍길동"
func sayMyName() {
print("나의 이름은 \(name)입니다.")
}
func sayMyName1() {
DispatchQueue.global().async {
print("나의 이름은 \(self.name)입니다.")
}
}
func sayMyName2() {
DispatchQueue.global().async { [weak self] in
print("나의 이름은 \(self?.name)입니다.")
}
}
func sayMyName3() {
DispatchQueue.global().async { [weak self] in
guard let weakSelf = self else { return } // 가드문 처리 ==> 객체없으면 일종료
print("나의 이름은 \(weakSelf.name)입니다.(가드문)")
}
}
}
let person = Person()
person.sayMyName()
person.sayMyName1()
person.sayMyName2()
//person.sayMyName3()