프로토콜이 자체 준수하지 않습니까?
이 Swift 코드는 왜 보이지 않습니까?
protocol P { }
struct S: P { }
let arr:[P] = [ S() ]
extension Array where Element : P {
func test<T>() -> [T] {
return []
}
}
let result : [S] = arr.test()
컴파일러는 "유형 P이 프로토콜을 준수하지 않는 "라고 말할 수 있습니다. P(또는 이후 버전의 Swift에서 "P"를 사용하는 것은 정확한 유형으로 "P"를 사용하는 것은 지원되지 않습니다. ")
왜 안돼? 이것은 어쨌든 언어의 구멍처럼 죽입니다. 문제 arr는 배열 을 프로토콜 유형 의 배열 로 선언하는 데 기인 생각하지만 부당한 일입니까? 형식 계층 구조와 같은 편의를 제공하는 데 도움이되는 프로토콜이 정확히 무엇이라고 생각 했습니까?
편집 : 새로운 진단을 제공하는 또 다른 주요 릴리스 인 Swift를 사용하여 18 개월 이상 작업하고 @AyBayBay의 의견 으로이 답변을 다시하고 싶습니다. 새로운 진단은 다음과 달라집니다.
" 'P'프로토콜을 따르는 구체적인 유형으로 'P'를 사용하는 것은 지원되지 않습니다."
실제로는 모든 것이 훨씬 더 명확 해집니다. 이 확장은 :
extension Array where Element : P {
Element == P이후 P의 경우 에는 적용되지 않습니다 P. 아래의 "상자에 넣는"솔루션이 여전히 가장 일반적인 솔루션입니다.
기존 답변 :
또 다른 메타 타입 사례입니다. Swift는
구체적인 사소하지 않은 것들에 대해 구체적인 유형에 도달하기를 원합니다.
(실제로는 사실이라고 생각하지 않습니다 . 간접적으로 수행[P]구체적인 유형이 아닙니다 (에 할당 된 메모리 블록을 할당 할 수 없습니다
P).
P 되기 때문에 절대적으로 크기가 큰 것을 만들 수 있습니다 .) 이 "아직 작동하지 않는"사례 중 하나와 매우 사고합니다. (불행히도 Apple이 경우에 차이점을 확인하는 것은 거의 불가능합니다.) 변수 유형일 수있는 사실Array<P>Array)는 이미 일부 작업을 수행했거나 그것을 사용하지 만 Swift 메타 타입도 많고 구현하지 않습니다. 나는 당신이 그것보다 더 나은 "왜"라고 생각하지 않습니다. "컴파일러가 허용하지 않기 때문에." (불만족, 알아요. 내 스위프트의 삶 전체…)
거의 물건을 상자에 넣는 것입니다. 타입 지우개를 만듭니다.
protocol P { }
struct S: P { }
struct AnyPArray {
var array: [P]
init(_ array:[P]) { self.array = array }
}
extension AnyPArray {
func test<T>() -> [T] {
return []
}
}
let arr = AnyPArray([S()])
let result: [S] = arr.test()
Swift가 직접 (직접 기대하는) 작업을 수행 할 수있게하면 자동 으로이 상자를 만드는 것입니다. 재귀 열거 형은이 역사를 정확히 가지고 있습니다. 당신은 신고를 박스에 넣어야, 그것은 매우 성 가시고 제한적이었습니다. 그리고 마침내 컴파일러 indirect는 같은 일을 더 자동적으로하기 위해 추가 할 수 있습니다.
프로토콜이 왜 자신과 맞지 꼭 보입니까?
일반적인 경우에 프로토콜이 자신을 준수하도록 허용하는 것은 좋지 않습니다. 문제는 정적 프로토콜 요구 사항에 있습니다.
여기에는 다음이 포함됩니다.
static방법과 속성- 이니셜 라이저
- 관련 유형 (현재는 실제 유형으로 프로토콜을 사용하지 못하지만)
우리는 일반적인 자리 표시 자 T에서 요구 사항에 액세스 할 수 있습니다. T : P그러나 앞으로 구체적인 형식이 내부 프로토콜 유형에서는 액세스 할 수 없습니다 . 우리는 허용 그러므로 할 수 없습니다 T수 P.
Array확장을 적용 할 수있는 허용 한 경우 다음 예제에서 어떤 일이 발생하는지 고려하십시오 [P].
protocol P {
init()
}
struct S : P {}
struct S1 : P {}
extension Array where Element : P {
mutating func appendNew() {
// If Element is P, we cannot possibly construct a new instance of it, as you cannot
// construct an instance of a protocol.
append(Element())
}
}
var arr: [P] = [S(), S1()]
// error: Using 'P' as a concrete type conforming to protocol 'P' is not supported
arr.appendNew()
는 가능하게 우리 호출 할 수 없습니다 appendNew()에 [P]있기 때문에, P((가) Element) 구체적인 유형 아니므로 인스턴스화 할 수 없습니다. 그것은 해야한다 콘크리트 형식의 요소 유형의 적합에 적합와 배열에 호출 P.
정적 메서드 및 속성 요구 사항과 내용 이야기입니다.
protocol P {
static func foo()
static var bar: Int { get }
}
struct SomeGeneric<T : P> {
func baz() {
// If T is P, what's the value of bar? There isn't one – because there's no
// implementation of bar's getter defined on P itself.
print(T.bar)
T.foo() // If T is P, what method are we calling here?
}
}
// error: Using 'P' as a concrete type conforming to protocol 'P' is not supported
SomeGeneric<P>().baz()
의 관점에서 이야기 할 수 없습니다 SomeGeneric<P>. (이없는 방법에 대한 우리는 더 의 구현 foo()또는 bar위의 예에서 정의 된) P확장 에서 요구 사항의 구현을 정의 할 수 있고, 요구 사항은 구체적인 유형에 정의됩니다. P여전히 P자체 호출 할 수 없습니다 .
이 때문에 Swift는 프로토콜을 자체 요구 사항에 맞는 유형으로 사용하지 못하도록합니다. 프로토콜에 정적 요구 사항이 있으면 소지하지 않기 때문입니다.
인스턴스 프로토콜 요구 사항은 프로토콜에 맞는 실제 인스턴스에서 호출 해야하는 문제가 없는 요구 사항을 구현해야합니다. 따라서 유형이 지정된 인스턴스에서 요구 사항을 호출하면 해당 요구 사항 P의 기본 구조 유형 구현으로 해당 호출을 사용할 수 있습니다.
그러나이 경우 규칙에 특별한 예외를두면 프로토콜이 일반 코드로 처리되는 방식에 놀라운 방식으로 저장됩니다. 그러나 현재 상황은 associatedtype요구 사항과 크게 다르지 언어입니다. 이는 현재 프로토콜을 유형으로 사용하지 않습니다. 정적 요구 사항이있을 때 프로토콜 자체를 따르는 유형으로 프로토콜을 사용하지 않는 제한이 있고 언어에 대한 옵션이 있습니다.
편집 : 그리고 아래에서 본 것처럼 보이는 스위프트 팀이 목표로하는 것입니다.
@objc 프로토콜
사실 실제로 언어가 프로토콜을 처리하는 방식과 정확히 일치합니다.@objc. 정적 요구 사항이 모두 준수합니다.
다음은 잘 될 것입니다.
import Foundation
@objc protocol P {
func foo()
}
class C : P {
func foo() {
print("C's foo called!")
}
}
func baz<T : P>(_ t: T) {
t.foo()
}
let c: P = C()
baz(c)
baz그 요구 T에 규격을 P; 우리는에서 그러나 대체 할 수 있습니다 P에, 대한 T때문에 P정적 요구 사항이 없습니다. 에 정적 요구 사항을 추가 P하면 더 이상 준비되지 않습니다.
import Foundation
@objc protocol P {
static func bar()
func foo()
}
class C : P {
static func bar() {
print("C's bar called")
}
func foo() {
print("C's foo called!")
}
}
func baz<T : P>(_ t: T) {
t.foo()
}
let c: P = C()
baz(c) // error: Cannot invoke 'baz' with an argument list of type '(P)'
따라서이 문제에 대한 한 가지 해결 방법은 프로토콜을 만드는 것입니다 @objc. 물론, Obj-C 실행을 요구할 수 없다는 것이 해결되지 않습니다. Linux와 같은 Apple 이외의 플랫폼에서는 실행 가능하지 않습니다.
그러나이 제한이 언어가 프로토콜에 대해 '정적 요구 사항이없는 프로토콜 자체'를 이미 구현하는 이유 중 하나라고 생각 @objc합니다. 컴파일러에 의해 일반적인 코드는 크게 단순화 될 수 있습니다.
왜? 때문에 @objc프로토콜 형식의 값은 그 요구 사항을 사용하여 파견 서열 단지 클래스 참조입니다 objc_msgSend. 반대로 @objc프로토콜이 아닌 유형의 값은 (잠재적으로 저장된) 랩핑 된 값의 메모리를 관리하고 다른 구현을 호출 할 구현을 결정하기 위해 값과 감시 테이블을 모두 가지고 있기 때문에 더 복잡합니다 . 요구 사항.
이 단순화 표현의 @objc프로토콜 같은 프로토콜 유형의 값은 P유형의 '일반적인 값'몇 가지 일반적인 자리와 같은 메모리 표현을 공유 할 수 있습니다. T : P, 아마도 자기 계층을 허용하도록 스위프트 팀이 쉽게 그것을 만드는 것입니다. 비- @objc프로토콜의 경우에도 마찬가지입니다. 그러나 현재 일반 값에는 현재 값 또는 프로토콜 감시 테이블이 없습니다.
그러나이 기능 은 의도적이며 @objcSwift 팀 구성원 Slava Pestov 가 SR-55 에 대한 귀하의 질문에 대한 답변 ( 이 질문에 의해 표시됨) 에 의해 확인 된 것처럼 비 프로토콜 로 롤아웃되기 바랍니다 .
Matt Neuburg는 게임을 추가했습니다 -2017 년 9 월 7 일 오후 1시 33 분
이것은 당신입니다 :
@objc protocol P {} class C: P {} func process<T: P>(item: T) -> T { return item } func f(image: P) { let processed: P = process(item:image) }추가 하면 추가
@objc됩니다. 제거하면 다시 준비되지 않습니다. 스택 오버플로에 대해 우리 중 일부는이 놀라운 것을 발견하고 의도적인지 또는 버그가있는 경우 알고 싶습니다.Slava Pestov는 게임을 추가했습니다 -2017 년 9 월 7 일 오후 1시 53 분
고의적 으로이 제한을 해제하는 것이 버그에 관한 것입니다. 내가 말했듯이 까다 롭고 아직 구체적인 계획이 없습니다.
언젠가 언젠가는 언어가 비 @objc프로토콜에 지원할 수 있기 때문에 바랍니다 .
그러나 비 @objc프로토콜에 대한 현재 솔루션은 무엇입니까?
프로토콜 제약 조건으로 확장 구현
Swift 3.1에서, 주어진 제네릭 플레이스 홀더 또는 관련 유형이 주어진 프로토콜 유형 (해당 프로토콜을 따르는 구체적인 유형이 아님)이어야한다는 제약 조건이있는 확장을 원한다면 제약 조건으로 간단히 정의 할 수 있습니다 ==.
예를 들어, 배열 확장을 다음과 같이 작성할 수 있습니다.
extension Array where Element == P {
func test<T>() -> [T] {
return []
}
}
let arr: [P] = [S()]
let result: [S] = arr.test()
물론 이것은 이제 우리가 P. when에 대한 추가 확장을 정의하고 확장으로 Element : P전달 하여이 문제를 해결할 수 == P있습니다.
extension Array where Element : P {
func test<T>() -> [T] {
return (self as [P]).test()
}
}
let arr = [S()]
let result: [S] = arr.test()
그러나 [P]각 요소가 실존 컨테이너에 박스 화되어야 하므로 배열의 O (n) 변환을으로 수행한다는 점에 주목할 가치가 있습니다. 성능이 문제인 경우 확장 방법을 다시 구현하여 간단히 해결할 수 있습니다. 이것은 완전히 만족스러운 해결책 은 아닙니다 . 언어의 향후 버전에는 '프로토콜 유형 또는 프로토콜 유형 준수'제약 조건 을 표현하는 방법이 포함될 것 입니다.
Swift 3.1 이전에는 Rob 이 그의 대답에서 보여 주듯이 이를 달성하는 가장 일반적인 방법은 단순히에 대한 래퍼 유형을 빌드 한 [P]다음 확장 메서드를 정의 할 수있는 것입니다.
제한된 일반 자리 표시 자에 프로토콜 유형 인스턴스 전달
다음과 같은 상황을 고려하십시오.
protocol P {
var bar: Int { get set }
func foo(str: String)
}
struct S : P {
var bar: Int
func foo(str: String) {/* ... */}
}
func takesConcreteP<T : P>(_ t: T) {/* ... */}
let p: P = S(bar: 5)
// error: Cannot invoke 'takesConcreteP' with an argument list of type '(P)'
takesConcreteP(p)
우리는 전달할 수 없습니다 p에 takesConcreteP(_:)우리가 현재 대체 할 수 없기 때문에, P일반적인 자리를 위해 T : P. 이 문제를 해결할 수있는 몇 가지 방법을 살펴 보겠습니다.
1. 실존의 열기
오히려 대체하지는 않고 P위한 T : P1, 우리는 기초 콘크리트 타입으로 무엇을 팔 수 있다면 것을 P입력 된 값 대신에 포장 및 대체했다? 안타깝게도 현재 사용자가 직접 사용할 수없는 실존 정보 열기 라는 언어 기능이 필요 합니다.
그러나 Swift 는 멤버에 접근 할 때 묵시적으로 존재 (프로토콜 유형 값)를 엽니 다 (즉, 런타임 유형을 파헤쳐 일반 자리 표시 자의 형태로 접근 가능하게 만듭니다). 프로토콜 확장에서이 사실을 이용할 수 있습니다 P.
extension P {
func callTakesConcreteP/*<Self : P>*/(/*self: Self*/) {
takesConcreteP(self)
}
}
Self암시 적 self매개 변수 를 입력하는 데 사용되는 확장 메서드가 취하는 암시 적 일반 자리 표시 자에 유의하십시오. 이것은 모든 프로토콜 확장 멤버와 함께이면에서 발생합니다. 프로토콜 유형 값에 대해 이러한 메소드를 호출 할 때 PSwift는 기본 구체적인 유형을 파 내고이를 사용하여 Self일반 자리 표시자를 충족합니다 . 우리가 호출 할 수있는 이유입니다 takesConcreteP(_:)함께 self우리가 만족하고 - T와 Self.
이것은 이제 우리가 말할 수 있음을 의미합니다.
p.callTakesConcreteP()
그리고 기본 구체 유형 (이 경우 ) 에 의해 충족되는 takesConcreteP(_:)일반 자리 표시 자로 호출됩니다 . 이것은 "자신을 따르는 프로토콜"이 아닙니다. 우리가 구체적인 유형을 대체하는 것이기 때문입니다.-프로토콜에 정적 요구 사항을 추가하고에서 호출 할 때 어떤 일이 발생하는지 확인하십시오 .TSPtakesConcreteP(_:)
Swift가 계속해서 프로토콜이 자신을 준수하는 것을 허용하지 않는다면, 차선책은 일반 유형의 매개 변수에 대한 인수로 전달하려고 할 때 암시 적으로 실존성을 여는 것입니다. 상용구없이 프로토콜 확장 트램폴린이 수행 한 작업을 효과적으로 수행합니다.
그러나 실존 정보를 여는 것은 프로토콜이 자신을 따르지 않는 문제에 대한 일반적인 해결책이 아닙니다. 프로토콜 유형 값의 이기종 컬렉션을 처리하지 않으며, 모두 서로 다른 기본 구체적인 유형을 가질 수 있습니다. 예를 들어 다음을 고려하십시오.
struct Q : P {
var bar: Int
func foo(str: String) {}
}
// The placeholder `T` must be satisfied by a single type
func takesConcreteArrayOfP<T : P>(_ t: [T]) {}
// ...but an array of `P` could have elements of different underlying concrete types.
let array: [P] = [S(bar: 1), Q(bar: 2)]
// So there's no sensible concrete type we can substitute for `T`.
takesConcreteArrayOfP(array)
같은 이유로 T매개 변수가 동일한 유형의 인수를 가져야하기 때문에 매개 변수가 여러 개인 함수 도 문제가 될 수 있습니다. 그러나 두 개의 P값 이있는 경우 컴파일 타임에 둘 다 동일한 기본 콘크리트를 갖는다 고 보장 할 수있는 방법은 없습니다. 유형.
이 문제를 해결하기 위해 우리는 유형 지우개를 사용할 수 있습니다.
2. 유형 지우개 만들기
마찬가지로 롭 말한다 하는 타입 지우개 , 프로토콜 자체에 부합하지 않는 문제에 대한 가장 일반적인 솔루션입니다. 이를 통해 인스턴스 요구 사항을 기본 인스턴스로 전달하여 프로토콜 유형 인스턴스를 해당 프로토콜을 준수하는 구체적인 유형으로 래핑 할 수 있습니다.
따라서 P의 인스턴스 요구 사항을 P다음 을 준수하는 기본 임의 인스턴스로 전달하는 유형 지우기 상자를 빌드 해 보겠습니다 .
struct AnyP : P {
private var base: P
init(_ base: P) {
self.base = base
}
var bar: Int {
get { return base.bar }
set { base.bar = newValue }
}
func foo(str: String) { base.foo(str: str) }
}
이제 우리는 AnyP대신에 P다음 과 같이 말할 수 있습니다 .
let p = AnyP(S(bar: 5))
takesConcreteP(p)
// example from #1...
let array = [AnyP(S(bar: 1)), AnyP(Q(bar: 2))]
takesConcreteArrayOfP(array)
이제 그 상자를 만들어야하는 이유를 잠시 생각해보십시오. 앞서 논의했듯이 Swift는 프로토콜에 정적 요구 사항이있는 경우 구체적인 유형이 필요합니다. P정적 요구 사항이 있는지 고려하십시오 AnyP.. 그러나 그것은 무엇으로 구현되어야합니까? 우리는 P여기서 준수하는 임의의 인스턴스를 다루고 있습니다. 기본 구체적인 유형이 정적 요구 사항을 구현하는 방법을 알지 못하므로에서 의미있게 표현할 수 없습니다 AnyP.
따라서이 경우 솔루션은 인스턴스 프로토콜 요구 사항 의 경우에만 실제로 유용 합니다. 일반적인 경우에, 우리는 여전히 치료를 할 수없는 P요건에 적합 그 구체적인 유형으로 P.
CollectionType대신 프로토콜 을 확장하고 프로토콜 별 Array제약을 구체적인 유형으로 확장하면 다음과 같이 이전 코드를 다시 작성할 수 있습니다.
protocol P { }
struct S: P { }
let arr:[P] = [ S() ]
extension CollectionType where Generator.Element == P {
func test<T>() -> [T] {
return []
}
}
let result : [S] = arr.test()
참고 URL : https://stackoverflow.com/questions/33112559/protocol-doesnt-conform-to-itself
'IT' 카테고리의 다른 글
| 정적 인덱서? (0) | 2020.07.27 |
|---|---|
| Linux에서 여러 가지로 신호 처리 (0) | 2020.07.27 |
| C ++, 프리 스토어 및 힙 (0) | 2020.07.27 |
| cmd.exe에서 stderr을 null로 리디렉션하는 방법 (0) | 2020.07.27 |
| HTML5shiv vs Dean Edwards IE7-js vs Modernizr- 어떤 것을 선택해야합니까? (0) | 2020.07.26 |