SWIFT

[Swift]. @어트리뷰트 키워드와 다양한 키워드들

clamp 2023. 2. 28. 11:45

어트리뷰트 키워드

컴파일러에게 추가적인 정보를 제공하는 키워드

@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

 

[Swift]. Method Dispatch

Method Dispatch 실제로 Method는 CPU에대한 명령어이며, 실제론 코드 영역에만 존재한다. 그렇기 때문에 코드 영역에 있는 메서드를 실행시키려면 메서드 주소가 필요하다. 그런데 그 메서드 주소를

clamp-coding.tistory.com


@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...
}