어트리뷰트 키워드
컴파일러에게 추가적인 정보를 제공하는 키워드
@available, @objc, @escaping, @IBOutlet, @IBAction, @discardableResult ...
1. 선언에 추가적인 정보를 제공
@available(iOS 11.0, macOS 10.12, *)
class MyClass{
//...
}
//MyClass 선언은 iOS 11.0, macOS 10.12이상에만 적용되는 클래스
//그보다 아래 버전에선 컴파일러가 못읽는다.
MyClass의 선언에 대한 추가정보 제공
2. 타입에 추가 정보 제공
func doAnything(completion: @escaping() -> ()){
//...
}
//@escaping:
() -> () 타입에 대한 추가정보 제공
@discardableResult 키워드
리턴값이 있는 함수에서 결과값이 있는 함수를 사용할 수도 있고 사용하지 않을 수도 있는 함수를 정의할 때 사용한다.
func sayHello() -> String{
print("ㅎㅇ")
return "안녕하세요"
}
sayHello()
리턴타입이 String인 함수를 사용하는데 리턴값을 사용하지 않고있다. (실제 함수는 리턴을 함)
경고를 없애기 위해 아래처럼 할 수 있는데,
print(sayHello())
let hello = sayHello()
_ = sayHello()
이렇게 하지 않고 리턴값을 사용할 수도 사용하지 않을 수도 있을 때엔 함수의 선언 이전에 @discardableResult을 사용한다.
@discardableResult
func sayHello() -> String{
print("ㅎㅇ")
return "안녕하세요"
}
@unknown 키워드
만약 열거형의 케이스가 늘어난다면 항상 올바른 처리를 하고 있다고 말할 수 있을까..
만약 열거형의 개수가 늘어날 수 있는?
예를 들어 로그인 경우의 수가 이메일, 페이스북, 넥슨 이였다가 네이버가 추가될 수 있는..
이런 상황의 경우의 수가 늘어날 수 있는 열거형을 Non-frozen이라고 한다. 이런 열거형을 처리하는 분기문에서 사용한다.
로그인의 방법을 담은 열거형
enum HowToLogin: String{
case email
case facebook
case nexon
case naver
}
let userLogin = HowToLogin.facebook
switch userLogin{ //경고: "Switch must be exhatstive"
case .email:
print("이메일 로그인")
case .nexon:
print("넥슨 로그인")
default:
print("네이버 로그인")
이와같은 경우 페이스북과 네이버의 로그인을 통틀어서 네이버로그인으로 출력하게된다.
사실 facebook로그인을 처리하지 않고 있는데도 default에서 "네이버 로그인"으로 처리하고 있기 때문에 모든 열거형의 케이스를 처리하고있다고 생각한다. 이 때문에 위와 같은 경우 컴파일러는 에러를 출력하지 않는다.
완벽하게 모든 케이스를 전부 처리해야 할 경우 아래와같이 할 수 있다.
switch userLogin{
case .email:
print("이메일 로그인")
case .nexon:
print("넥슨 로그인")
@unknown default:
print("네이버 로그인")
이렇게 @unknown키워드를 추가하게되면 컴파일러는 경고창을 띄워 모든 케이스를 확인해야한다고 알려준다
1. @unknown키워드를 default블럭에 추가함으로 사용할 수 있다.
2. switch문에서 열거형의 모든 케이스를 다루지 않는 경우, 스위치 문에서 모든 열거형의 케이스를 다루지 않았다고 경고를 통해 알려준다.
3. 에러: "Switch must be exhaustive"로 알려준다.
4. 쉽게 문제점이 무엇인지 알 수 있다.
@objc dynamic 키워드
Method DIspatch에서 메서드의 Dispatch방식을 Message Dispatch방식으로 바꿔준다.
이를 위해 objective-c방식으로 작동하게한다.
https://clamp-coding.tistory.com/entry/Swift-Method-Dispatch
@escaping (탈출) 키워드
- 원칙적으로 함수의 실행이 종료되면 파라미터로 쓰이는 클로저도 제거된다.
@escaping는 클로저를 제거하지 않고 함수에서 탈출시킨다. (함수가 종료되어도 클로저가 존재하도록 함)
1. 클로저를 단순 실행 ==> non-escaping 클로저
func performEscaping1(closure: () -> ()) {
print("프린트 시작")
closure()
}
함수 내부에서 단순 실행하고 종료할 때 클로저를 Heap에 저장할 필요가 없다.
@escaping 사용의 대표적인 경우
1. 어떤 함수 내부에 존재하는 클로저(함수)를 외부 변수에 저장하는 경우 => 파라미터로 받은 클로저를 외부의 변수에 할당하는 경우.
Heap 영역에 저장해서 더 오래 유지하고 사용해야할 경우
var aSaved: () -> () = { print("aSavedClosure") }
aSaved()
func performEscaping(closure: @escaping () -> ()){
aSaved = closure
}
perforemEscaping(closure: { print("anotherClosure") }
aSaved()
// aSavedClosure
// anotherClosure
파라미터로 전달한 클로저를 외부 클로저 저장소에 담고있다. == > 외부로 탈출시킨다.
컴파일러에게 "파라미터로 받는 함수를 외부로 탈출할거다" 라고 알려주는것.
2. GCD(비동기 코드의 사용)
func performEscaping1(closure: @escaping (String) -> ()) {
var name = "홍길동"
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { //1초뒤에 실행하도록 만들기
closure(name)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1{
// 1초 뒤에 실행하는 클로저
}
이 코드는 performEscaping1이라는 함수 내부에 정의되어있다.
1초 뒤에 실행한다는 의미는 정의되어있는 함수의 StackFrame을 벗어난다는 의미이다. 는 즉, 저장해 두었다가 1초뒤에 사용하겠다는 뜻이다. 그렇기 때문에 escaping 키워드가 필요하다.
함수 내부에서 즉시 사용하는게 아닌 힙 영역에 1초동안 보관하기 때문이다.
위 함수를 실행시켜 보면
performEscaping1 { str in
print("이름 출력하기: \(str)")
}
//...1초뒤
//이름 출력하기: 홍길동
@escaping키워드를 사용하지 않고 외부변수에 저장할 경우 에러가 발생한다.
@escaping 키워드를 사용하여 함수의 실행을 벗어나서 힙영역에 저장할 경우 메모리관리가 필요하다.
@autoclosure 키워드
자동으로 클로저를 만들어줄게!(파라미터가 없는 경우에만)
func someFuction(closure: @autoclosure () -> Bool) {
if closure() {
print("참입니다.")
} else {
print("거짓입니다.")
}
}
//파라미터인 클로저는 Bool을 return하므로 참 혹은 거짓이 된다 즉 if문에 사용 가능
someFuction(closure: <Bool>) //@autoclosure키워드가 붙어있기 때문에 중괄호를 입력하지 않아도 된다.
someFunction(closure: num == 1)
//아래와 같다 @autoclosure키워드가 중괄호를 삽입해준 느낌
someFunction{ return Bool }
someFunction(closure: { return Bool } )
// Bool값을 나타낼 수 있는 조건만 주면 자동으로 클로저 생성
클로저 형태로 써도 되지만 너무 번거로운 경우 사용.
번거로움을 해결해 주지만, 실제 코드가 명확해 보이지 않을 수 있으므로 사용을 지양한다(애플공식문서)
잘 사용하지 않는다. 공식 문서를 읽기위해 학습
@autoclosure는 기본적으로 non-escaping 특성을 갖고있는데, escaping 특성이 필요하다면 아래처럼 작성해야한다.
func someClosure(closure: @autoclosure @escaping () -> String{
DispatchQueue.main.ansyncAfter...
}