programing

일반 프로토콜을 변수 유형으로 사용하는 방법

yellowcard 2023. 8. 28. 20:56
반응형

일반 프로토콜을 변수 유형으로 사용하는 방법

제가 프로토콜을 가지고 있다고 가정해 보겠습니다.

public protocol Printable {
    typealias T
    func Print(val:T)
}

그리고 여기 구현이 있습니다.

class Printer<T> : Printable {

    func Print(val: T) {
        println(val)
    }
}

제 예상은 제가 사용할 수 있어야 한다는 것이었습니다.Printable변수는 다음과 같은 값을 인쇄합니다.

let p:Printable = Printer<Int>()
p.Print(67)

컴파일러가 다음 오류로 불만을 제기하고 있습니다.

"프로토콜 'Printable'은 자체 또는 관련 형식 요구 사항이 있기 때문에 일반 제약 조건으로만 사용할 수 있습니다.

내가 뭘 잘못하고 있나요?그런데 이걸 고치려고요?

**EDIT :** Adding similar code that works in C#

public interface IPrintable<T> 
{
    void Print(T val);
}

public class Printer<T> : IPrintable<T>
{
   public void Print(T val)
   {
      Console.WriteLine(val);
   }
}


//.... inside Main
.....
IPrintable<int> p = new Printer<int>();
p.Print(67)

EDIT 2: 제가 원하는 것의 실제 예입니다.이것은 컴파일이 아니라 제가 이루고 싶은 것을 보여줍니다.

protocol Printable 
{
   func Print()
}

protocol CollectionType<T where T:Printable> : SequenceType 
{
   .....
   /// here goes implementation
   ..... 
}

public class Collection<T where T:Printable> : CollectionType<T>
{
    ......
}

let col:CollectionType<Int> = SomeFunctiionThatReturnsIntCollection()
for item in col {
   item.Print()
}

Thomas가 지적했듯이, 당신은 당신의 변수를 선언할 수 있습니다. 당신은 전혀 유형을 지정하지 않고 (또는 당신은 그것을 유형으로 명시적으로 제공할 수 있습니다.Printer<Int>하지만 여기 당신이 어떤 유형의 것을 가질 수 없는 이유에 대한 설명이 있습니다.Printable의전

일반 프로토콜처럼 연결된 유형의 프로토콜을 처리하고 독립 실행형 변수 유형으로 선언할 수 없습니다.그 이유에 대해 생각하기 위해, 이 시나리오를 고려합니다.일부 임의 유형을 저장하고 다시 가져오기 위한 프로토콜을 선언했다고 가정합니다.

// a general protocol that allows for storing and retrieving
// a specific type (as defined by a Stored typealias
protocol StoringType {
    typealias Stored

    init(_ value: Stored)
    func getStored() -> Stored
}

// An implementation that stores Ints
struct IntStorer: StoringType {
    typealias Stored = Int
    private let _stored: Int
    init(_ value: Int) { _stored = value }
    func getStored() -> Int { return _stored }
}

// An implementation that stores Strings
struct StringStorer: StoringType {
    typealias Stored = String
    private let _stored: String
    init(_ value: String) { _stored = value }
    func getStored() -> String { return _stored }
}

let intStorer = IntStorer(5)
intStorer.getStored() // returns 5

let stringStorer = StringStorer("five")
stringStorer.getStored() // returns "five"

좋아요, 아직까지는 좋아요.

변수의 종류가 실제 유형이 아닌 프로토콜을 구현하는 것이 가장 중요한 이유는 프로토콜에 적합한 다른 종류의 개체를 동일한 변수에 할당할 수 있기 때문입니다. 그리고 실제로 개체가 무엇인지에 따라 런타임에 다형성 동작을 수행할 수 있습니다.

그러나 프로토콜에 연결된 유형이 있는 경우에는 이 작업을 수행할 수 없습니다.다음 코드는 실제로 어떻게 작동합니까?

// as you've seen this won't compile because
// StoringType has an associated type.

// randomly assign either a string or int storer to someStorer:
var someStorer: StoringType = 
      arc4random()%2 == 0 ? intStorer : stringStorer

let x = someStorer.getStored()

위의 코드에서, 어떤 종류의xbe? anInt오라StringSwift에서 모든 유형은 컴파일 시에 고정되어야 합니다.함수는 런타임에 결정된 요인을 기반으로 한 유형을 반환하는 것에서 다른 유형으로 동적으로 전환할 수 없습니다.

대신에, 당신은 오직 사용할 수 있습니다.StoredType일반적인 제약으로서저장된 모든 유형을 인쇄하려고 했다고 가정합니다.다음과 같은 함수를 작성할 수 있습니다.

func printStoredValue<S: StoringType>(storer: S) {
    let x = storer.getStored()
    println(x)
}

printStoredValue(intStorer)
printStoredValue(stringStorer)

컴파일할 때 컴파일러가 두 가지 버전을 쓰는 것과 같기 때문에 이것은 괜찮습니다.printStoredValue을 위한 하나.Ints, 그리고 하나는String그 두 버전 내에서x특정 유형의 것으로 알려져 있습니다.

이 질문에 대해 언급되지 않은 해결책이 하나 더 있는데, 바로 유형 삭제라는 기술을 사용하는 것입니다.일반 프로토콜에 대한 추상 인터페이스를 사용하려면 프로토콜을 준수하는 개체 또는 구조체를 래핑하는 클래스 또는 구조체를 만듭니다.일반적으로 'Any{protocol name}'이라는 이름의 래퍼 클래스는 자체적으로 프로토콜을 준수하며 모든 호출을 내부 개체로 전달하여 함수를 구현합니다.놀이터에서 아래의 예를 시도해 보십시오.

import Foundation

public protocol Printer {
    typealias T
    func print(val:T)
}

struct AnyPrinter<U>: Printer {

    typealias T = U

    private let _print: U -> ()

    init<Base: Printer where Base.T == U>(base : Base) {
        _print = base.print
    }

    func print(val: T) {
        _print(val)
    }
}

struct NSLogger<U>: Printer {

    typealias T = U

    func print(val: T) {
        NSLog("\(val)")
    }
}

let nsLogger = NSLogger<Int>()

let printer = AnyPrinter(base: nsLogger)

printer.print(5) // prints 5

.printer로 알려져 있습니다.AnyPrinter<Int>가능한 프린터 프로토콜 구현을 추상화하는 데 사용할 수 있습니다.AnyPrinter는 기술적으로 추상적이지는 않지만 실제 구현 유형으로 전환되는 과정일 뿐이며, 구현 유형을 사용하는 유형에서 분리하는 데 사용할 수 있습니다.

한 가지 주의할 점은AnyPrinter기본 인스턴스를 명시적으로 유지할 필요는 없습니다.사실, 우리는 신고할 수 없기 때문에 할 수 없습니다.AnyPrinter다가 나다Printer<T>소유물.대신, 우리는 함수 포인터를 얻습니다._print기본적으로print기능.부르기base.print호출하지 않고 기본값이 자체 변수로 큐리되는 함수를 반환하므로 이후 호출을 위해 유지됩니다.

또한 이 솔루션은 기본적으로 동적 디스패치의 또 다른 계층으로, 성능에 약간의 영향을 미칩니다.또한 유형 삭제 인스턴스에는 기본 인스턴스 위에 추가 메모리가 필요합니다.이러한 이유로 유형 삭제는 비용이 들지 않는 추상화가 아닙니다.

분명히 유형 삭제를 설정해야 하는 작업이 있지만 일반 프로토콜 추상화가 필요한 경우 매우 유용할 수 있습니다.이 패턴은 다음과 같은 유형의 빠른 표준 라이브러리에서 발견됩니다.AnySequence추가 읽기: http://robnapier.net/erasure

보너스:

이 같은 의 일한구주입결정경을 주입하고 한다면,Printer할 수 .AnyPrinter그런 유형을 주입하는 거죠

extension AnyPrinter {

    convenience init() {

        let nsLogger = NSLogger<T>()

        self.init(base: nsLogger)
    }
}

let printer = AnyPrinter<Int>()

printer.print(10) //prints 10 with NSLog

이는 앱 전체에서 사용하는 프로토콜에 대한 종속성 주입을 쉽고 간편하게 표현할 수 있는 방법입니다.

업데이트된 사용 사례 해결:

(btw))Printable이미 표준 Swift 프로토콜이므로 혼동을 피하기 위해 다른 이름을 선택하는 것이 좋습니다.)

프로토콜 구현자에 특정 제한을 적용하려면 프로토콜의 유형 별칭을 제한할 수 있습니다.요소를 인쇄할 수 있어야 하는 프로토콜 컬렉션을 만들려면 다음과 같이 하십시오.

// because of how how collections are structured in the Swift std lib,
// you’d first need to create a PrintableGeneratorType, which would be
// a constrained version of GeneratorType
protocol PrintableGeneratorType: GeneratorType {
    // require elements to be printable:
    typealias Element: Printable
}

// then have the collection require a printable generator
protocol PrintableCollectionType: CollectionType {
    typealias Generator: PrintableGenerator
}

인쇄 가능한 요소만 포함할 수 있는 컬렉션을 구현하려면 다음과 같이 하십시오.

struct MyPrintableCollection<T: Printable>: PrintableCollectionType {
    typealias Generator = IndexingGenerator<T>
    // etc...
}

그러나 기존 Swift 컬렉션 구조를 제한할 수 없기 때문에 구현한 구조만 제한할 수 있으므로 실제 유용성은 거의 없습니다.

대신 인쇄 가능한 요소를 포함하는 컬렉션에 입력을 제한하는 일반 함수를 만들어야 합니다.

func printCollection
    <C: CollectionType where C.Generator.Element: Printable>
    (source: C) {
        for x in source {
            x.print()
        }
}

언급URL : https://stackoverflow.com/questions/27725803/how-to-use-generic-protocol-as-a-variable-type

반응형