What is Swift Attributes? @_@

DAMIN KIM
daily-monster
Published in
31 min readApr 3, 2024

안녕하세요 수요괴물 damin 입니다! swiftUI를 조금씩 공부하면서 마주치게 되는 @ 표시! attribute 에 대해서 어떤 것들이 있는지 한번 간단하게 훑어보려고 합니다!

잘못된 부분이 있다면 댓글로 피드백 부탁드릴게요!

Attribute란?

컴퓨팅 분야에서 속성(영어: attribute)이란 객체, 요소, 또는 파일의 성질이다. 속성은 또한 이들의 인스턴스(instance)에 주어진 특정 값을 지정하거나 나타내는 데에도 쓰인다. 속성은 메타데이터로 간주하는 것이 더 정확하다. 속성은 일반적으로 성질의 성질이다. 그러나 실제 사용될 때 속성(attribute)이란 용어는 논의되는 기술에 따라 성질(property)과 동일하게 간주되기도 한다.

오브젝트의 속성은 보통 이름과 값으로 구성된다. 요소의 속성은 타입과 클래스 이름으로, 파일의 속성은 이름과 확장자로 구성되어 있다.

attribute와 property의 차이는 뭘까요? (In 웹프로그래밍)

attribute는 정적으로 변하지 않고 property는 동적으로 그 값이 변할 수 있다는 의미를 내포하고 있다.

그럼 Swift에서 Attributes는?

Attributes

Add information to declarations and types = 선언과 타입에 정보를 더하다

그래서 Swift의 Attributes는 크게 2가지로 분류할 수 있어요

선언 속성 — Declaration Attributes

타입 속성 — Type Attributes

Syntax

@attributeName name
@attributeName name(attribute arguments)

선언 Attributes 의 경우 선언을 정의하기 위해 요렇게 @와 함께 Attributes 이름과 소괄호와 함께 Attributes 인자를 적을 수 있습니다.

Declaration Attributes

선언 Attributes는 선언에만 적용할 수 있음!

@available

특정 swift 언어, iOS 버전, 플랫폼에서만 실행이 가능하도록 도와주는 기능

/// iOS 15 부터 * (상위 모든 버전) 실행가능
if #available(iOS 15, *) {
print("This code will run on iOS 15 and upper versions.")
} else {
print("This code will only runs on iOS 14 and lower versions.")
}

/// 이렇게 쓰면 특정 iOS버전에서만 동작하도록 할 수 있음
guard #available(iOS 15, *) else {
print("This code will only runs on iOS 14 and lower versions.")
return
}
print("This code will run on iOS 15 and upper versions.")

왜 여기서 @대신 #을 쓰지? → #available이랑 @available이랑 똑같은데 #available를 쓰면 위 코드처럼 조건문을 달아 줄 수 있습니다.

@dynamicMemberLookup

이 Attributes를 쓰는 타입은 subscript(dynamicMember: )를 구현해야합니다.
그래서 subscript에 대해서 먼저 알아야하는데 간단하게 살펴 보면 배열과 같은 swift collection에서 많이 사용하고 있는것으로

var arr = ["개", "말", "양"]
arr[0] // "개"
arr[1] // "말"

이렇게 인덱스로 값을 가져올 수 있는게 collection protocol이 subscript를 사용하고 있기 때문입니다.

subscript(position: Self.Index) -> Self.Element { get }

그래서 딕셔너리를 가지는 구조체에 subscript를 구현해주면

struct DynamicStruct {
let dictionary = ["one": 111,
"two": 222]
subscript(member: String) -> Int? {
return self.dictionary[member]
}
}
let dynamicStruct = DynamicStruct()
print(dynamicStruct["one"]!) // 111

이렇게 배열의 인덱스에 접근하듯 키를 넣어 값을 가지고 올 수 있습니다.

근데 여기서 @dynamicMemberLookup을 붙이면 우리가 프로퍼티에 접근 할때 쓰는것처럼 dot 문법을 이용해 값에 접근할 수 있다고 합니다.

@dynamicMemberLookup
struct DynamicStruct {
let dictionary = ["someDynamicMember": 325,
"someOtherMember": 787]
// subscript 는 dynamicMember를 인자로 가져야함!
subscript(dynamicMember member: String) -> Int {
return dictionary[member] ?? 1054
}
}

let s = DynamicStruct()
// Use dynamic member lookup.
// 요렇게 dot 문법으로 접근가능! - syntaxsugar
let dynamic = s.someDynamicMember
print(dynamic)
// Prints "325"
// Call the underlying subscript directly.
// 이렇게 subscript 쓸 때처럼 [] 에 dynamicMember 인자 넣어 직접 접근 가능
let equivalent = s[dynamicMember: "someDynamicMember"]
print(dynamic == equivalent)
// Prints "true"

KeyPath 로 요렇게 컴파일 타임 타입 체킹을 지원하는 wrapper type을 쓸 수도 있습니다.


struct Point { var x, y: Int }

@dynamicMemberLookup
struct PassthroughWrapper<Value> {
var value: Value
subscript<T>(dynamicMember member: KeyPath<Value, T>) -> T {
get { return value[keyPath: member] }
}
}

let point = Point(x: 381, y: 431)
let wrapper = PassthroughWrapper(value: point)
print(wrapper.x)

주의사항

subscript(dynamicMember: )에는 ExpressibleByStringLiteral를 따르는 타입만 넣을 수 있다 (대표적으로 String)

@dynamicCallable

dynamicMemberLookup이 subscript를 요구한것 처럼 이 친구는

dynamicallyCall(withArguments:)dynamicallyCall(withKeywordArguments:) 메서드 중 하나이상을 구현해야 합니다.(둘다 가능)

// dynamicallyCall(withArguments:) 구현 
@dynamicCallable
struct TelephoneExchange {
func dynamicallyCall(withArguments phoneNumber: [Int]) {
if phoneNumber == [4, 1, 1] {
print("Get Swift help on forums.swift.org")
} else {
print("Unrecognized number")
}
}
}

let dial = TelephoneExchange()
// Use a dynamic method call.
dial(4, 1, 1)
// Prints "Get Swift help on forums.swift.org"
dial(8, 6, 7, 5, 3, 0, 9)
// Prints "Unrecognized number"
// 위 함수 정의시 phonumber는 [Int] 타입이라고 되어있지만
// 호출시 그냥 Int값들을 넣으면 배열화가 됨 - syntaxsugar
// Call the underlying method directly.
dial.dynamicallyCall(withArguments: [4, 1, 1]) // 이렇게 명시적 호출도 가능

withArguments에는 ExpressibleByArrayLiteral를 준수하는 타입만 들어갈 수 있습니다. → 대표적으로 Array가 있어요 그리고 리턴타입은 어떤 타입이든 ok입니다

두번째 구현 메서드인 dynamicallyCall(withKeywordArguments:)
는 ExpressibleByDictionaryLiteral를 만족하는 타입 하나의 parameter로 가질 수 있습니다. 이 타입은 KeyValuePairsDictionary 가 있습니다

@dynamicCallable
struct Repeater {
func dynamicallyCall(withKeywordArguments pairs: KeyValuePairs<String, Int>) -> String {
return pairs
.map { label, count in
repeatElement(label, count: count).joined(separator: " ")
}
.joined(separator: "\\n")
}
}

let repeatLabels = Repeater()
print(repeatLabels(a: 1, b: 2, c: 3, b: 2, a: 1))
// a
// b b
// c c c
// b b
// a

이것도 역시 리턴타입은 아무거나 가능해요

@discardableResult

이 Attributes는 이름 그대로 Result를 버리는? Attributes로

func 리턴스트링() -> String {
return "스트링"
}
리턴스트링()

Result of call to ‘리턴스트링’ is unused
이라고 귀찮게 Wraning을 뿜을텐데

@discardableResult
func 리턴스트링() -> String {
return "스트링"
}

리턴스트링()

@discardableResult를 메서드에 적용해주면 워닝이 사라집니다!

@frozen

이름처럼 얼려서 고정시키는 느낌? enum 이나 구조체에 적용되는데

@frozen
enum SomeEnum {
case A
case B
case C
}

위처럼 frozen을 쓰면 enum의 case 가 고정된다고 알려주는거라

switch SomeEnum {
case .A: break
case .B: break
case .C: break
}

이렇게 default 없이 case만 구현해도 됩니다.(frozen이 없었으면 @unknown default를 추가하라고 경고가 뜹니다.)

When the compiler isn’t in library evolution mode, all structures and enumerations are implicitly frozen, and this attribute is ignored. 컴파일러가 library evolution mode가 아니면 암시적으로 다 frozen으로 적용된다고함

근데 Library evolution supports 는 기본적으로 꺼져있기 때문에 크게 신경쓸 필요가 없을것 같아요

@main

(Xcode12이전 까지 @UIApplicationMain으로 사용했다가 @main으로 바뀌었다고합니다. )

@main은 프로그램 실행 시작 시 진입점으로 타입을 지정하기 위한 Swift 언어의 기능이다. 사용자는 탑 레벨의 코드를 작성하는 대신 @main단일 유형의 속성을 사용할 수 있고, 라이브러리와 프레임워크는 프로토콜이나 클래스 상속을 통해 맞춤형 진입점 동작을 제공할 수 있다.

다른 언어에서 main 함수가 시작점이 되는것처럼 swift에서 @main 속성을 통해 진입점을 지정할 수 있습니다.

일반적으로 AppDelegate 클래스에 적용되는것으로 컴파일러에게 AppDelegate를 Entry Point = 시작점이라고 알려주는것입니다.

@NSApplicationMain

이 Attributes안쓰면 main.swift를 TOP LEVEL 코드에서 NSApplicationMain() 호출하면 됩니다.

@Objc

다들 많이 본 이Attributes는 이름에서 알 수 있듯이 swift로 선언된 클래스, 메서드, 프로퍼티 등에 이 Attributes를 적용해주면 objective-c코드에서 참조 할 수 있습니다.

import Foundation
class UserValidator {
@objc func authenticateUser() {
// statements to validate user details
}
}

컴파일 타임에 메서드를 바인딩하는 Swift와는 다르게 Objective-C는 런타임에 메서드를 바인딩합니다.

따라서 Swift에서 Objective-C에 의존적인 메서드(Selector)에 사용할 때는 해당 메서드가 Objective-C와 유사한 방식으로 컴파일 되어야함을 컴파일러에게 알려주어야 합니다.

이를 의미하는 것이 @objc입니다. 이를 메서드 앞에 붙여주면 Swift로 작성된 코드를 Objective-C 런타임에서도 사용할 수 있게됩니다. (cf. 만약 class가 NSObject를 상속하고 있는 경우 해당 키워드가 필요하지 않습니다.)
- 월요괴물 ian-

@objcMembers

objc 와 다르게 class에만 적용됩니다.(swift4이상)

class UserManager {
private var users = ["leo","ninha","mike","pepijn","alan"]

private let url = "myurl.com"

func add(user: String) {
users.append(user)
print(users)
}
}
// @objc와 NSObject를 상속하여 옵씨 코드에서 UserManager 클래스에 접근 가능
@objc
class UserManager: NSObject {
private var users = ["leo","ninha","mike","pepijn","alan"]

private let url = "myurl.com"

func add(user: String) {
users.append(user)
print(users)
}
}
@objc
class UserManager: NSObject {
private var users = ["leo","ninha","mike","pepijn","alan"]

private let url = "myurl.com"

@objc // 이렇게 따로 또 붙여줘야됨
func add(user: String) {
users.append(user)
print(users)
}
}
@objc
class UserManager: NSObject {
private var users = ["leo","ninha","mike","pepijn","alan"]

private let url = "myurl.com"

@objc
func add(user: String) {
users.append(user)
}

@objc
func remove(user: String) {
users.removeAll { $0 == user }
}

@objc
func logUsers() {
print(users)
}
}
// 함수가 여러개일 경우 이렇게 따로 붙이거나
// 아래처럼 extension으로 따로 빼서 붙여줌
class UserManager: NSObject {
private var users = ["leo","ninha","mike","pepijn","alan"]

private let url = "myurl.com"

func logUsers() {
print(users)
}
}

@objc // add this
extension UserManager {
func add(user: String) {
users.append(user)
}

func remove(user: String) {
users.removeAll { $0 == user }
}
}

근데 클래스 내에 많은 선언들을 옵씨코드에서 사용하려면 @objcMembers를 사용하면됨 @objc 필요하지 않은 속성을 적용하면 이진 크기가 커지고 성능에 부정적인 영향을 줄 수 있습니다.고 하니 @objcMembers를 쓰는것을 권장한다고 합니다.

@objcMembers // just need to add this
class UserManager: NSObject {
private var users = ["leo","ninha","mike","pepijn","alan"]

private let url = "myurl.com"

func add(user: String) {
users.append(user)
}

func remove(user: String) {
users.removeAll { $0 == user }
}

func logUsers() {
print(users)
}
}

@GKInspectable

Apply this attribute to expose a custom GameplayKit component property to the SpriteKit editor UI. Applying this attribute also implies the objc attribute.

SpriteKit editor UI에서 GameplayKit 컴포턴트를 접근할 수 있게함, objc 속성 효과 적용됩니다.

@nonobjc

이름처럼 objc나 objcMembers에 대해 예외를 적용하는거라 생각하면 될 것 같아요

@objcMembers // just need to add this
class UserManager: NSObject {
private var users = ["leo","ninha","mike","pepijn","alan"]

private let url = "myurl.com"

func add(user: String) {
users.append(user)
}

func remove(user: String) {
users.removeAll { $0 == user }
}
}
@nonobjc
extension UserManager {

func logUsers() {
print(users)
}
}

Declaration Attributes Used by Interface Builder

스토리보드의 친구들 @IBAction과 @IBOutlet, @IBDesignable, @IBInspectable의 인터페이스 빌더도 Attributes를 이용해 선언을 해서 사용했죠

import UIKit
class LoginController: UIViewController {
@IBOutlet var sendButton: UIButton!
@IBAction func handleSendButtonTapped(_ sender: UIButton) {
// perform send action here
}
}

@PropertyWrapper

이 Attributes는 property의 값을 override할 때 유용합니다., @propertyWrapper를 class나 struct에 사용해서 프로퍼티가 특정 타입처럼 행동하게 wrapping 할 수 있습니다.

import Foundation
@propertyWrapper struct UserName {
var wrappedValue: String {
didSet { wrappedValue = wrappedValue.lowercased() }
}
init(wrappedValue: String) {
self.wrappedValue = wrappedValue.lowercased()
}
}
struct InstagramUser {
@UserName var firstName: String
@UserName var lastName: String

var fullUserName: String {
"\\(firstName)-\\(lastName)"
}
}
let user = InstagramUser(firstName: "DAMIN", lastName: "KIM")
print(user.fullUserName) // damin-kim

firstName과 lastName 프로퍼티가 @UserName으로 감싸져서 wrappedValue인 lowercase값이 출력됩니다.

@inlinable

Function, method, computed property, subscript, convenience initializer, or deinitializer declaration 등에 사용할 수 있는데 이 속성을 붙이면 컴파일러가 함수를 이름으로 호출하는게 아니라 본문을 바로 가져다 호출하므로 오버헤드가 적다는 장점이 있다.

@usableFromInline

usableFromInline 속성은 internal로 선언된 곳에서만 사용할 수 있습니다.

public class Person {
@usableFromInline // ✅
internal let birthYear: Int // ✅

...

@inlinable
public func isGenerationZ() -> Bool {
return 1990 <= birthYear && birthYear <= 2010
}
}

@NSCopying

이 친구는 class의 stored value에 붙일 수 있어요 이 Attributes를 붙인 프로퍼티의 setter는 프로퍼티 값의 복사본을 copyWithZone(_:)이라는 메서드를 통해 리턴하게 됩니다. 이 프로퍼티는 NSCopying 프로토콜을 채택한 타입이여야합니다.

(이건 자료가 없어서 잘 모르겠네요..ㅠ)

@NSManaged

NSManagedObject 를 상속하는 클래스의 인스턴스 메서드나 저장된 변수 프로퍼티에 이 속성을 적용 NSManaged 속성으로 표시된 프로퍼티의 경우 코어 데이터 (Core Data) 는 런타임에 스토리지 (storage) 도 제공합니다. 이 속성을 적용하면 objc 속성도 의미합니다.

@requires_stored_property_inits

선언의 일부로 기본값을 제공하기 위해 클래스 내에 모든 저장된 프로퍼티를 요구하기 위해 클래스 선언에 이 속성을 적용합니다. 이 속성은 NSManagedObject 에서 상속한 모든 클래스에 대해 유추됩니다.

이를 적용한 클래스에서는 init할때 stored property 초기해줘야 하는듯합니다.?

@warn_unqualified_access

@main
struct FiveStarsApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
//top-level 함수
func doSomething() {
print("top-level function")
// ...
}
---

struct ContentView: View {
var body: some View {
Button(action: doSomething) {
Text("Tap me")
}
}

// instance 메서드
func doSomething() {
print("instance method")
// ...
}
}

struct ContentView: View {
var body: some View {
Button(action: doSomething) {
Text("Tap me")
}
}

@warn_unqualified_access
func doSomething() {
print("instance method")
// ...
}
}

@warn_unqualified_access 를 적용함으로써 다음 두가지의 선택지를 제안하는 에러를 발생시킵니다.

  1. Use ‘self.’ to silence this warning
  2. Use ‘FiveStars.’ to reference the global function

이렇게 해서 다른 사람이 인스턴스 메서드의 존재를 몰라도 워닝으로 알려줄 수 있어요

@testable

기본 access level인 internal 인 swift에서 @testable을 추가한 import 모듈에 대한 높은 접근 권한을 활성화시켜서 클래스, 멤버에 접근할 수 있다고 합니다 ~ 테스트를 아직 제대로 써본 적이 없어서 모르겠습니다ㅠ

@UIApplicationMain

@unchecked

컴파일러에게 이거 적용한거 무시하라고 알려줌

Macro

매크로선언에 붙이는 속성, 매크로에 대한 것을 다루려면 너무 많아서 이번에는 넘어가겠습니다..

@attached

@freestanding

@resultBuilder

요부분은 swiftUI의 @viewBuilder 가 @resultBuilder 라고하는데 간단히 넘어가기에는 양이 많아 다음에 따로 정리해보겠습니다.

Type Attributes

이 Attributes들은 타입에만 적용할 수 있다!

@autoclosure

이름에서 알 수 있듯이 함수 인자에서 자동으로 클로저를 만들어준다 ( @autoclosure는 기본적으로 non-escaping이기 때문에 escaping 필요시 @autoclosure @escaping () → Void 처럼 같이 써줘야합니다.)

autoclosure가 쓰이는 함수중에 assert()가 있습니다.

public func assert(_ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String = String())

assert함수 호출시 단순히 Bool과 String 타입을 인자로 가지는 함수 처럼 간단히 호출 할 수 있습니다

assert(false, "Error Occurred!")
assert(0 < 1, "Error Occurred!")
assert(false, (statusCode == .fileNotFound) ? "File Not Found!" : "Something going wrong!")

근데 만약 @autoclosure를 쓰지 않았다면

func assert(_ condition: () -> Bool, _ message: String = String()) { }

지저분하게 클로저를 그대로 인자로 넣어 호출해야합니다

assert({ false }, { "Error Occurred!" })
assert({ 0 < 1 }, { "Error Occurred!" })
assert({ false }, {(statusCode == .fileNotFound) ? "File Not Found!" : "Something going wrong!"})

@escaping

다들 아시죠?..여기서는 생략할게요ㅎㅎ

@convention

  • @convention(c): C 호출 규약을 의미하며, C 언어와의 상호 운용성을 위해 사용됩니다.
  • @convention(block): Objective-C 블록 호출 규약을 의미하며, 클로저를 Objective-C 코드에 전달할 때 사용됩니다.
  • @convention(swift): Swift 호출 규약을 의미하며, Swift 코드에서 사용되는 클로저입니다.

아래는 @convention 속성의 사용 예시입니다:

swiftCopy code
// Swift 클로저를 Objective-C 메서드에 전달하는 예제
import Foundation

@objc class ExampleClass: NSObject {
// Objective-C 메서드에 Swift 클로저를 전달할 때 @convention(block) 속성 사용
@objc func performOperation(completion: @convention(block) () -> Void) {
completion()
}
}
let obj = ExampleClass()
obj.performOperation {
print("Operation completed")
}

이 예제에서 @convention(block) 속성은 Swift 클로저를 Objective-C 블록으로 변환하여 사용하도록 지정합니다. 이렇게 함으로써 Swift와 Objective-C 간의 호환성을 유지하면서 클로저를 사용할 수 있게 됩니다.

요약하면, @convention 속성은 함수나 클로저의 호출 규약을 지정하여 다른 언어와의 상호 운용성을 유지하거나 인터페이스를 구현할 때 사용되며, C 호출 규약, Objective-C 블록 호출 규약, Swift 호출 규약 등을 지정할 수 있습니다.

Switch Case Attributes

Attributes는 SwitchCase에만 적용할 수 있음!

@unknown

switch case 구문을 쓸 때 딱 정의할 필요 없는 케이스에 대해서는 default로 처리되도록 하는데 이 Attributes를 default에 붙이면 default가 있더라도 정의되지 않은 case가 있음을 warning으로 알려준다고 해요

나는 현재 모든 case에 다 정의 했는데 나중에 case가 추가가 될 경우 warning을 통해 알 수 있을 것 같습니다.

enum Gender {
case male
case female
case transgender
case two_spirit
case non_binary
}

func printGender(gender: Gender) {
switch gender {
case .male:
print("🙋‍♂️")
case .female:
print("🙋‍♀️")
case .transgender:
print("🔁")
case .two_spirit:
print("🙋‍♂️🙋‍♀️")
@unknown default :
print("👽")
}
}

@convention

  • @convention(c): C 호출 규약을 의미하며, C 언어와의 상호 운용성을 위해 사용
  • @convention(block): Objective-C 블록 호출 규약을 의미하며, 클로저를 Objective-C 코드에 전달할 때 사용
  • @convention(swift): Swift 호출 규약을 의미하며, Swift 코드에서 사용되는 클로저
// Swift 클로저를 Objective-C 메서드에 전달하는 예제
import Foundation

@objc class ExampleClass: NSObject {
// Objective-C 메서드에 Swift 클로저를 전달할 때 @convention(block) 속성 사용
@objc func performOperation(completion: @convention(block) () -> Void) {
completion()
}
}
let obj = ExampleClass()
obj.performOperation {
print("Operation completed")
}

이 예제에서 @convention(block) 속성은 Swift 클로저를 Objective-C 블록으로 변환하여 사용하도록 지정합니다. 이렇게 함으로써 Swift와 Objective-C 간의 호환성을 유지하면서 클로저를 사용할 수 있게 됩니다.

요약하면, @convention 속성은 함수나 클로저의 호출 규약을 지정하여 다른 언어와의 상호 운용성을 유지하거나 인터페이스를 구현할 때 사용되며, C 호출 규약, Objective-C 블록 호출 규약, Swift 호출 규약 등을 지정할 수 있습니다.

@sendable

Sendable functions and closures must use only by-value captures, and the captured values must be of a sendable type. In a context that expects a sendable closure, a closure that satisfies the requirements implicitly conforms to Sendable. Otherwise, you mark sendable functions and closures with the @Sendable attribute explicitly.

전송 가능한 함수와 클로저는 값에 의한 캡처만을 사용해야 하며, 캡처된 값은 전송 가능한 타입이어야 합니다. 전송 가능한 클로저를 기대하는 상황에서 요구 사항을 충족하는 클로저는 암시적으로 Sendable에 부합합니다. 그렇지 않은 경우, 전송 가능한 함수와 클로저를 명시적으로 @Sendable 속성으로 표시합니다.

let hello = { @Sendable (name: String) -> String in
return "Hello" + name
}

func runLater(_ function: @escaping @Sendable () -> Void) {
DispatchQueue.global().asyncAfter(deadline: .now() + 3, execute: function)
}
func globalFunction(arr: [Int]) {
var state = 42
@Sendable
func mutateLocalState2(value: Int) {
// Error: 'state' is captured as a let because of @Sendable
state += value
}
}

Sendable 프로토콜의 기능을 함수나 클로저에 적용한것 같아요

Sendable 프로토콜은 간단하게 설명하면 채택시 컴파일단에서 swift의 Concrruency에서 thread-safe한 코드를 쓰도록 요구해서 sendable 타입은 thread간에 값을 안전하게 넘겨 줄 수 있습니다.
( actor 같은것을 써서 thread-safe하게 만들 수 있지만 그러면 기존의 코드를 리팩토링 해야하고 actor를 써도 thead-safe 하지 않는 경우도 있기때문에 sendable을 써서 그런 문제를 해결할 수 있다합니다.. )

Sendable 함수나 클로저는 value 캡처를 사용해야하고 캡처된 value는 Sendable 타입 이여야합니다.

Sendable 조건을 만족하는 함수, 클로저는 암시적으로 이미 Sendable을 채택하고 있는데 이를 명시적으로 표현하기 위해 @Sendable을 붙여줄 수 있습니다

class User: Sendable {
let name: String

init(name: String) { self.name = name }
}

그럼 위 User 클래스는 Sendable을 conform할 수 있을까요?

….

User 는 final class가 아니라 sendable을 채택할 수 없습니다! 그래서 아래 Attributes를 씁니다.

@unchecked

프로토콜에 적용해서 컴파일러에게 해당 프로토콜의 요구사항을 무시하라는 Attributed입니다.

위 Sendable 경고 메시지에서 Sendable에 unchecked를 붙여서 쓰라고 경고 메시지가 떠있습니다.

class User: @unchecked Sendable {
let name: String

init(name: String) { self.name = name }
}

근데 이렇게 쓰면 컴파일러가 thread-safe인지 체크 해주지 못하기 때문에 우리가 직접 thread-safe하도록 유지해줘야 하기 때문에 이렇게 쓸바에 final 써서 바로 Sendable 커펌하게 하는게 낫다고 합니다.

--

--