IT이야기

Swift - 프로토콜 확장 - 속성 기본값

cyworld 2021. 10. 15. 21:06
반응형

Swift - 프로토콜 확장 - 속성 기본값


다음 프로토콜이 있다고 가정해 보겠습니다.

protocol Identifiable {
    var id: Int {get}
    var name: String {get}
}

그리고 다음 구조체가 있습니다.

struct A: Identifiable {
    var id: Int
    var name: String
}

struct B: Identifiable {
    var id: Int
    var name: String
}

보시다시피 구조체 A와 구조체 B의 식별 가능한 프로토콜을 '준수'해야 했습니다. 하지만 이 프로토콜을 준수해야 하는 구조체가 N개 더 있다고 상상해 보세요... '복사/붙여넣기'를 하고 싶지 않습니다. ' 적합성(var id: Int, var name: String)

그래서 프로토콜 확장을 만듭니다 .

extension Identifiable {
    var id: Int {
        return 0
    }

    var name: String {
        return "default"
    }
}

이제 이 확장을 사용하여 두 속성을 모두 구현하지 않고도 Identifiable 프로토콜을 준수하는 구조체를 만들 수 있습니다.

struct C: Identifiable {

}

이제 문제는 id 속성이나 name 속성에 값을 설정할 수 없다는 것입니다.

var c: C = C()
c.id = 12 // Cannot assign to property: 'id' is a get-only property

이것은 Identifiable 프로토콜에서 id와 name만 얻을 수 있기 때문에 발생합니다. 이제 id 및 name 속성을 {get set}으로 변경 하면 다음 오류가 발생합니다.

유형 'C'는 '식별 가능' 프로토콜을 준수하지 않습니다.

이 오류는 프로토콜 확장에 setter를 구현하지 않았기 때문에 발생합니다... 그래서 프로토콜 확장을 변경합니다.

extension Identifiable {
    var id: Int {
        get {
            return 0
        }

        set {

        }
    }

    var name: String {
        get {
            return "default"
        }

        set {

        }
    }
}

이제 오류는 사라지지만 새 값을 id 또는 name으로 설정하면 기본값(getter)이 설정됩니다. 물론, setter는 비어 있습니다.

내 질문은 : 어떤 코드 조각을 setter 안에 넣어야합니까? self.id = newValue를 추가 하면 충돌(재귀)하기 때문입니다.

미리 감사드립니다.


stored property프로토콜 확장을 통해 유형 에 추가하려는 것 같습니다 . 그러나 확장을 사용하면 저장 속성을 추가할 수 없기 때문에 불가능합니다.

몇 가지 대안을 제시할 수 있습니다.

서브클래싱(객체 지향 프로그래밍)

가장 쉬운 방법(아마도 이미 상상한 대로)은 구조체 대신 클래스를 사용하는 것입니다.

class IdentifiableBase {
    var id = 0
    var name = "default"
}

class A: IdentifiableBase { }

let a = A()
a.name  = "test"
print(a.name) // test

단점: 이 경우 A 클래스는 다음에서 상속해야 하고 IdentifiableBaseSwift에는 다중 상속이 없기 때문에 이것이 A 클래스가 상속할 수 있는 유일한 클래스가 됩니다.

컴포넌트(프로토콜 지향 프로그래밍)

이 기술은 게임 개발에서 꽤 유명합니다.

struct IdentifiableComponent {
    var id = 0
    var name = "default"
}

protocol HasIdentifiableComponent {
    var identifiableComponent: IdentifiableComponent { get set }
}

protocol Identifiable: HasIdentifiableComponent { }

extension Identifiable {
    var id: Int {
        get { return identifiableComponent.id }
        set { identifiableComponent.id = newValue }
    }
    var name: String {
        get { return identifiableComponent.name }
        set { identifiableComponent.name = newValue }
    }
}

이제 유형을 Identifiable단순히 쓰기에 적합 하게 만들 수 있습니다.

struct A: Identifiable {
    var identifiableComponent = IdentifiableComponent()
}

시험

var a = A()
a.identifiableComponent.name = "test"
print(a.identifiableComponent.name) // test

프로토콜 및 프로토콜 확장은 매우 강력하지만 읽기 전용 속성 및 기능에 가장 유용한 경향이 있습니다.

달성하려는 것(기본값이 있는 저장 속성)에 대해 클래스와 상속이 실제로 더 우아한 솔루션일 수 있습니다.

다음과 같은 것:

class Identifiable {
    var id: Int = 0
    var name: String = "default"
}

class A:Identifiable {
}

class B:Identifiable {
}

let a = A()

print("\(a.id) \(a.name)")

a.id = 42
a.name = "foo"

print("\(a.id) \(a.name)")

Objective-C 관련 개체

Objective-C 관련 개체를 사용하여 기본적으로 stored propertya class또는 에 a 추가 할 수 있습니다 protocol. 연결된 개체는 클래스 개체에 대해서만 작동합니다.

import ObjectiveC.runtime

protocol Identifiable: class {
    var id: Int { get set }
    var name: String { get set }
}

var IdentifiableIdKey   = "kIdentifiableIdKey"
var IdentifiableNameKey = "kIdentifiableNameKey"

extension Identifiable {
    var id: Int {
        get { 
            return (objc_getAssociatedObject(self, &IdentifiableIdKey) as? Int) ?? 0
        }
        set {
            objc_setAssociatedObject(self, &IdentifiableIdKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }   
    }

    var name: String {
        get { 
            return (objc_getAssociatedObject(self, &IdentifiableNameKey) as? String) ?? "default"
        }
        set {
            objc_setAssociatedObject(self, &IdentifiableNameKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }   
    }
}

이제 간단히 작성 하여 class준수 할 수 있습니다.Identifiable

class A: Identifiable {

}

시험

var a = A()
print(a.id)    // 0
print(a.name)  // default
a.id = 5
a.name = "changed"
print(a.id)    // 5
print(a.name)  // changed


이것이 속성을 설정할 수 없는 이유입니다.

속성은 ObjC에서와 같이 _x와 같은 지원 변수가 없다는 것을 의미하는 계산된 속성이 됩니다. 아래 솔루션 코드에서 xTimesTwo는 아무 것도 저장하지 않고 단순히 x에서 결과를 계산하는 것을 볼 수 있습니다.

계산된 속성에 대한 공식 문서를 참조하십시오.

원하는 기능은 속성 관찰자일 수도 있습니다.

세터/게터는 Objective-C에서와 다릅니다.

필요한 것은 다음과 같습니다.

var x:Int

var xTimesTwo:Int {
    set {
       x = newValue / 2
    }
    get {
        return x * 2
    }
}

setter/getter 내에서 다른 속성을 수정할 수 있습니다.

ReferenceURL : https://stackoverflow.com/questions/38885622/swift-protocol-extensions-property-default-values

반응형