일반 프로토콜을 변수 유형으로 사용하는 방법
프로토콜이 있다고 가정 해 봅시다.
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)
컴파일러가 다음 오류로 불평합니다.
"프로토콜 '인쇄 가능'은 자체 또는 관련 유형 요구 사항이 있으므로 일반 제약 조건으로 만 사용할 수 있습니다."
내가 뭘 잘못하고 있니? 어쨌든 이것을 고치려면?
**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)
편집 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가 지적했듯이, 유형을 전혀 제공하지 않음으로써 변수를 선언 할 수 있습니다 (또는 명시 적으로 type으로 제공 할 수 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()
위 코드에서 유형은 무엇 x
입니까? Int
? 아니면 String
? Swift에서 모든 유형은 컴파일 타임에 수정되어야합니다. 함수는 런타임에 결정된 요소에 따라 한 유형을 반환하는 것에서 다른 유형으로 동적으로 이동할 수 없습니다.
대신 StoredType
일반 제약 조건으로 만 사용할 수 있습니다 . 모든 종류의 저장된 유형을 인쇄하려고한다고 가정합니다. 다음과 같은 함수를 작성할 수 있습니다.
func printStoredValue<S: StoringType>(storer: S) {
let x = storer.getStored()
println(x)
}
printStoredValue(intStorer)
printStoredValue(stringStorer)
컴파일 타임에 컴파일러 printStoredValue
가 Int
s 용과 s 용 의 두 가지 버전을 작성하는 것처럼 괜찮습니다 String
. 이 두 버전 내에서은 x
특정 유형으로 알려져 있습니다.
There is one more solution that hasn't been mentioned on this question, which is using a technique called type erasure. To achieve an abstract interface for a generic protocol, create a class or struct that wraps an object or struct that conforms to the protocol. The wrapper class, usually named 'Any{protocol name}', itself conforms to the protocol and implements its functions by forwarding all calls to the internal object. Try the example below in a playground:
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
The type of printer
is known to be AnyPrinter<Int>
and can be used to abstract any possible implementation of the Printer protocol. While AnyPrinter is not technically abstract, it's implementation is just a fall through to a real implementing type, and can be used to decouple implementing types from the types using them.
One thing to note is that AnyPrinter
does not have to explicitly retain the base instance. In fact, we can't since we can't declare AnyPrinter
to have a Printer<T>
property. Instead, we get a function pointer _print
to base's print
function. Calling base.print
without invoking it returns a function where base is curried as the self variable, and is thusly retained for future invocations.
Another thing to keep in mind is that this solution is essentially another layer of dynamic dispatch which means a slight hit on performance. Also, the type erasing instance requires extra memory on top of the underlying instance. For these reasons, type erasure is not a cost free abstraction.
Obviously there is some work to set up type erasure, but it can be very useful if generic protocol abstraction is needed. This pattern is found in the swift standard library with types like AnySequence
. Further reading: http://robnapier.net/erasure
BONUS:
If you decide you want to inject the same implementation of Printer
everywhere, you can provide a convenience initializer for AnyPrinter
which injects that type.
extension AnyPrinter {
convenience init() {
let nsLogger = NSLogger<T>()
self.init(base: nsLogger)
}
}
let printer = AnyPrinter<Int>()
printer.print(10) //prints 10 with NSLog
This can be an easy and DRY way to express dependency injections for protocols that you use across your app.
Addressing your updated use case:
(btw Printable
is already a standard Swift protocol so you’d probably want to pick a different name to avoid confusion)
To enforce specific restrictions on protocol implementors, you can constrain the protocol's typealias. So to create your protocol collection that requires the elements to be printable:
// 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
}
Now if you wanted to implement a collection that could only contain printable elements:
struct MyPrintableCollection<T: Printable>: PrintableCollectionType {
typealias Generator = IndexingGenerator<T>
// etc...
}
However, this is probably of little actual utility, since you can’t constrain existing Swift collection structs like that, only ones you implement.
Instead, you should create generic functions that constrain their input to collections containing printable elements.
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
'Nice programing' 카테고리의 다른 글
초기화되지 않은 개체와 NULL로 초기화 된 개체 (0) | 2020.10.05 |
---|---|
Entity Framework가 자식 개체를 저장 / 삽입하지 못하도록하려면 어떻게해야합니까? (0) | 2020.10.05 |
Java에서 객체를 null에 할당하면 가비지 수집에 영향을 줍니까? (0) | 2020.10.05 |
ContextMenuStrip이 사용 된 컨트롤 확인 (0) | 2020.10.05 |
JavaScript로 SVG의 Z 인덱스 / 레이어를 변경할 수 있습니까? (0) | 2020.10.04 |