Hamp 2025. 3. 22. 22:39
반응형

👋 들어가기 전

정말 오랜만에 컨커린시 관련 포스팅이다.

최근에 현생이 정말 정신이 없다.

주말은 쉬는 날인데 현재는 평일에 없는 학습시간을 위한 날인 것 같다.

정말 침대에 눕고 싶다. ㅋㅋㅋ 

 

잡소리는 그만하고 오늘의 주제로 들어가기 전 간단한 사전 지식을 알아보자.


@property wrapper

프로퍼티 래퍼 정말 무의식적으로 많이 썼고 특히, SwiftUI를 사용할 때
많이 쓴 것 같다.

 

한번 구조를 간단하게 정리해보자.

전체적인 내용인 출처 첫번 째 링크를 참고했다.

목적

프로퍼티 래퍼의 목적은 반복적인 로직을 담당하는 하나의 타입처럼 선언하여

프로퍼티의 타입처럼 쉽게 사용할 수 있게 만들어주는 것이다.

구조

property wrapper는 크게 2가지 프로퍼티를 제공한다.

wrappedValue 

wrappedValue는 가장 핵심적인 프로퍼티이며 필수 구현 프로퍼티다.

위에서 말한 반복적인 로직을 이곳에서 처리한다.

projectedValue

추가적인 로직이 필요할 때 사용하므로  구현 여부는 옵셔널이다.

사용할 때는 변수에 $를 붙혀 사용한다.

예는 String 값의 시작 값을 대문자로 변환해주고

upperCase를 projectedValue로 제공해주는 형태의 프로퍼티 래퍼를 만들어보자.

import Foundation

@propertyWrapper
struct Capitalized {
  private var _value: String = ""

  var wrappedValue: String {
    get {
      return _value.capitalized
    }

    set {
      _value = newValue
    }
  }

  var projectedValue: String {
    return _value.uppercased()
  }

  init(_ value: String = "") {
    self._value = value
  }
}

struct Person {
  @Capitalized var engName: String

  func sayHello() {
    print("Hello, my name is \(engName)")
    print("Hello, my name is \(_engName)")
  }
}

var p = Person()
p.engName = "abcde"
print(p.engName) //Abcde
print(p.$engName) //ABCDE
p.sayHello()
// Hello, my name is Abcde
// Hello, my name is Capitalized(_value: "abcde")

 

여기서 보면 sayHello 함수에서 만들지 않은 _engName 변수가 있다.

이건 propertywrapper로 만든 Capitalized로 접근할 수 있도록 변수 앞에 _를 붙혀 제공한다.


☝️구조 및 정의

구조

오늘의 주제인 @TaskLocal을 들어가기전 프로퍼티 래퍼에 대해 알아봤다.

 

왜냐하면 형태를 보면 알겠지만 @TaskLocal 역시 프로퍼티 래퍼 형태로 사용된다.

 

구현 구조를 살펴보면 다음과 같이 프로퍼티 래퍼와 매우 비슷한 구조이다.

wrappedValued와 projectValue가 보인다.

정의

공식문서를 보면 특정 스코프에 바인딩되어 사용할 수 있는 value 키다.


✌️특징

Task 메타데이터에 포함된다.

우리는 자식 Task에게 Task 메타데이터가 상속되는 것을 알고 있다.

우선순위, 실행중인 엑터 등  메타 데이터에 @TaskLocal 값이 포함된다.

static 형태로 선언되야 한다.

문법적인 약속, 별도의 작업간 데이터 공유 없이 사용하기 위해 그런 듯

옵셔널 또는 기본 값을 선언해야한다.

옵셔널로 선언해주거나 옵셔널이 아니라면 기본 값을 부여해야한다.

값의 변경은 wrappedValue로 하지 않는다.

위에 구조를 보면 당연한거지만 get-only 이므로 wrappedValue 자체는 변경이 불가능하다.

값의 변경은 projectedValue를 이용한다.

값의 직접적인 변경은 할 수 없고 projectedValue의 withValue를 이용해서 현재 Task 바운더리에서

@TaskLocal 값을 일시적으로 변경한다.


👍 예제 코드

코드를 보고 출력 값을 예상해보자.

import Foundation

enum Level {
  case low
  case medium
  case high
}

class Game {
  @TaskLocal
  static var level: Level = .low

  func printLevel() async {
    print("현재 난이도는 \(Game.level) 입니다.")
  }
}


let game = Game()

Task {
  print("#1 location [첫번 째 Task] \(Game.$level.get())")
  print("------------------------------------")

  await Game.$level.withValue(.medium) {
    print("값 변경:  medium")
    print("#2 location [첫번 째 Task] \(Game.$level.get())")
    print("------------------------------------")

    Task {
      print("#3 location [두번 째 Task] \(Game.$level.get())")
      print("------------------------------------")
    }
  }
  print("첫번 째 값 변경 종료")
  print("#4 location [첫번 째 Task] \(Game.$level.get())")

  await Game.$level.withValue(.high) {
    print("값 변경: high")
    print("#5 location [첫번 째 Task] \(Game.$level.get())")
    print("------------------------------------")

    Task {
      print("#6 location [세번 째 Task] \(Game.$level.get())")
    }
  }
}

#1 location [첫번 째 Task] low
------------------------------------
값 변경:  medium
#2 location [첫번 째 Task] medium
------------------------------------
첫번 째 값 변경 종료
#4 location [첫번 째 Task] low
값 변경: high
#5 location [첫번 째 Task] high
------------------------------------
#3 location [두번 째 Task] medium
------------------------------------
#6 location [세번 째 Task] high

😀 목적

공통된 로직을 상태(예제로는 level)에 따라 전혀 다른 동작하게 컨트롤 하기위해 사용된다.

 

이 때 범위 제약적으로 컨트롤 한다는 중요한 특징을 기억하자.


출처

 

Property wrappers in Swift | Swift by Sundell

This week, let’s take a look at how Swift’s property wrappers work, and explore a few examples of situations in which they could be really useful.

www.swiftbysundell.com

 

 

TaskLocal | Apple Developer Documentation

Wrapper type that defines a task-local value key.

developer.apple.com

 

반응형