Self를 반환하는 프로토콜 func


82

개체의 복사본을 반환하는 프로토콜 P가 있습니다.

protocol P {
    func copy() -> Self
}

P를 구현하는 클래스 C :

class C : P {
    func copy() -> Self {
        return C()
    }
}

그러나 Self다음 오류가 발생 하면 반환 값을 입력했는지 여부 :

'C'유형의 반환 식을 'Self'반환 유형으로 변환 할 수 없습니다.

나는 또한 반환을 시도했다 C.

class C : P {
    func copy() -> C  {
        return C()
    }
}

그 결과 다음과 같은 오류가 발생했습니다.

최종 클래스가 아닌 클래스 'C'의 'copy ()'메서드 Self는 프로토콜 'P'를 준수하도록 반환되어야합니다.

내가 접두사 class Cfinal붙인 경우를 제외하고는 아무것도 작동 하지 않습니다.

final class C : P {
    func copy() -> C  {
        return C()
    }
}

그러나 C를 하위 클래스로 만들고 싶다면 아무것도 작동하지 않습니다. 이 문제를 해결할 방법이 있습니까?


1
"아무것도 작동하지 않음"이란 무엇을 의미합니까?
Rob Napier

반환 값으로 C 또는 자기 중 하나를 넣어 때하지 않는 한 컴파일러는 불평 classA는final class
aeubanks

6
좋습니다. 오류를 재현했지만 질문 할 때 반환 된 실제 오류를 포함해야합니다. 단순히 "오류가 발생한다"또는 "작동하지 않는다"가 아닙니다.
Rob Napier

컴파일러는 여기 BTW의 오류가 완전히 정확합니다. 나는 당신이하려는 일을 전혀 얻을 수 있을지 생각하고 있습니다.
Rob Napier

1
하지만 [[[self class] alloc] init]. 그래서 질문은 현재 클래스를 호출하고 init 메서드를 호출하는 형식 안전 방법이 있다는 것입니다.
aeubanks 2014 년

답변:


144

문제는 컴파일러가 당신이 지킬 것이라는 것을 증명할 수 없다는 약속을하고 있다는 것입니다.

그래서이 약속을 만들었습니다 : 호출 copy()은 완전히 초기화 된 자체 유형을 반환합니다.

그러나 다음과 같이 구현 copy()했습니다.

func copy() -> Self {
    return C()
}

이제 나는 재정의하지 않는 하위 클래스입니다 copy(). 그리고 나는 C완전히 초기화되지 않은 Self(내가 약속 한)를 반환합니다 . 그래서 그것은 좋지 않습니다. 어때 :

func copy() -> Self {
    return Self()
}

음, 그것은 컴파일되지 않을 것입니다.하지만 그렇게되었다고해도 좋지 않을 것입니다. 하위 클래스에는 사소한 생성자 D()가 없을 수 있으므로 합법적이지 않을 수도 있습니다. (아래를 참조하십시오.)

좋습니다. 다음은 어떻습니까?

func copy() -> C {
    return C()
}

예,하지만 반환되지 않습니다 Self. 반환합니다 C. 당신은 여전히 ​​당신의 약속을 지키지 않고 있습니다.

"하지만 ObjC는 할 수 있습니다!" 글쎄요. 대부분은 Swift가하는 것처럼 약속을 지키더라도 상관하지 않기 때문입니다. copyWithZone:하위 클래스에서 구현 하지 못하면 객체를 완전히 초기화하지 못할 수 있습니다. 컴파일러는 당신이 그렇게했다는 경고조차하지 않을 것입니다.

"그러나 ObjC의 대부분은 Swift로 번역 될 수 있으며 ObjC는 NSCopying." 예, 그렇습니다. 정의 된 방법은 다음과 같습니다.

func copy() -> AnyObject!

따라서 동일한 작업을 수행 할 수 있습니다 (여기에는!에 대한 이유가 없습니다).

protocol Copyable {
  func copy() -> AnyObject
}

그것은 "나는 당신이 돌려받는 것에 대해 아무것도 약속하지 않습니다."라고 말합니다. 다음과 같이 말할 수도 있습니다.

protocol Copyable {
  func copy() -> Copyable
}

그것은 당신이 할 수있는 약속입니다.

하지만 잠시 동안 C ++에 대해 생각하고 수 있습니다 . 우리와 우리의 모든 서브 클래스가 특정 종류의 이니셜 라이저를 구현할 것이라고 약속 할 수 있으며, Swift는이를 강제 할 것입니다 (따라서 우리가 진실을 말하고 있음을 증명할 수 있습니다).

protocol Copyable {
  init(copy: Self)
}

class C : Copyable {
  required init(copy: C) {
    // Perform your copying here.
  }
}

이것이 복사를 수행하는 방법입니다.

한 단계 더 dynamicType나아갈 수는 있지만를 사용하며 항상 우리가 원하는 것인지 확인하기 위해 광범위하게 테스트하지는 않았지만 정확해야합니다.

protocol Copyable {
  func copy() -> Self
  init(copy: Self)
}

class C : Copyable {
  func copy() -> Self {
    return self.dynamicType(copy: self)
  }

  required init(copy: C) {
    // Perform your copying here.
  }
}

여기서 우리는 우리를 위해 복사를 수행하는 이니셜 라이저가 있다는 것을 약속하고, 런타임에 호출 할 것을 결정할 수 있으며, 찾고 있던 메소드 구문을 제공합니다.


흠, 그들은 이것을 바꿨을 것입니다. func copy() -> C이전 베타에서 작동하는 것을 맹세 할 수 있었고 프로토콜 적합성이 상속되지 않았기 때문에 일관성이있었습니다. (이제 프로토콜 준수가 상속 된 것으로 보이며 func copy() -> C작동하지 않습니다.)
newacct

2
마지막 pure-Swift 솔루션은 init(copy: C)대신 구현해야하기 때문에 하위 클래스에서 작동하지 않습니다. init(copy: Self)(
fluidsonic

마지막 해결책으로 반환 값을 보장 Self하지만, 초기화는 모든 변수가 정적으로 입력 받아 들여야 C그냥 반환에 많은 개선이 아니라, 어떤 말을하는 것입니다 AnyObject처음에.
chakrit

1
swift 2.0에서는 명시 적으로 init를 호출해야합니다.self.dynamicType.init( ... )
pronebird

1
@Dschee 내부 C, Self는 C 또는 C의 하위 클래스 일 수 있습니다. 이들은 다른 유형입니다.
Rob Napier

25

Swift 2에서는이를 위해 프로토콜 확장을 사용할 수 있습니다.

protocol Copyable {
    init(copy:Self)
}

extension Copyable {
    func copy() -> Self {
        return Self.init(copy: self)
    }
}

이것은 좋은 대답과 접근 방식의 유형이 WWDC 2015에 광범위하게 논의되었다
gkaimakas

2
이것은 받아 들여진 대답이어야합니다. return Self(copy: self)(적어도 Swift 2.2에서는) 로 단순화 할 수 있습니다 .
jhrmnn 2011 년

16

Swift의 관련 유형을 활용하는 것과 관련하여 원하는 작업을 수행하는 또 다른 방법이 있습니다. 다음은 간단한 예입니다.

public protocol Creatable {

    associatedtype ObjectType = Self

    static func create() -> ObjectType
}

class MyClass {

    // Your class stuff here
}

extension MyClass: Creatable {

    // Define the protocol function to return class type
    static func create() -> MyClass {

         // Create an instance of your class however you want
        return MyClass()
    }
}

let obj = MyClass.create()


이것은 내가 관심있는 일을합니다. 감사합니다!
Josh at The Nerdery

10

실제로 프로토콜 ( gist )에서 필요할 때 쉽게 반환 Self할 수있는 트릭이 있습니다 .

/// Cast the argument to the infered function return type.
func autocast<T>(some: Any) -> T? {
    return some as? T
}

protocol Foo {
    static func foo() -> Self
}

class Vehicle: Foo {
    class func foo() -> Self {
        return autocast(Vehicle())!
    }
}

class Tractor: Vehicle {
    override class func foo() -> Self {
        return autocast(Tractor())!
    }
}

func typeName(some: Any) -> String {
    return (some is Any.Type) ? "\(some)" : "\(some.dynamicType)"
}

let vehicle = Vehicle.foo()
let tractor = Tractor.foo()

print(typeName(vehicle)) // Vehicle
print(typeName(tractor)) // Tractor

1
와. 컴파일합니다. 컴파일러는 당신을 바로 할 수 없기 때문에 즉, 위험한입니다return Vehicle() as! Self
SimplGy

그것은 놀랍습니다. 와. 내가 여기서 묻는 것은 실제로 이것에 대한 변형입니까 ?? stackoverflow.com/q/42041150/294884
Fattie 2017

@JoeBlow 나는 그렇지 않다는 것을 두려워합니다. 마음을 안전하게 유지하려면 반환 유형을 정확히 알아야합니다 (예 : "A 또는 B"가 아니라 "A"). 그렇지 않으면 다형성 + 상속 + 함수 오버로딩 (적어도)에 대해 생각해야합니다.
wasdiver

그것은 컴파일러 속임수입니다. 의 재정의 foo()가 적용되지 않기 때문에 사용자 지정 구현이 Vehicle없는 모든 하위 항목 foo()autocast(). 예 : class SuperCar: Vehicle { } let superCar = SuperCar.foo() . 의 인스턴스를 Vehicle다운 캐스팅 할 수 없습니다. SuperCar따라서 'autocast ()'에서 nil을 강제로 언 래핑하면 충돌이 발생합니다.
freennnn

1
@freennnn 코드를 다음과 같이 변경해도 하위 클래스가 재정의되지 않을 때 충돌이 발생하지 않습니다 foo(). 유일한 요구 사항은 Foo아래와 같이 작동하려면 클래스 에 필수 이니셜 라이저가 있어야한다는 것입니다. class Vehicle: Foo { public required init() { // Some init code here } class func foo() -> Self { return autocast(self.init())! // return autocast(Vehicle())! } } class Tractor: Vehicle { //Override is not necessary /*override class func foo() -> Self { return autocast(Tractor())! }*/ }
shawnynicole

2

Rob의 제안에 따라 관련 유형 으로 더 일반적으로 만들 수 있습니다 . 접근 방식의 이점을 보여주기 위해 예제를 약간 변경했습니다.

protocol Copyable: NSCopying {
    associatedtype Prototype
    init(copy: Prototype)
    init(deepCopy: Prototype)
}
class C : Copyable {
    typealias Prototype = C // <-- requires adding this line to classes
    required init(copy: Prototype) {
        // Perform your copying here.
    }
    required init(deepCopy: Prototype) {
        // Perform your deep copying here.
    }
    @objc func copyWithZone(zone: NSZone) -> AnyObject {
        return Prototype(copy: self)
    }
}

1

나는 비슷한 문제가 있었고 유용한 것을 생각해 냈기 때문에 이것이 해결책을 찾을 때 찾은 첫 번째 장소 중 하나이기 때문에 나중에 참조하기 위해 공유 할 것입니다.

위에서 언급했듯이 문제는 copy () 함수에 대한 반환 유형의 모호성입니다. 이것은 copy ()-> C 및 copy ()-> P 함수를 분리하여 매우 명확하게 설명 할 수 있습니다.

따라서 프로토콜과 클래스를 다음과 같이 정의한다고 가정합니다.

protocol P
{
   func copy() -> P
}

class C:P  
{        
   func doCopy() -> C { return C() }       
   func copy() -> C   { return doCopy() }
   func copy() -> P   { return doCopy() }       
}

이는 반환 값의 유형이 명시적일 때 예상되는 결과를 컴파일하고 생성합니다. 컴파일러가 (자체적으로) 반환 유형을 결정해야 할 때마다 상황이 모호하고 P 프로토콜을 구현하는 모든 구체적인 클래스에 대해 실패합니다.

예를 들면 :

var aC:C = C()   // aC is of type C
var aP:P = aC    // aP is of type P (contains an instance of C)

var bC:C         // this to test assignment to a C type variable
var bP:P         //     "       "         "      P     "    "

bC = aC.copy()         // OK copy()->C is used

bP = aC.copy()         // Ambiguous. 
                       // compiler could use either functions
bP = (aC as P).copy()  // but this resolves the ambiguity.

bC = aP.copy()         // Fails, obvious type incompatibility
bP = aP.copy()         // OK copy()->P is used

결론적으로 이것은 기본 클래스의 copy () 함수를 사용하지 않거나 항상 명시적인 유형 컨텍스트가있는 상황에서 작동합니다.

모든 곳에서 다루기 힘든 코드를 위해 만들어진 구체적인 클래스와 동일한 함수 이름을 사용하는 것을 발견했기 때문에 프로토콜의 copy () 함수에 다른 이름을 사용하게되었습니다.

최종 결과는 다음과 같습니다.

protocol P
{
   func copyAsP() -> P
}

class C:P  
{
   func copy() -> C 
   { 
      // there usually is a lot more code around here... 
      return C() 
   }
   func copyAsP() -> P { return copy() }       
}

물론 내 상황과 기능은 완전히 다르지만 질문의 정신으로 가능한 한 주어진 예에 가깝게 유지하려고 노력했습니다.


1

Swift 5.1은 이제 Self에 강제 캐스트를 허용합니다. as! Self

  1> protocol P { 
  2.     func id() -> Self 
  3. } 
  9> class D : P { 
 10.     func id() -> Self { 
 11.         return D()
 12.     } 
 13. } 
error: repl.swift:11:16: error: cannot convert return expression of type 'D' to return type 'Self'
        return D()
               ^~~
                   as! Self


  9> class D : P { 
 10.     func id() -> Self { 
 11.         return D() as! Self
 12.     } 
 13. } //works

0

여기 반지에 내 모자를 던 졌어요. 프로토콜이 적용된 유형의 옵션을 반환하는 프로토콜이 필요했습니다. 또한 오버라이드가 Self뿐만 아니라 명시 적으로 유형을 반환하기를 원했습니다.

트릭은 반환 유형으로 'Self'를 사용하는 것이 아니라 대신 Self와 동일하게 설정 한 관련 유형을 정의한 다음 해당 관련 유형을 사용하는 것입니다.

Self를 사용하는 오래된 방법이 있습니다.

protocol Mappable{
    static func map() -> Self?
}

// Generated from Fix-it
extension SomeSpecificClass : Mappable{
    static func map() -> Self? {
        ...
    }
}

관련 유형을 사용하는 새로운 방법은 다음과 같습니다. 반환 유형은 이제 'Self'가 아니라 명시 적입니다.

protocol Mappable{
    associatedtype ExplicitSelf = Self
    static func map() -> ExplicitSelf?
}

// Generated from Fix-it
extension SomeSpecificClass : Mappable{
    static func map() -> SomeSpecificClass? {
        ...
    }
}

0

associatedtype방법으로 답변을 추가하려면 인스턴스 생성을 프로토콜 확장의 기본 구현으로 이동하는 것이 좋습니다. 그런 식으로 준수하는 클래스는이를 구현할 필요가 없으므로 코드 중복을 방지 할 수 있습니다.

protocol Initializable {
    init()
}

protocol Creatable: Initializable {
    associatedtype Object: Initializable = Self
    static func newInstance() -> Object
}

extension Creatable {
    static func newInstance() -> Object {
        return Object()
    }
}

class MyClass: Creatable {
    required init() {}
}

class MyOtherClass: Creatable {
    required init() {}
}

// Any class (struct, etc.) conforming to Creatable
// can create new instances without having to implement newInstance() 
let instance1 = MyClass.newInstance()
let instance2 = MyOtherClass.newInstance()
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.