안녕하세요. 린다입니다.
저번 (https://sy-catbutler.tistory.com/51)
글에 이어서 마저 정리해볼게요!!
근데 제가 이미 최적화를 정리해놓았거든요^__^ ....
이 게시물보다 더 자세하고 이전 글 예제와 동일하게 다시 적어볼게요.
후행클로저와 문법 최적화
먼저 후행 클로저가 무엇이냐면..
함수에 대한 마지막 매개변수가 클로저인 경우에는 후행클로저라는 특수한 구문을 사용할 수 있습니다!
“구문” 이니까요. 예제를 봐야 이해가 빨라요.
저번 글에서 했던 예제로 계속 해볼게요
func doSomething(_ name: (String) -> (Bool)) {
name("린다")
print("doSomething 내부")
}
doSomething은 String 타입을 받고 Bool을 리턴하는 이름 없는 함수
즉, 클로저 타입의 name 매개변수를 가지고 있는 함수입니다.
해당 함수를 호출 할 때를 정석적으로 보면
func sayHelloa(name: String) {
print("Hello, \\(name)"
}
sayHello("서연")
// sayHello처럼, 정석으로 실행문 작성
doSomething({ (name: String) -> Bool in
print("안녕, \\(name)")
return true
})
doSomething()의 ()안에 클로저를 넣어서 작성하면 됩니다.
근데 저번에 그냥 { } 라고 말했었죠?
사실 실제로 우리가 보는 모든 클로저는 저렇게 안 생겼잖아요?
이건 모두 문법 최적화와 후행클로저 때문이에요!
(클로저의 가독성을 높이기 위해서 위와 같은 방법이 있는데요 냅다 이 개념부터 보고 돌아가면..
이해가 안 가기 때문에 이전 게시물에 대한 사고를 가지고 오셔야 이해가 빠를 거에요.)
아까 함수에 대한 마지막 매개변수가 클로저인 경우 후행클로저로 변환할 수 있다.
고 했잖아요? “후행 클로저” 말 그대로 후에 실행되는 클로저라고 생각하시면 빨라요.
한번 doSomething을 후행 클로저로 바꿔볼게요.
doSomething() { (name: String) -> Bool in
return true
}
지금 뭐가 바뀐거냐면… ({}) -> () {} 으로 바뀐거에요!
풀어서 보면 doSomething의 name은 첫번째 매개변수이자 마지막 매개변수이기
때문에 ({}) -> () {} 으로 변환 가능하다는 것
이렇게가 바로 후행 클로저의 첫단계에요!
근데 지금 doSomething함수는 _ name: string.. argument가 없잖아요?
만약 Argument가 있는 함수라면… or Parameter가 있는 함수라면…
// MARK: - 근데 만약 argument가 있는 함수였다면?
func haveArgument(to name: (String) -> (Bool)) { }
// 정석
haveArgument(to: {(name: String) -> (Bool) in
return true
})
// 후행 클로저로 변환할때는 argument가 생략됨!
haveArgument() {(name: String) -> (Bool) in
return true
}
// MARK: - Parameter만 있는 함수였다면?
func haveParameter(name: (String) -> (Bool)) { }
// 정석
haveParameter(name: {(name: String) -> (Bool) in
return true
})
// 후행 클로저 변환 (파라미터이면서 아규먼트)
haveParameter() { (name: String) -> (Bool) in
return true
}
이게 2번째 단계에요. argument의 생략!
즉 후행 클로저는 마지막 매개변수가 클로저 일때,
함수 바로 뒤에 붙여서 사용하며 Argument 를 생략하는 것을 의미해요!
근데 여기서 한단계 더 나아가서 파라미터의 클로저가 하나인 경우
() {} 이렇게 되기 때문에 ()도 생략이 가능합니다.
여러개일 경우에는 마지막 클로저만 가능하겠죠?
doSomething { (name: String) -> Bool in
print("안녕히가세요, \\(name)")
return true
}
haveParameter { (name: String) -> (Bool) in
return true
}
haveArgument { (name: String) -> (Bool) in
return true
}
파라미터가 여러개인 경우를 확인해볼게요.
// MARK: - 파라미터가 2개인 경우에는?
func doAnything( first: (String) -> (), second: (String) -> ()) {
print("doAnything 내부")
}
// 정석
doAnything(first: { (first: String) -> () in
}, second: { (second: String) -> () in
})
정석을 보면.. 진짜 복잡해 보이죠? 이거를 후행으로 바꿔볼게요.
먼저 마지막 파라미터는 String → () 타입의 함수를 가지는 second에요.
second는 후행으로 변환할 수 있으니 확인해봅시다.
// 마지막 파라미터는 후행으로 변환할 수 있음!!
// 여기서 마지막 파라미터는 second + argument의 생략!
doAnything(first: {(first: String) -> () in
}) { (second: String) -> () in
}
짠. 이렇게 마지막 매개변수를 클로저로 변환할 수 있습니다!
근데.. 사실 우리가 보는 클로저들은 이렇게 안 생겼잖아요?
여기에는 문법 최적화라는 것이 추가적으로 존재합니다.
이전 게시물에도 있긴한데요. 여기 예제로 해야 한번에 이해하기 쉬울 것 같아서
doSomething을 가지고 마저 예제를 들어볼게요.
문법 최적화
- 말 그대로 최적화로 깔끔하게 만들어서 가독성을 높이겠다 이런 말이겠조...
1. 파라미터 “타입”과 리턴 “타입”을 생략할 수 있음
// 위에서 후행 클로저까지 깔끔하게 변환한 doSomething을 보면
doSomething() { (name: String) -> (Bool) in
return true
}
// 여기서 파라미터 타입!! 과 리턴 타입!! 을 생략할 수 있음
// 각 타입들은? String과 Bool 임 그래서..
doSomething { name in
return true
}
// 바로위의 매개변수가 2개인 함수를 보자면.
doAnything(first: {(first) in
}) { (second) in
}
이렇게 “타입”을 생략해 줄 수 있어요. 타입들을 생략하고 파라미터 값만 받아서 쓸 수 있습니다.
파라미터는 함수 안에서 사용되어야 하는 거니까 생략할 수 없으니,
타입들만 생략되었다고 생각하면 조금 쉬울듯합니다.
2. 파라미터 이름과 in 키워드 생략하고, 이름을 축약 인자로 대체
- 축약 인자? $0, $1, $2같이 $+index를 사용하여 Parameter에 순서대로 접근하는 것
doSomething {
print("Hello, \\($0) 단축인자 사용하기!")
return true
}
지금 파라미터 이름과 in을 생략하고
name이라는 매개변수를 축약인자를 사용해서 $0으로 대체한 거에요!
3. 단일 리턴문일 경우, return도 생략한다
단일 리턴문이란? 클로저 내부에 return 구문 하나만 남은 경우!
위의 예제는 print문도 있기 때문에 단일 리턴문은 아니지만..
doSomething { name in
// 이렇게 함수 내부에 return 문 하나만 남은 경우
return true
}
doSomething { name in
true // true만 써도 됨!
}
4. closure가 마지막 파라미터라면 trailing closure로 작성
이제 여기서 후행 클로저의 등장합니다.
doSomething({ (name: String) -> (Bool) in
return true
})
doSomething() { name in // == (name: String) -> Bool in
return true
}
5. () 안에 다른 파라미터가 없다면 () 생략하기
doSomething { name in
return true
}
위에서 파라미터 이름이랑 in도 생략 가능하다고 했죠? 그래서 사실 이렇게도 쓸 수 있어요
doSomething {
print($0)
return true
}
어쨌든 그래서 결론적으로 우리가 정말 정말 많이 쓰는..
// 정석으로 실행문 작성
doSomething({ (name: String) -> Bool in
print("안녕, \\(name)")
return true
})
doSomething( name in
print("안녕, \\(name)")
return true
}
위에서 아래로 바뀌는거랍니다..
func voidSample(closure: () -> ()) {
}
// 정석
voidSample(closure: {
})
// 후행 클로저
voidSample() {
}
voidSample {
}
이것두요!
함수랑 후행이랑 최적화, 파라미터, 아규먼트 등등..
쓰임새가 정말 연관성이 깊고 아 이래서 이렇게 ??? 이런게 정말 많아요
매번 헷갈려서 날잡고 정리하고 블로깅도 완료..!
혹시 틀린 부분 있다면 피드백 부탁드립니다~~!!!!!
'Swift > Swift 기초문법' 카테고리의 다른 글
Swift의 ARC 톺아보기 (feat. CFGetRetainCount) (1) | 2024.04.18 |
---|---|
Swift 기초 문법, 함수와 클로저를 톺아보자 (1) | 2024.02.06 |
DispatchQueue(GCD)를 알아보자2, GCD 사용시 주의해야할 사항 (0) | 2023.11.08 |
DispatchQueue(GCD)를 알아보자, 이제 동기/비동기, 동시/직렬을 곁들인.. (2) | 2023.11.03 |
[Swift 기초문법] Error Handling을 알아보자 (0) | 2023.07.17 |