- This topic has 3개 답변, 2명 참여, and was last updated 4 years, 6 months 전에 by 야곰.
-
글쓴이글
-
-
HyunJi참가자
- 글작성 : 9
- 답글작성 : 10
@escaping
@escaping 속성은 인자 값으로 전달된 클로저를 저장해 두었다가, 나중에 다른 곳에서도 실행할 수 있도록 허용해주는 속성이다.
func callback(fn: () -> Void) { fn() } callback { print("Closure가 실행되었습니다.") }
정의된 함수 callback(fn:)은 매개변수를 통해 전달된 클로저를 함수 내부에서 실행하는 역할을 한다.
func callback(fn: () -> Void) { let f = fn // 클로저를 상수 f에 대입 f() // 대입된 클로저를 실행 }
만약 함수를 위와 같이 바꾼다면 오류가 출력된다.
전달된 클로저를 변수에 대입한 후 실행하는 것이 안된다는 것이다.오류의 내용은 Non-escaping 파라미터인 ‘fn’은 오직 직접 호출하는 것만 가능하다는 의미인데, 어떤 뜻일까?
스위프트에서 함수의 인자값으로 전달되는 클로저는 기본적으로 탈출 불가(non-escaping)의 성격을 가진다.
이는 해당 클로저를 함수내에서 직접 실행을 위해서만 사용해야 하는 것을 의미하고, 이 때문에 함수 내부여도 변수나 상수에 대입할 수 없다.
변수나 상수에 대입하는 것을 허용한다면 내부 함수를 통한 캡처 기능을 이용해 클로저가 함수 바깥으로 탈출할 수 있기 때문이다.또한, 인자값으로 전달된 클로저는 중첩된 내부 함수에서 사용할 수도 없다.
내부 함수에서 사용할 수 있도록 허용할 경우, 이 역시 콘텍스트의 캡처를 통해 탈출될 수도 있기 때문이다.하지만 클로저를 변수나 상수에 대입하거나 중첩 함수 내부에서 사용해야할 경우도 있다.
이때 사용되는 것이 @escaping 속성이다.
이 속성을 클로저에 붙여준다면 해당 클로저는 탈출이 가능한 인자값으로 설정된다.
(앞의 탈출불가 제약 조건들이 모두 사라진다.)func callback(fn: @escaping () -> Void) { let f = fn // 클로저를 상수 f에 대입 f() // 대입된 클로저를 실행 }
@escaping 속성을 추가하면 오류 발생 없이 실행이 가능하다.
(@escaping 속성은 인자 값에 설정되는 값이므로, 함수 타입 앞에 넣어주어야 한다.)그렇다면 인자값으로 전달되는 클로저의 기본 속성이 탈출 불가로 설정된 이유는 어떤 것일까?
가장 큰 이점은 컴파일러가 코드를 최적화하는 과정에서의 성능 향상이다.
또한, 탈출불가 클로저 내에서는 self 키워드를 사용할 수 있다.(이 클로저는 해당 함수가 끝나서 리턴되기 전에 호출될 것이 명확하기 때문이다.)
따라서 클로저 내에서 self에 대한 약한 참조를 사용할 필요가 없다.
@autoclosure
@autoclosure 속성은 인자 값으로 전달된 일반 구문이나 함수 등을 클로저로 래핑 하는 역할을 한다.
이 속성이 붙어 있을 경우, 일반 구문을 인자값으로 넣어도 컴파일러가 알아서 클로저로 만들어서 사용한다.
이 속성을 적용하면 인자값을 직접 클로저 형식으로 넣어줄 필요가 없기 때문에 {} 형태가 아니라 () 형태로 사용할 수 있다는 장점이 있다.
func condition(stmt: () -> Bool) { if stmt() == true { print("결과가 참입니다.") } else { print("결과가 거짓입니다.") } } // 실행 방법 1 : 일반 구문 condition(stmt: { 4 > 2 }) // 실행 방법 2 : 클로저 구문 condition { () -> Bool in return (4 > 2) } condition { return (4 > 2) } condition { 4 > 2 }
위에서 작성된 실행 방법 1, 2에서 실제 전달하고 싶은 것은 ‘4 > 2’ 구문이다.
하지만 일반 실행 구문이나 트레일링 클로저 어느 것을 적용해도, 해당 구문을 {} 형태로 감싸 클로저 형태로 만든 후 인자 값으로 전달해야 한다.
func condition(stmt: @autoclosure () -> Bool) { if stmt() == true { print("결과가 참입니다.") } else { print("결과가 거짓입니다.") } } // 실행 방법 condition(stmt: (4 > 2))
하지만 @autoclosure 속성을 적용한다면 위와 같이 간단하게 구문 실행이 가능하다.
(@autoclosure를 사용한다면 더이상 일반 클로저를 인자 값으로 사용할 수 없기 때문에 실행 방법 1, 2와 같은 방법으로 구문 실행이 불가능하다.)
또한, @autoclosure 속성을 적용하면 지연된 실행이 가능하다.
var arrs = [String]() func addVars(fn: @autoclosure () -> Void) { arrs = Array(repeating: "", count: 3) fn() } // arrs.insert("KR", at: 1) 오류 addVars(fn: arrs.insert("KR", at: 1))
arrs.insert(“KR”, at: 1)은 arrs 배열의 두번째 인덱스에 “KR”을 입력하는 것인데 아직 배열의 인덱스가 그만큼 확장되어있지 않기 때문에 오류가 발생한다.
하지만 addVar(fn:)를 사용해 위의 구문을 적용한다면 오류가 발생하지 않는데, 이것이 지연된 실행이다.
@autoclosure 속성이 부여된 인자값은 컴파일러에 의해 클로저, 즉 함수로 감싸 지기 때문에 위와 같이 작성해도 addVars(fn:) 함수 실행 전까지는 실행되지 않으며, 해당 구문이 실행될 때에는 이미 배열의 인덱스가 확장된 후이므로 오류도 발생하지 않는다.
즉, @autoclosure 속성이 인자값에 부여된다면 해당 인자 값은 컴파일러에 의해 클로저로 자동 래핑 된다.
이 때문에 함수를 실행할 때에는 {} 형식의 클로저가 아닌 () 형식의 일반값을 인자 값으로 사용해야 하며, 인자 값은 코드에 작성된 시점이 아니라 해당 클로저가 실행되는 시점에 맞추어 실행된다.
2020-04-17 오전 3:24 #6904 -
-
-
야곰키 마스터
- 글작성 : 37
- 답글작성 : 579
음…아마 여러줄은 불가한 것으로 알고 있어요.
오토 클로저는 클로저로 써야 하는데 한줄인 경우가 많다면 그냥 매개변수로 전달할 수 있도록 자동으로 클로저로 변환해 주는 것인데요,
경우에 따라서 읽거나 이해하기 어려우므로 그냥 클로저를 쓰는 것이 좋을 것 같다는 제 개인적인 의견입니다.
물론 오토클로저가 있다는 것은 잘 알고 있어야겠죠 🙂condition(stmt: @autoclosure () -> Bool)
메서드에서Bool
타입의 값을 매개변수로 받아오도 되는데 굳이 오토클로저를 사용한 이유는 ‘지연실행’을 해야하기 때문입니다. 이 메서드의 예저로는 지연실행이 가지는 효과에 대해서 명확히 설명하지 못하지만 아래 예제는 조금 설명해볼 수 있겠네요.결론은 ‘오토클로저는 클로저를 써야할 곳(대체적으로 지연실행이 필요한 곳)에 한 줄 클로저 대신 간결한 한 줄 코드를 작성할 수 있게 해주지만, 경우에 따라 남용하면 헷갈리거나 읽기 어려운 코드가 될 수 있으므로 꼭 필요한 경우가 아니라면 지양하는 것이 좋겠다.’ 겠네요.
물론 필요한 경우도 있겠죠? Swift 라이브러리에서는
assert
함수 등에서 사용하고 있습니다.assert
에선 왜 오토클로저가 필요할까요? 한 번 생각해보세요 🙂
이 글에도 좋은 내용이 있네요.2020-04-22 오후 10:49 #7077
-
-
-
-
글쓴이글
- 답변은 로그인 후 가능합니다.