Published on

🎊 WWDC23 - What's new in Swift

Authors
  • avatar
    Name
    이창쀀
    Twitter

WWDC23 - What’s new in Swift

Swift의 μƒˆλ‘œμš΄ feature, λ§€ν¬λ‘œκ°€ λ„ˆλ¬΄ κΆκΈˆν•΄μ„œ ν•΄λ‹Ή μ„Έμ…˜λΆ€ν„° μ°Ύμ•„λ³΄κ²Œ λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

λ°”λ‘œ μ‹œμž‘ν•΄λ³΄μ£ ..!

Expressive Code

Using if/else and switch Statements as Expressions

μ™€μš°.. μ‹œμž‘λΆ€ν„° 큰 μ†Œμ‹μ΄λ„€μš”.. if-else ꡬ문과 switch ꡬ문을 ν‘œν˜„μ‹ μœΌλ‘œμ„œ μ‚¬μš©ν•  수 μžˆλ‹€λŠ” μ†Œμ‹μž…λ‹ˆλ‹€.

let bullet =
  isRoot && (count == 0 || !willExpand) ? ""
    : count == 0    ? "- "
    : maxDepth <= 0 ? "β–Ή " : "β–Ώ "

λ‹€μ†Œ κ³Όμž₯되긴 ν–ˆμ§€λ§Œ λ³΅μž‘ν•œ 쑰건에 μ˜ν•΄ λ‹€μ–‘ν•œ 값을 let λ³€μˆ˜μ— λ„£μ–΄μ£Όκ³  싢을 λ•ŒλŠ” μœ„μ™€ 같이 μ‚Όν•­μ—°μ‚°μžλ₯Ό 겹겹이 μŒ“μ•„κ°€λ©° μ‚¬μš©ν•΄μ•Ό ν–ˆμŠ΅λ‹ˆλ‹€. ν•˜μ§€λ§Œ 이제 if-else ꡬ문으둜 읽기 μ‰½κ²Œ κ°€λŠ₯ν•©λ‹ˆλ‹€.

let bullet = 
  if isRoot && (count == 0 || !willExpand) { "" }
  else if count == 0 { "- " }
  else if maxDepth <= 0 { "β–Ή " }
  else { "β–Ώ " }

이 방식이 νŠΉνžˆλ‚˜ 효과λ₯Ό λ³΄λŠ” 곳은 μ „μ—­ λ³€μˆ˜λ‚˜ μ €μž₯ ν”„λ‘œνΌν‹°λ₯Ό μ‚¬μš©ν•  λ•Œμž…λ‹ˆλ‹€.

let attributedName = {
  if let displayName, !displayName.isEmpty {
    AttributedString(markdown: displayName)
  } else {
    "Untitled"
  }
}()      

μ „μ—­ λ³€μˆ˜λ‚˜ μ €μž₯ ν”„λ‘œνΌν‹°μ— 쑰건에 따라 λ‹€λ₯Έ 값을 λ„£μ–΄μ£ΌκΈ° μœ„ν•΄μ„œλŠ” μ΄λ ‡κ²Œ λ°”λ‘œ ν˜ΈμΆœλ˜λŠ” ν΄λ‘œμ €λ₯Ό ν†΅ν•΄μ•Όλ§Œ ν–ˆμŠ΅λ‹ˆλ‹€.

let attributedName =
    if let displayName, !displayName.isEmpty {
    AttributedString(markdown: displayName)
  } else {
    "Untitled"
  }

ν•˜μ§€λ§Œ 이제 if-else ꡬ문을 ν‘œν˜„μ‹μœΌλ‘œ μ‚¬μš©ν•  수 있기 λ•Œλ¬Έμ— ν΄λ‘œμ €κ°€ ν•„μš” μ—†μŠ΅λ‹ˆλ‹€.

Result Builders

Result Builderκ°€ λ­λƒλ©΄μš”..

저도 μ΄λ²ˆμ— 처음 μ•Œμ•„λ³΄κ²Œ λ˜μ—ˆλŠ”λ°μš”.. WWDC21μ—μ„œ λ°œν‘œλ˜μ—ˆλ˜ λ‚΄μš©μ΄λ”λΌκ΅¬μš”..!?

image.constraints { view in
  view.centerXAnchor == container.centerXAnchor
  view.topAnchor == container.topAnchor + 20
  view.widthAnchor == container.widthAnchor -- 20
  view.heightAnchor == view.widthAnchor * 0.6      
}

마치 μ΄λ ‡κ²Œ SwiftUI의 ν˜•νƒœμ²˜λŸΌ μ„ μ–Έν˜•μœΌλ‘œ 값을 섀정해쀄 수 μžˆλŠ” λ¬Έλ²•μž…λ‹ˆλ‹€.

DSL(Domain Specific Language) 라고 λΆˆλ¦¬λŠ”λ°μš”..

μ œκ°€ 잘 λͺ¨λ₯΄κΈ° λ•Œλ¬Έμ—β€¦ πŸ˜…

μ•„λ¬΄νŠΌ νƒ€μž… 체킹이 빨라지고 μ—λŸ¬κ°€ μ’€ 더 μ •ν™•ν•œ 지점에 ν‘œμ‹œλœλ‹€κ³  ν•©λ‹ˆλ‹€.

Type Parameter Packs

이 뢀뢄은 Genericκ³Ό κ΄€λ ¨λœ λ‚΄μš©μž…λ‹ˆλ‹€.

Swift의 λͺ¨λ“  곳은 Generic이라고도 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

λ°°μ—΄λ§Œλ΄λ„ 사싀은 Array<Element>λΌλŠ” Generic을 ν™œμš©ν•œ νƒ€μž…μ΄μ£ !?

struct Request<Result> { ... }

struct RequestEvaluator {
  func evaluate<Result>(_ request: Request<Result>) -> Result
}

func evaluate(_ request: Request<Bool>) -> Bool {
  return RequestEvaluator().evaluate(request)
}

μ—¬κΈ° Bool νƒ€μž…μ˜ 값을 ν•˜λ‚˜λ§Œ λ°›λŠ” ν•¨μˆ˜ evaluateκ°€ μžˆμŠ΅λ‹ˆλ‹€.

ν•˜μ§€λ§Œ μ—¬λŸ¬κ°œμ˜ 값을 νŒŒλΌλ―Έν„°λ‘œ λ°›κ³  싢을 λ•Œλ„ 있겠죠?

let value = RequestEvaluator().evaluate(request)
let (x, y) = RequestEvaluator().evaluate(r1, r2)
let (x, y, z) = RequestEvaluator().evaluate(r1, r2, r3)

λ¬Έμ œλŠ” μ΄λ ‡κ²Œ ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•˜λ €λ©΄..

func evaluate<Result>(_:) -> (Result)
func evaluate<R1, R2>(_:_:) -> (R1, R2)
func evaluate<R1, R2, R3>(_:_:_:) -> (R1, R2, R3)
func evaluate<R1, R2, R3, R4>(_:_:_:_:)-> (R1, R2, R3, R4)
func evaluate<R1, R2, R3, R4, R5>(_:_:_:_:_:) -> (R1, R2, R3, R4, R5)
func evaluate<R1, R2, R3, R4, R5, R6>(_:_:_:_:_:_:) -> (R1, R2, R3, R4, R5, R6)

μ΄λ ‡κ²Œ λ”μ°ν•˜κ²Œ 각 νŒŒλΌλ―Έν„°μ˜ κ°œμˆ˜μ— λ§žλŠ” ν•¨μˆ˜λ“€μ„ μ—¬λŸ¬λ²ˆ μ˜€λ²„λ‘œλ“œν•˜λ©° μ •μ˜ν•΄μ£Όμ–΄μ•Ό ν•©λ‹ˆλ‹€.

이걸 보자마자 RxSwift의 combineLatestκ°€ μƒκ°λ‚˜λ”κ΅°μš”β€¦

λ”± μ΄λŸ΄λ•Œμ˜ κ²½μš°μž…λ‹ˆλ‹€.

이 방법은 보기에도 μ•ˆμ’‹μ„ λΏλ”λŸ¬, μ œκ³΅ν•˜λŠ” νŒŒλΌλ―Έν„°μ˜ 개수λ₯Ό μ‚¬μš©ν•˜λŠ” μͺ½μ—μ„œ λ›°μ–΄λ„˜μœΌλ©΄ λ‹Ήμ—°ν•˜κ²Œλ„ 컴파일러 μ—λŸ¬λ₯Ό λ°œμƒμ‹œν‚΅λ‹ˆλ‹€.

이 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ Swift의 Generic에 제곡된 인자의 κ°œμˆ˜λ³΄λ‹€ λ§Žμ€ 수의 값을 λ°›μ•„λ‚Ό 수 μžˆλŠ” μƒˆλ‘œμš΄ 문법이 λ„μž…λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

μƒˆλ‘œμš΄ each ν‚€μ›Œλ“œλŠ” Generic νƒ€μž…μ„ μΆ©μ‘±ν•˜λŠ” λ‹€μ–‘ν•œ 개수의 μΈμžλ“€μ„ β€œpackedβ€λœ μƒνƒœ 둜 받을 수 μžˆλ„λ‘ ν•©λ‹ˆλ‹€.

func evaluate<each Result>(_: repeat Request<each Result>) -> (repeat each Result)   

ν•˜λ‚˜μ˜ νŒŒλΌλ―Έν„° Request<Result> λŒ€μ‹  μ—¬λŸ¬κ°œμ˜ νŒŒλΌλ―Έν„°λ₯Ό λ°˜λ³΅ν•΄μ„œ λ°›λŠ”λ‹€λŠ” 의미둜 repeat Request<each Result> λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.

λ°˜ν™˜ν•˜λŠ” 값은 μΈμžκ°€ ν•˜λ‚˜μΌ 경우 ν•˜λ‚˜μ˜ 값을, μ—¬λŸ¬ 개일 경우 μ—¬λŸ¬ 개의 값은 Tuple (repeat each Result) 둜 λ°˜ν™˜ν•©λ‹ˆλ‹€.

κ·Έλ ‡λ‹€λ©΄ μ‚¬μš©ν•˜λŠ” μͺ½μ—μ„œλŠ” μ–΄λ–¨κΉŒμš”?

let results = RequestEvaluator.evaluate(r1, r2, r3)

Type Parameter Packs APIλ₯Ό μ‚¬μš©ν–ˆλŠ”μ§€ μ‘°μ°¨ μ•Œ 수 없을 μ •λ„λ‘œ μžμ—°μŠ€λŸ½μŠ΅λ‹ˆλ‹€..!

Swift Macros

λŒ€λ§μ˜ λ§€ν¬λ‘œμž…λ‹ˆλ‹€..

assertμ—μ„œλΆ€ν„° μ‹œμž‘ν•˜κ³  μžˆλ„€μš”.

assertλŠ” 쑰건문이 false일 λ•Œ, ν”„λ‘œκ·Έλž¨μ„ μžλ™μœΌλ‘œ μ’…λ£Œμ‹œν‚€μ£ .

assert(max(a, b) == c)

λ¬Έμ œλŠ” μœ„μ™€ 같은 방식은 ν”„λ‘œκ·Έλž¨μ„ κ·Έλƒ₯ μ’…λ£Œμ‹œμΌœλ²„λ¦¬κ³ , μ’…λ£Œλœ λΌμΈλ§Œμ„ λ‘œκ·Έμ— λ‚¨κΈ΄λ‹€λŠ” κ²ƒμž…λ‹ˆλ‹€.

κ·Έλž˜μ„œ μ• ν”Œμ€ λ‹€λ₯Έ μ—¬λŸ¬κ°€μ§€ assert 방식듀을 μΆ”κ°€ν•΄μ€¬μŠ΅λ‹ˆλ‹€.

![](XCAssertEqual(max(a, b), c))

XCAssertEqual은 μ’€ 더 λ‚˜μ€ 둜그λ₯Ό λ‚¨κ²¨μ€λ‹ˆλ‹€. μ•„κΉŒλ³΄λ‹€λŠ” 훨씬 λ‚«κΈ΄ ν•˜μ§€λ§Œ μ—¬μ „νžˆ a, b, ν˜Ήμ€ max(a, b) μ€‘μ—μ„œ μ–΄λ–€ 값이 μ—λŸ¬λ₯Ό λ°œμƒμ‹œν‚€λŠ”μ§€λŠ” μ—¬μ „νžˆ μ•Œ μˆ˜κ°€ μ—†μ£ .

ν•˜μ§€λ§Œ μ΄μ œλŠ” Macroλ₯Ό μ‚¬μš©ν•΄μ„œ 이λ₯Ό ν•΄κ²°ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

#assert(max(a, b) == c)

λ§€ν¬λ‘œλŠ” λ‹€λ₯Έ APIλ“€κ³Ό λΉ„μŠ·ν•˜κ²Œ νŒ¨ν‚€μ§€μ˜ ν˜•νƒœλ‘œ importν•˜μ—¬ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μ—¬κΈ°μ„œ μ‚¬μš©ν•œ #assert λ§€ν¬λ‘œλŠ” PowerAssertλΌλŠ” 라이브러리λ₯Ό μ„€μΉ˜ν•΄μ„œ μ‚¬μš©ν•  수 μžˆλ„€μš”.

πŸ“Ž PowerAssert

Macro Declarations

이름이 맀크둜라면 λ‚΄κ°€ 직접 μ›ν•˜λŠ” ν˜•νƒœμ˜ 맀크둜λ₯Ό μ‚¬μš©ν•  μˆ˜λ„ μžˆμ–΄μ•Όκ² μ£ ?

macro ν‚€μ›Œλ“œλ₯Ό μ‚¬μš©ν•¨μœΌλ‘œμ¨ 맀크둜λ₯Ό μ •μ˜ν•΄μ€„ 수 μžˆμŠ΅λ‹ˆλ‹€.

public macro assert(_ condition: Bool)

ν•¨μˆ˜μ™€ μƒλ‹Ήνžˆ λΉ„μŠ·ν•˜κ²Œ 생겼죠..?

ν•¨μˆ˜μ™€ λ™μΌν•˜κ²Œ μ •μ˜ν•΄μ€„ 수 있고, ν•¨μˆ˜μ™€ λ§ˆμ°¬κ°€μ§€λ‘œ νƒ€μž… 체크도 μ΄λ£¨μ–΄μ§‘λ‹ˆλ‹€.

κ·Έλž˜μ„œ 이 assert 맀크둜λ₯Ό

#assert(max(a, b))

μ΄λ ‡κ²Œ 잘 λͺ» μ‚¬μš©ν•˜λ©΄?

public macro assert(_ condition: Bool) = #externalMacro(
  module: "PowerAssertPlugin",
  type: "PowerAssertMacro"
)     

λŒ€λΆ€λΆ„μ˜ λ§€ν¬λ‘œλŠ” β€œExternal Macros” 라고 ν•©λ‹ˆλ‹€.

μ»΄νŒŒμΌλŸ¬λŠ” 맀크둜λ₯Ό String으둜 μ œκ³΅λ˜λŠ” module 정보와 type 정보λ₯Ό 톡해 μ‹λ³„ν•œλ‹€κ³  ν•˜λ„€μš”..

λ§€ν¬λ‘œλŠ” 컴파일러 ν”ŒλŸ¬κ·ΈμΈμœΌλ‘œ μž‘λ™ν•˜λ©° μ•±κ³ΌλŠ” λ³„λ„μ˜ ν”„λ‘œκ·Έλž¨ 으둜써 μ‹€ν–‰λœλ‹€κ³  ν•©λ‹ˆλ‹€.

μ»΄νŒŒμΌλŸ¬λŠ” ν”ŒλŸ¬κ·ΈμΈμ—κ²Œ 맀크둜λ₯Ό μ „λ‹¬ν•˜κ³ , ν”ŒλŸ¬κ·ΈμΈμ΄ λ§€ν¬λ‘œμ— λŒ€μ‘λ˜λŠ” μ†ŒμŠ€μ½”λ“œλ₯Ό μ»΄νŒŒμΌλŸ¬μ—κ²Œ μ œκ³΅ν•˜κ³  μžˆλ„€μš”.

λ§€ν¬λ‘œμ—κ²Œ ν•„μš”ν•œ λ§ˆμ§€λ§‰ μ •λ³΄λŠ” κ·Έλ“€μ˜ μ—­ν• (role) μž…λ‹ˆλ‹€.

Freestanding Macros

@freestanding(expression)
public macro assert(_ condition: Bool) = #externalMacro(
  module: "PowerAssertPlugin",
  type: "PowerAssertMacro"
)        

#assert λ§€ν¬λ‘œλŠ” @freestanding(expression) λ§€ν¬λ‘œμΈλ°μš”..

freestanding 은 β€œ#β€λ‘œ μ‚¬μš©ν•  수 있고, λŒ€μ‘λ˜λŠ” μ½”λ“œλ₯Ό μ§μ ‘μ μœΌλ‘œ μ‚½μž…ν•˜κΈ° λ•Œλ¬Έμ— μ‚¬μš©λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

expression 은 결과값이 μžˆλŠ” κ·Έ μ–΄λŠκ³³μ—μ„œλ„ μ‚¬μš©ν•  수 있기 λ•Œλ¬Έμ— μ‚¬μš©λ˜μ—ˆλ‹€κ³  ν•˜λ„€μš”.

@freestanding(exrpession)
public macro Predicate<each Input>(
  _ body: (repeat each Input) -> Bool
) -> Predicate<repeat each Input>

let pred = #Predicate<Person> {
  #0.favoriteColor == .blue
}
let blueLovers = people.filter(pred)    

expression λ§€ν¬λ‘œμ—λŠ” #Predicate λΌλŠ” λ§€ν¬λ‘œλ„ μžˆμŠ΅λ‹ˆλ‹€.

#Predicate λ§€ν¬λ‘œλŠ” ν΄λ‘œμ € μ•ˆμ—μ„œ type-safeν•˜κ²Œ Predicateλ₯Ό μž‘μ„±ν•  수 μžˆλ„λ‘ ν•΄μ€€λ‹€κ³  ν•˜λ„€μš”.

Attached Macros

또 ν˜•νƒœμ˜ λ§€ν¬λ‘œλ„ μžˆμŠ΅λ‹ˆλ‹€.

μ˜ˆμ‹œλ‘œ μ‚¬μš©ν•  enum을 μ‚¬μš©ν•  κ²½μš°μΈλ°μš”.

enum Path {
  case relative(String)
  case absolute(String)
}

extension Path {
  var isAbsolute: Bool {
    if case .absolute = self { true }
    else { false }
  }
}

extension Path {
  var isRelative: Bool {
    if case .relative = self { true }
    else { false }
  }
}

let absPaths = paths.filter { $0.isAbsolute }        

enum의 caseλ₯Ό 검사할 λ•Œ 보톡 μ΄λ ‡κ²Œ 많이 μ‚¬μš©ν–ˆμŠ΅λ‹ˆλ‹€.

이런 방식은 λ¬Έμ œκ°€ μžˆμŠ΅λ‹ˆλ‹€. caseκ°€ λŠ˜μ–΄λ‚ μˆ˜λ‘ 점점 더 κΈΈμ–΄μ§€λŠ” λ°©μ‹μ˜ μ½”λ“œλΌλŠ” 것이죠.

@CaseDetection
enum Path {
  case relative(String)
  case absolute(String)
}

let absPaths = paths.filter { $0.isAbsolute }

@CaseDetction은 β€œAttached Macros” μž…λ‹ˆλ‹€.

Property Wrapper와 λ™μΌν•˜κ²Œ β€œ@β€œ λ₯Ό μ‚¬μš©ν•˜κ³  μžˆλ„€μš”!

이 @CaseDetection λ§€ν¬λ‘œλŠ” μ΄μ „μ˜ isAbsolute와 isRelative ν”„λ‘œνΌν‹°λ₯Ό μžλ™μœΌλ‘œ μƒμ„±ν•΄μ€λ‹ˆλ‹€.

** Attached Macros** λŠ” λ‹€μ„― 가지 μ’…λ₯˜λ‘œ λ‚˜λˆŒ 수 있고, 각각의 역할은 μœ„μ™€ κ°™μŠ΅λ‹ˆλ‹€.

  • member

    • type / extension으둜 μƒˆλ‘œμš΄ μ •μ˜λ₯Ό μΆ”κ°€
    • ex) @CaseDetection
  • peer

    • μ •μ˜λœ ν‘œν˜„κ³Ό ν•¨κ»˜ λ‹€λ₯Έ ν‘œν˜„μœΌλ‘œμ¨ μ •μ˜λ₯Ό μΆ”κ°€
    • ex) async λ©”μ„œλ“œμ™€ 같은 λ‘œμ§μ„ μˆ˜ν–‰ν•˜λŠ” completion handler λ²„μ „μ˜ λ©”μ„œλ“œ μΆ”κ°€
  • accessor

    • ν”„λ‘œνΌν‹°μ— μ ‘κ·Όν•˜λŠ” 방식을 μΆ”κ°€ (Property Wrapper와 μœ μ‚¬)
    • ex) μ €μž₯ ν”„λ‘œνΌν‹°λ₯Ό μ—°μ‚° ν”„λ‘œνΌν‹°λ‘œ λ³€κ²½
  • memberAttribute

    • type / extension으둜 속성(attribute)λ₯Ό μΆ”κ°€
  • conformance

    • type / extension으둜 ν”„λ‘œν† μ½œμ„ μΆ©μ‘±μ‹œν‚€κΈ° μœ„ν•œ μš”μ†Œ μΆ”κ°€

일반적인 API와 λ‹€λ₯΄κ²Œ λ§€ν¬λ‘œκ°€ 쒋은 점은, μ½”λ“œμ—μ„œ λ°”λ‘œ μ–΄λ–€ μ½”λ“œ κ°€ μ‹€ν–‰λ˜κ³  μžˆλŠ” 지 확인할 수 있고, 디버그 κ³Όμ •μ—μ„œ μ§„μž… ν•  수 있으며, 맀크둜 μ˜μ—­ λ°”κΉ₯으둜 λ³΅λΆ™ν•˜λŠ” κ²ƒμœΌλ‘œ μ‰½κ²Œ μˆ˜μ • ν•  μˆ˜λ„ μžˆλ‹€λŠ” κ²ƒμž…λ‹ˆλ‹€.

Attached Macros 듀은 λ™μ‹œμ— μ—¬λŸ¬κ°œκ°€ μ‚¬μš©λ μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€.

μ˜ˆμ‹œλ‘œ SwiftUI의 μ½”λ“œλ₯Ό λ“€κ³  μžˆλŠ”λ°μš”.. 무슨 μ˜λ―ΈμΈμ§€λŠ” μ•Œ 것 κ°™μ•„μ„œ ν•œ 번 μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

// Observation in SwiftUI
final class Person: ObservableObject {
  @Published var name: String
  @Published var age: Int
  @Published var isFavorite: Bool
}

struct ContentView: View {
  @ObservedObject var person: Person
    
  var body: some View {
    Text("Hello, \(person.name)")
  }
}

이런 ν˜•νƒœμ˜ ν΄λž˜μŠ€κ°€ μžˆλŠ”λ°μš”, Person의 각 ν”„λ‘œνΌν‹°λ“€μ˜ λ³€ν™”λ₯Ό λ°›μ•„ ContentView의 λ‚΄μš©μ„ μ—…λ°μ΄νŠΈν•΄μ£ΌλŠ” μ½”λ“œ κ°™μŠ΅λ‹ˆλ‹€.

λ¬Έμ œλŠ” λ°˜λ³΅λ˜λŠ” @Published, ObservableObject ν”„λ‘œν† μ½œ λ“± ν•„μš”ν•œ λΆ€κ°€μž‘μ—…λ“€μ΄ λ„ˆλ¬΄ λ§Žλ‹€λŠ” 것이죠.

맀크둜λ₯Ό μ‚¬μš©ν•˜λ©΄ 이 과정듀을 ν•˜λ‚˜λ‘œ μΆ•μ†Œμ‹œν‚¬ 수 μžˆμŠ΅λ‹ˆλ‹€.

// Observation in SwiftUI
@Observable final class Person {
  var name: String
  var age: Int
  var isFavorite: Bool
}

struct ContentView: View {
  var person: Person
    
  var body: some View {
    Text("Hello, \(person.name)")
  }
}

@Observable ν•˜λ‚˜λ©΄ λͺ¨λ“  μž‘μ—…μ΄ μˆ˜ν–‰λ©λ‹ˆλ‹€.

λ‚΄λΆ€λ₯Ό ν•œ 번 μ‚΄νŽ΄λ³ΌκΉŒμš”?

@attached(member, names: ...)
@attached(memberAttribute)
@attached(conformance)
public macro Observable() = #externalMacro(...).
  1. member role을 톡해 μƒˆλ‘œμš΄ ν”„λ‘œνΌν‹°λ“€κ³Ό λ©”μ„œλ“œλ“€μ„ μΆ”κ°€ν•©λ‹ˆλ‹€.
  2. memberAttribute role을 톡해 @ObservationTracked 맀크둜λ₯Ό 각 μ €μž₯ ν”„λ‘œνΌν‹°μ— μΆ”κ°€ν•˜κ³  getter와 setterλ₯Ό μΆ”κ°€ν•©λ‹ˆλ‹€.
  3. Observable ν”„λ‘œν† μ½œμ„ μ±„νƒν•©λ‹ˆλ‹€.

맀크둜λ₯Ό 펼친 ν˜•νƒœμ˜ μ½”λ“œλŠ” μ΄λ ‡μŠ΅λ‹ˆλ‹€.

@Observable final class Person: Observable {
  @ObservationTracked var name: String { get { … } set { … } }
  @ObservationTracked var age: Int { get { … } set { … } }
  @ObservationTracked var isFavorite: Bool { get { … } set { … } }
  
  internal let _$observationRegistrar = ObservationRegistrar<Person>()
  internal func access<Member>(
    keyPath: KeyPath<Person, Member>
  ) {
    _$observationRegistrar.access(self, keyPath: keyPath)
  }
  internal func withMutation<Member, T>(
    keyPath: KeyPath<Person, Member>,
    _ mutation: () throws -> T
  ) rethrows -> T {
    try _$observationRegistrar.withMutation(of: self, keyPath: keyPath, mutation)
  }
}

λ§€ν¬λ‘œμ— λŒ€ν•΄μ„œλŠ” ν•œ 번 κ΄€λ ¨ μ„Έμ…˜μ„ μ‚΄νŽ΄λ³΄λ©΄μ„œ κ³΅λΆ€ν•΄λ΄μ•Όκ² λ„€μš”.

μƒˆλ‘œμš΄ νŒ¨λŸ¬λ‹€μž„μ˜ μ‹œμž‘μΌ λ“―ν•œ λŠλ‚Œ..!

Swift Foundation

졜근 μ• ν”Œμ΄ Foundation μ½”λ“œλ“€μ„ Swift둜 λ‹€μ‹œ μ“°λŠ” ν”„λ‘œμ νŠΈλ₯Ό μ‹œμž‘ν–ˆλ‹€κ³  ν•©λ‹ˆλ‹€.

기쑴의 C, Obj-C μ½”λ“œλ“€μ„ Swift둜 κ΅μ²΄ν•˜κ³  μžˆλ‹€κ³  ν•˜λ„€μš”..

이번 λ²„μ „μ—λŠ” Date, Calendar, Locale, AttributedString, JSON encode / decodeκ°€ Swift둜 λ‹€μ‹œ μ“°μ—¬μ‘Œλ‹€κ³  ν•©λ‹ˆλ‹€.

λ„€.. κ·Έλ ‡λ‹€κ³  ν•©λ‹ˆλ‹€..

λ„˜μ–΄κ°€μ£ !

Ownership

μ–΄λ–€ 값을 μ–΄λ–€ μ½”λ“œκ°€ μ†Œμœ ν•˜κ³  μžˆλŠ” 지에 λŒ€ν•œ 좔적이 κ°€λŠ₯ν•΄μ‘Œλ‹€κ³  ν•©λ‹ˆλ‹€.

μ €λŠ” μ½”λ“œ μž‘μ„±μžκ°€ λˆ„κ΅°μ§€ μ‹œκ·Έλ‹ˆμ²˜ 같은 κ±Έ λ‚¨κ²¨λ‘λŠ” 건 쀄 μ•Œμ•˜λŠ”λ°, μ „ν˜€ λ‹€λ₯Έ κΈ°λŠ₯μ΄μ˜€λ„€μš”.

struct FileDescriptor {
  private var fd: CInt
  
  init(descriptor: CInt) { self.fd = descriptor }

  func write(buffer: [UInt8]) throws {
    let written = buffer.withUnsafeBufferPointer {
      Darwin.write(fd, $0.baseAddress, $0.count)
    }
    // ...
  }

  func close() {
    Darwin.close(fd)
  }
}

이 μ½”λ“œλŠ” μœ„ν—˜μ„±μ΄ μžˆμŠ΅λ‹ˆλ‹€.

파일 μˆ˜μ •μ„ 마친 후에 close()λ₯Ό ν˜ΈμΆœν•˜μ§€ μ•Šμ„ κ°€λŠ₯성이 λ†ν›„ν•˜μ£ .

FileDescriptorλ₯Ό 클래슀둜 λ°”κΎΈκ³  deinit μ‹œμ μ— close()λ₯Ό ν˜ΈμΆœν•΄μ€˜λ„ λ˜μ§€λ§Œ, ν΄λž˜μŠ€λŠ” reference νƒ€μž…μ΄κΈ° λ•Œλ¬Έμ— ν•˜λ‚˜μ˜ νŒŒμΌμ„ κ΄€λ¦¬ν•˜λŠ” μΈ‘λ©΄μ—μ„œλŠ” μœ„ν—˜μ„±μ΄ ν½λ‹ˆλ‹€.

struct FileDescriptor: ~Copyable {
  private var fd: CInt
  
  init(descriptor: CInt) { self.fd = descriptor }

  func write(buffer: [UInt8]) throws {
    let written = buffer.withUnsafeBufferPointer {
      Darwin.write(fd, $0.baseAddress, $0.count)
    }
    // ...
  }
  
  consuming func close() {
    Darwin.close(fd)
  }
  
  deinit {
    Darwin.close(fd)
  }
}

κ·Έλž˜μ„œ λ“±μž₯ν•œ 것이 Non-Copyable struct μž…λ‹ˆλ‹€.

~Copyable 을 λΆ™μ—¬μ£Όλ©΄, ν΄λž˜μŠ€μ™€ λ§ˆμ°¬κ°€μ§€λ‘œ deinit을 μ‚¬μš©ν•  수 있게 λ©λ‹ˆλ‹€.

μΆ”κ°€μ μœΌλ‘œ consuming μ΄λΌλŠ” ν‚€μ›Œλ“œλ„ λ“±μž₯ν–ˆλŠ”λ°μš”, ν•΄λ‹Ή ν‚€μ›Œλ“œκ°€ 달린 ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•¨μœΌλ‘œμ¨ ꡬ쑰체에 λŒ€ν•œ μ†Œμœ κΆŒμ„ 포기할 수 μžˆμŠ΅λ‹ˆλ‹€.

Non-Copyable κ΅¬μ‘°μ²΄λŠ” 볡사될 수 μ—†μœΌλ‹ˆ, μ†Œμœ κΆŒμ„ ν¬κΈ°ν•˜κ²Œ 되면 μ ‘κ·Όν•  수 μ—†λŠ” 값이 되겠죠?

κ·Έλž˜μ„œ consuming ν•¨μˆ˜λŠ” 클래슀λ₯Ό λ©”λͺ¨λ¦¬μ—μ„œ ν•΄μ œν•˜λŠ” κ²ƒμ²˜λŸΌ ꡬ쑰체λ₯Ό λ©”λͺ¨λ¦¬μ—μ„œ ν•΄μ œ μ‹œν‚΅λ‹ˆλ‹€.

What’s new in Swift Concurrency

Custom Actor Executors λΌλŠ” 녀석이 μƒˆλ‘­κ²Œ λ‚˜μ™”μŠ΅λ‹ˆλ‹€.

기쑴의 ActorλΌλŠ” λ†ˆμ€ 자유둭게 μ‚¬μš©ν•  수 μžˆλŠ” 녀석은 μ•„λ‹ˆμ˜€μ£ ?

이제 Actorλ₯Ό νŠΉμ • DispatchQueue 에 ν• λ‹Ήμ‹œν‚¬ 수 μžˆλ‹€κ³  ν•©λ‹ˆλ‹€.

actor MyConnection {
  private var database: UnsafeMutablePointer<sqlite3>
  private let queue: DispatchSerialQueue

  nonisolated var unownedExecutor: UnownedSerialExecutor { queue.asUnownedSerialExecutor() }

  init(filename: String, queue: DispatchSerialQueue) throws { … }
  
  func pruneOldEntries() { … }
  func fetchEntry<Entry>(named: String, type: Entry.Type) -> Entry? { … }
}

await connection.pruneOldEntries()

unownedExecutorλ₯Ό μ΄λ ‡κ²Œ 지정해주면, λͺ¨λ“  Synchronization이 ν•΄λ‹Ή queueμ—μ„œλ§Œ 이루어진닀고 ν•˜λ„€μš”.

λ‹¨μˆœνžˆ await connection.pruneOldEntries()λ₯Ό ν˜ΈμΆœν•΄μ£ΌλŠ” 것 λ§ŒμœΌλ‘œλ„ μ§€μ •λœ queueμ—μ„œ asyncν•˜κ²Œ λ™μž‘ν•œλ‹€λŠ” 것이죠!

이제 Realm을 닀루기 μ’€ 더 μ‰¬μ›Œμ§€λŠ” κ±ΈκΉŒμš”.. μ΅œκ·Όμ— 이걸둜 고민을 많이 ν–ˆλŠ”λ° 말이죠..

// Executor protocols

protocol Executor: AnyObject, Sendable {
  func enqueue(_ job: consuming ExecutorJob)
}

protocol SerialExecutor: Executor {
  func asUnownedSerialExecutor() -> UnownedSerialExecutor
  func isSameExclusiveExecutionContext(other executor: Self) -> Bool
}

extension DispatchSerialQueue: SerialExecutor { … }

μ•„λ¬΄νŠΌ 이 μƒˆλ‘œμš΄ λ™μž‘μ€ DispatchQueueκ°€ μƒˆλ‘œμš΄ SerialExecutor ν”„λ‘œν† μ½œμ„ λ”°λ₯΄κΈ° λ•Œλ¬Έμ— κ°€λŠ₯ν–ˆλ‹€κ³  ν•©λ‹ˆλ‹€.

isSameExclusiveExecutionContext(other executor:) -> Bool을 톡해 이미 ν•΄λ‹Ή μ½”λ“œκ°€ μ‹€ν–‰λ˜κ³  μžˆλŠ” 지λ₯Ό κ²€μ‚¬ν•˜κ³ , asUnownedSerialExecutor() -> UnownedSerialExecutorλ₯Ό 톡해 Executor의 unowned μ°Έμ‘°λ₯Ό κ°€μ Έμ˜¨λ‹€κ³  ν•©λ‹ˆλ‹€.

그리고 κ°€μž₯ μ€‘μš”ν•œ enqueue(_ job:) λ₯Ό 톡해 Executor의 Job 듀을 κ΄€λ¦¬ν•œλ‹€κ³  ν•˜λ„€μš”.

μ—¬κΈ°μ„œ Job μ΄λž€ Executorμ—μ„œ λΉ„λ™κΈ°μ μœΌλ‘œ μ‹€ν–‰λ˜μ–΄μ•Ό ν•  동기 Task라고 ν•©λ‹ˆλ‹€.

μ†”μ§νžˆ 무슨 말인지 λ„ˆλ¬΄ ν—·κ°ˆλ¦¬λ„€μš”β€¦ πŸ˜…

결둠은 이제 β€œActor μ—μ„œ DispatchQueue λ₯Ό μ§€μ •ν•΄μ„œ μ‚¬μš©ν•  수 있고, μ΄λŠ” DispatchQueueκ°€ μƒˆλ‘œμš΄ SerialExecutor ν”„λ‘œν† μ½œμ„ λ”°λ₯΄κΈ° λ•Œλ¬Έμ΄λ‹€. SerialExecutor ν”„λ‘œν† μ½œμ€ dispatch asyncλ₯Ό ν¬ν•¨ν•œ μ—¬λŸ¬ Job 듀을 순차적으둜 queue λ₯Ό 톡해 μˆ˜ν–‰ν•΄λ‚˜κ°€λŠ” μž‘μ—…μ„ ν•œλ‹€.” 라고 μ΄ν•΄ν•˜κ³  λ„˜μ–΄κ°€λ €κ³  ν•©λ‹ˆλ‹€.


μ™€μš°..

λ’€λ‘œ 갈수둝 정신이 μ•„λ“ν•΄μ§ˆ λ»” ν–ˆμ§€λ§Œ.. ν•œ 번 μ­‰ 훑어보긴 ν–ˆμŠ΅λ‹ˆλ‹€..

λ‹Ήμž₯ μ €μ—κ²Œ 제일 μ€‘μš”ν–ˆλ˜ λ‚΄μš©λ“€μ€

  1. Macros
  2. Type Parameter Packs
  3. DispatchQueue in Actor

μ΄λ ‡κ²ŒμΈ 것 κ°™μ•„μš”!

νŠΉνžˆλ‚˜ λ§€ν¬λ‘œλŠ” μ œλŒ€λ‘œ ν•œ 번 μ‚΄νŽ΄λ΄μ•Ό ν•  것 κ°™μŠ΅λ‹ˆλ‹€..