Swift의 함수는 1급 타입(First Class-type)이다
근데 1급타입이 몬데 ? 아래의 3가지를 만족하면된다.아래의 3개가 1급시민의 조건이고 그것을 충족하는 객체가 1급객체이다.
- 변수와 상수에 함수가 저장될수 있다. 아래에서 보면 increment라는 변수에 함수를 할당해주었다. 그러면 저 변수는 함수 그자체
- 그런데 주의 할게 동일한 이름의 함수가 오버로딩 되어있을때는 타입추론이 안되고 타입 명시를 해주어야함.
- 함수가 다른 함수를 값으로 반환할 수 있다.
func makeIncrementer() -> ((Int) -> Int) {
var answer = 1
func addOne(number: Int) -> Int {
return answer + number
}
return addOne
}
var increment = makeIncrementer()
increment(7)
- 다른 함수의 매개변수로 함수가 들어갈수 있다
func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
for item in list {
if condition(item) {
return true
}
}
return false
}
func lessThanTen(number: Int) -> Bool {
return number < 10
}
var numbers = [20, 19, 7, 12]
hasAnyMatches(list: numbers, condition: lessThanTen)
inout 키워드
swift 함수에서 파라미터는 상수이다. 하지만 C++을 보면 포인터를 넘겨주어 해당하는 값을 바꿔주는 경우도 있었는데 여기서는 못하는 걸까?
ㄴㄴ! 파라미터 앞에 inout이라는 키워드를 붙여주면 된다.
func someFunction(a: inout Int) {
a += 1
}
var x = 7
someFunction(&x)
print(x) // Prints "8"
inout은 그러면 call by reference로 같은 값을 넘겨주는 것일까? 여기서 주의해야 한다. 이 대답은?
ㄴㄴ~ 애플 공식문서에 보면 call by value다.. 어? 어떻게 그러면 x에는 8이 온거지?
1. 함수가 호출될때 인수의 값은 복사된다.
2. 함수의 본문내에서 복사본이 수정된다.
3. 함수가 반환될때 복사본의 값은 기본 인수에 할당된다.
그래서 관찰자가 있는 프로퍼티가 inout파라미터로 전달되는 경우 getter는 함수 호출부분에 setter는 함수반환부분에 호출된다!
그러기에 inout안에 크고 복잡한 데이터를 넣으면 안된다!! 복사하기에 비효율적이라
함수의 목적이 문자열을 직접 포맷하거나 삭제하는 함수와 같이 입력 값을 정확하게 수정하는 경우에만 inout 키워드를 사용하는 것이 좋다
그런데 결국 swift가 추구하는 거랑 달라서 최대한 자제하는 것이 좋다고 한다.
많은 경우, 새로운 값을 반환하거나 변경 사항(클 클래스와 같은)을 자연스럽게 캡슐화하는 구조를 사용하는 것은 함수형 프로그래밍 패러다임과 스위프트의 디자인 원칙에 부합하는 더 나은 접근 방식일 수 있다.라고 어떤 분이 정리해주셨다
func someFunction(a: Int) {
a += 1
}
var x = 7
x = someFunction(x)
print(x) // Prints "8"
차라리 이게 더 낫다고 한다.
클로저
3가지 형태가 있다.
1. 전역함수는 이름을 가지고 어떠한 값도 캡처하지 않는 클로저이다 (우리가 일반적으로 사용하는 함수가 이에 해당함 func요놈)
2. 중첩 함수는 이름을 가지고 둘러싼 함수로부터 값을 캡쳐할수 있는 클로저이다.(함수안에 함수가 있는 경우)
3. 클로저 표현식은 주변 컨텍스트에서 값을 캡쳐할수 있는 경량구문으로 작성된 이름이 없는 클로저이다.
- 1급객체 함수의 특징을 그대로 가지고 있어 마찬가지로 변수나 상수에 대입할 수 있고 함수의 파라미터 타입으로 클로저를 전달할수 있고 반환타입으로도 사용할 수 있다
trailing Closure(후행 클로저)
함수의 마지막 파라미터가 클로저일때 파라미터 값 형식이 아닌 함수 뒤에 붙여 작성하는 문법! ArgumentLabel은 생략된다!
아래의 예시에서 fail이 arugumentLabel
func fetchData(success: () -> (), fail: () -> ()) {
}
fetchData(success: {() -> () in
print("hi")},
fail: {() -> () in
print("FAIOL")})
이걸 이렇게 안쓰고
fetchData(success: {() -> () in
print("success")
}){() -> () in
print("FAIL)}
fetchData(success: {() -> () in
print("success")
}){() -> () in
print("FAIL)}
fetchData(success: {
print("success")
}){
print("FAIL)}
이렇게 써줄수 있다. 여기서 더 줄여서 ()->()요놈들을 더 단순화 하게 되면 마지막 표현처럼 간단하게 사용할수 있다.
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
reversedNames = names.sorted(by: { $0 > $1 } )
reversedNames = names.sorted(by: >)
클로저 메서드에 인수로 전달되기에 Swift는 파라미터타입과 반환되는 타입을 유추할수 있다.
그리고 나서 단일표현클로저는 return 키워드를 생략하여 값을 반환할 수 있다. 그 이유가 클로저에서 Bool타입이 반환되어야 한다는게 명확하기에
그리고 클로저는 shorthand argument names를 제공한다. 그래서 s1, s2 이런걸 쓸필요 없이 $0,$1으로 대체가 가능하다. 또한 클로저 표현식이 본문으로 전체가 구성되기 때문에 in 키워드도 생략할 수 있다.
마지막은 연산자메서들르 이용하여 더 간단하게 ㅍ표현하는 방법이다
@autoclosure
파라미터로 전달된 일반 구문 &함수를 클로저로 Wrapping한다 이게 몬소리냐?
func withoutautoclosure(_ closure: () -> Void) {
closure()
}
withoutautoclosure({print("HI")})
func usingautoclosure(_ closure: @autoclosure () -> Void) {
closure()
}
usingautoclosure(print("hi"))
요롷게 코드가 더 간결해진것을 확인할수 있다. 위와같이 autoclosure를 통해 일반표현의 코드를 클로저 표현의 코드로 변환해서 더 쉬운표현으로 사용 가능하게 한다.
위에서 보이듯 arugment를 가지지 않고 리턴
많은 블로그에서 예로 들어주는 assert가 autoclosure의 예이다.
func goodMorning(morning: Bool, whom: String) {
if morning {
print("Good morning, \(whom)")
}
}
func giveAname() -> String {
print("giveAname() is called")
return "Robert"
}
goodMorning(morning: true, whom: giveAname())
// 출력결과
giveAname() is called
Good morning, Robert
이 블로그에 정리가 굉장히 잘되어있는데 위와같이 저렇게 쓰는것을 morning을 false로 바꿔도 giveAname() is called가 된다.
그러면 나는 false일때는 안불리게 하고 싶어.. 그러면 어떻게 할까
func goodMorning(morning: Bool, whom: () -> String) {
if morning {
print("Good morning, \(whom())")
}
}
func giveAname() -> String {
print("giveAname() is called")
return "Robert"
}
goodMorning(morning: true, whom: giveAname)
// 출력결과
giveAname() is called
Good morning, Robert
goodMorning(morning: false, whom: giveAname)
// 출력결과 없음
이렇게 되면 우리가 원하는데로 사용할수 있나? ㄴㄴ! whom에 string값을 넘겨줄수 없다.
이럴때 autoclosure를 사용해보자
func goodMorning(morning: Bool, whom: @autoclosure () -> String) {
if morning {
print("Good morning, \(whom())")
}
}
func giveAname() -> String {
print("giveAname() is called")
return "Robert"
}
goodMorning(morning: false, whom: giveAname())
goodMorning(morning: true, whom: "Pavel")
// 출력결과
Good morning, Pavel
@escaping
- 이전까지는 함수내부에서 사용되기에 그 함수가 종료되기 전에 무조건 실행되어야 했었다. 하지만 어 ?난 이 함수가 끝나고 어떤 클로저를 실행시키고 싶어 or 중첩함수에서 실행 후 중첩 함수를 리턴받고 싶어하면 사용한다
하지만 여기서 주의해야할 것이 메모리 관리! 함수 종료된후 클로저 실행시키는데 이 클로저가 함수 내부값이용해?
아래 코드를 보면 함수안에서 클로저를 만들어서 변수에 넣어주었는데 isCaptured라는 값이 true로 바뀐것을 볼 수 있다.
이를 isCaptured값이 클로저에 의해 캡쳐 되었다라고 표현한다. reference Capture!
그렇다면 어? 나는 value타입으로 복사해서 사용하고 싶어? 그럴때는 []안에 복사할 값을 넣고 in을 써주면 됨!!! 그런데 주의해야할점은 클로저 선언할 당시의 값을 const value type으로 복사하기에 주의해야한다! 또한 value로 복사를 하기에 클로저 내부에서 위와 같이 변경을 할 수 없다.
'Swift' 카테고리의 다른 글
매크로(Macros) (1) | 2024.03.07 |
---|---|
초기화(initialization.. 편의? 지정?) (0) | 2024.03.05 |
Assertions && Preconditions (0) | 2024.03.02 |
LLVM? Swift가 컴파일되는 과정 Swift 기초 (0) | 2024.03.01 |
Swift 5.9 Updates (2) | 2023.10.10 |