ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Swift - Property Wrapper
    Programming/Swift 2021. 10. 28. 14:29

     

     

     

    안녕하세요 Beepeach 입니다 :)

    오늘은 Property Wrapper에 대해서 공부해보려고 합니다.

     

    이제 막 Swift를 배우시는 분이라면 조금은 어려울 수도 있어서 이해가 가지 않는다면 나중에 보시는 것을 추천드립니다!

     

    Property wrapper는 프로퍼티를 정의하는 코드와 프로퍼티를 어떻게 저장할지 관리하는 코드를 분리시켜줍니다.

    예를 들어서 어떤 프로퍼티들의 최댓값을 100으로 설정하고 싶습니다.

    그럼 이 프로퍼티들을 선언해줄 때 최댓값이 100넘이 넘는다면 프로퍼티 값이 100으로 설정되는 코드를 모두 넣어줘야 합니다.

     

    다른 예로는 어떤 프로퍼티들은 초기화와 동시에 UserDefaults에 저장되길 원합니다.

    그렇다면 해당하는 프로퍼티들은 모두 UserDefaults에 저장하는 코드가 들어가야 합니다.

    두 가지 상황 모두 코드들이 프로퍼티의 수만큼 중복이 되겠죠??

     

    Property wrapper는 이러한 중복되는 코드를 한 번만 선언하도록 하고 이를 이용해서 코드의 재사용성, 가독성을 높여줍니다.

    그럼 어떻게 사용하는지 예시를 통해서 보도록 하겠습니다!

     


    Property Wrapper

     

    먼저 Wrapper를 사용하지 않은 예시를 보겠습니다.

     

     

    CustomSteroeAudio라는 구조체를 만들고 leftVolum과 rightVolum 프로퍼티를 정의했습니다.

    이 두 프로퍼티의 값이 100을 넘기고 싶지 않아서 다른 computed property를 만들어서 값을 제한했습니다.

    leftVolum과 rightVolum 모두 100을 넘기지 않는 코드를 작성했습니다.

     

    그럼 이제 Wrapper를 이용한 예제를 보도록 하겠습니다.

    Property wrapper는 struct, class, enum에 사용할 수 있습니다.

     

     

    핵심은 @propertyWrapper와 wrappedValue 두 가지입니다.

    PropertyWrapper로 만들어줄 타입 앞에 @propertyWrapper 특성을 선언해야 합니다.

     

    @propertyWrapper 특성을 추가해주고 새로운 Type을 이전에 만드는 것과 똑같이 만들면 에러가 발생합니다.

    이유는 wrapper에는 wrappedValue라는 프로퍼티가 꼭 선언되어 있어야 합니다.

    Stored property, Computed property 아무거나 상관없지만 주로 computed property로 구현을 합니다.

    wrappedValues wrappingValue 등 오타가 발생하면 에러가 발생하니 오타 없이 정확하게 구현해줘야 합니다.

     

    예제에서는 100보다 큰 값이 들어온다면 100을 저장하도록 만들었습니다.

    number 프로퍼티 앞에 private이 들어간 이유는 해당 값을 다른 곳에서 바꾸지 못하게 하기 위해서입니다.

    그리고 wrappedValue앞에 public이 들어간 이유는 public으로 선언해주지 않으면 가끔씩 이후에 wrapper로 만든 프로퍼티에 접근할 때 접근 에러가 발생하여 작성했습니다. 

     

    public문제는 단순한 플레이그라운드 문제인지 확인이 필요한 거 같습니다.

    작성해주지 않아도 되는데 에러가 발생할 때가 있고 아닐 때가 있어서 정확히 어느 부분에서 문제인지 찾지 못했습니다. [각주:1]

     

    그럼 이제 property wrapper를 이용하여 프로퍼티를 만들어보겠습니다.

     

     

    이러한 특성을 적용하고 싶은 프로퍼티 앞에 @WrapperName 특성을 추가해주면 끝입니다.

    맨 위에 propertyWrapper를 사용하지 않은 CustomStereoAudio와 StereoAudio의 차이점이 확 보이죠??

    코드가 아주아주 간단해졌습니다.

     

    문제는 이제 StrereoAudio의 인스턴스를 생성할 때 Line 7과 같이 이전에 사용하던 생성자로 초기화하면 에러가 발생합니다.

    @wrapper특성을 추가한 프로퍼티는 초기화를 wrapper에게 맡깁니다.

     

    간단히 설명하자면 property wrapper를 적용한 leftVolumn, rightVolumn 프로퍼티는 위에 선언한 wrapper인 HundredOrLess에 wrappedValue의 setter 통하여 값을 초기화합니다.

    그래서 leftValue와 rightValue의 default value를 지정하지 않았지만 인스턴스를 생성할 수 있었습니다.

     wrapper에서 number의 기본값을 지정하고 setter에서 초기화를 시켰으니까요!

     

    하지만 잘 생각해보면 코드가 매우 간결해졌지만 이 코드도 마음에 들지는 않습니다.

    기본값이 0으로 고정되어있고 최대 max값도 100으로 지정되어 있으니까요.

     

    이 코드를 더 확장시켜서 기본값도 인스턴스를 생성할 때 정해줄 수 있고 최댓값도 내가 원하는 값으로 만들고 싶습니다.

     

     


    Property Wrapper Initializer

     

    위에서 말한 대로 구현하는 방법은 initializer(생성자)를 만들어주면 해결할 수 있습니다.

     

     

    Wrapper는 strcut, class, enum으로 만들 수 있다고 했습니다. 이번에는 class로 만들어봤습니다.

    그리고 프로퍼티의 Default value를 사용하지 않고 생성자를 사용하여 wrapper를 만들었습니다.

     

    간단하게 코드를 살펴보겠습니다.

    이전과 달라진 부분은 number와 maxNumber에 default value를 사용하지 않고 생성자를 이용하여 내가 원하는 number와 maxNumber값을 정할 수 있게 되었습니다.

     

    일반적인 Type 정의와 크게 다른 부분이 없지만 몇 가지를 주의해야 합니다.

    (생성자를 3개나 선언한 이유가 있습니다.)

     

    1. init() 생성자를 선언해야 wrapper를 사용한 인스턴스를 생성할 때 기본 생성자를 사용할 수 있습니다.

    2. init(wrappedValue:) 생성자를 선언해야 wrapper를 사용한 프로퍼티에 default value를 할당할 수 있습니다.

     

    그럼 첫 번째 init()을 정의해준 이유에 대해서 알아보겠습니다.

     

     

    PerfectStereoAudio 구조체를 만들고 wrapper 특성을 추가한 프로퍼티 2개를 선언했습니다.

    그리고 PerfectStereoAudio의 인스턴스를 만들어보았습니다.

    여기서 기본 init인 PerfectStereoAudio()를 사용할 수 있는 이유는 init()을 선언해놓았기 때문입니다.

     

    print(#function)으로 어떤 생성자가 호출되어서 인스턴스가 만들어졌는지 알 수 있습니다.

    우리는 basicAudio라는 인스턴스 하나만 생성했는데 생성자가 3개나 호출됐죠????

    그 이유는 이전에 말씀드린 대로 wrapper를 추가한 프로퍼티의 초기화는 PerfectStereoAudio가 하지 않습니다.

    LimitedNumber가 초기화를 담당합니다.

    wrapper를 두 개의 프로퍼티에서 사용했으므로 두 번 init()이 호출되는 것을 볼 수 있습니다.

    (여기서 헷갈리면 안 되는 부분은 wrapper를 추가한 인스턴스의 프로퍼티의 초기화만 Wrapper가 하는 것이지 해당 인스턴스 자체 초기화를 담당하는 게 아닙니다. 그래서 마지막에는 init() PerfectStereoAudio init이 호출되는 것을 확인할 수 있습니다.)

     

     

    그럼 두 번째 init(wrappedValue:)를 정의해준 이유에 대해서 알아보겠습니다.

    이번에는 PerfectStereoAudio에서 wrapper를 추가한 프로퍼티에 defaultValue를 할당하려고 합니다.

     

     

    default value를 선언하는 건 어렵지 않죠??

    하지만 주목해야 할 부분은 default value를 정해주니까 init(wrappedValue:)가 호출됐습니다.

     

    wrapper를 추가한 프로퍼티에 default value를 할당하고 싶다면 꼭 init(wrappedValue:)를 선언해줘야 합니다.

    마찬가지로 오타가 있으면 안 됩니다.

     

     


    General Usage

     

    마지막으로 선언한 init을 보도록 하겠습니다.

    이 방법이 가장 일반적으로 사용하는 방법입니다.

     

     

    wrapper에 argument를 전달하는 것처럼 사용을 하면 됩니다.

    그럼 init(number:maxNumber:)이 호출되고 우리가 원하는 대로 코드를 작성할 수 있습니다.

     

    위 예제에서는 leftVolume는 max값이 200이고 기본값이 50이 됩니다.

     

     

    rightVolume는 max값이 150이고 기본값이 400으로 전달됐지만 max가 150이므로 150이 됩니다.

     

    원래 한번에 끝내려했는데... 생각보다 양이 많아져서 다음에는 다른 예제와 함께 Wrapper의 인스턴스에 접근하는 방법에 대해서 공부해보도록 하겠습니다!!

     

     


    참고자료

     

    https://kxcoding.com

     

    여러분의 새로운 도전을 응원합니다 | KxCoding

    Mastering SwiftUI 더 적은 코드로, 더 멋진 UI 만들기

    kxcoding.com

     

     

    https://docs.swift.org/swift-book/LanguageGuide/Properties.html

     

    Properties — The Swift Programming Language (Swift 5.6)

    Properties Properties associate values with a particular class, structure, or enumeration. Stored properties store constant and variable values as part of an instance, whereas computed properties calculate (rather than store) a value. Computed properties a

    docs.swift.org

     

     

    1. - 21년 11월 5일 추가 

      이 부분은 단순 에러인 거 같습니다.

      public을 써줘도 에러가 날 때가 있는데 모든 경우에서 xcode를 종료 후 재실행하거나 플레이그라운드 실행을 stop하고 다시 실행하면  public이 없어도 접근 에러가 발생하지 않는 것을 확인했습니다.

      실제 프로젝트에서 사용하실때는 public을 빼시는것을 추천드립니다!

      [본문으로]

    728x90

    'Programming > Swift' 카테고리의 다른 글

    Swift - Method  (0) 2021.11.06
    Swift - Property Wrapper Projecting Value  (0) 2021.11.05
    Swift - Type Property  (0) 2021.10.27
    Swift - Property Observer  (0) 2021.10.26
    Swift - Computed Property  (0) 2021.10.25
Designed by Tistory.