[swift] Self를 반환하는 프로토콜 func

개체의 복사본을 반환하는 프로토콜 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를 하위 클래스로 만들고 싶다면 아무것도 작동하지 않습니다. 이 문제를 해결할 방법이 있습니까?



답변

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

그래서이 약속을 만들었습니다 : 호출 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.
  }
}

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


답변

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

protocol Copyable {
    init(copy:Self)
}

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


답변

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()


답변

실제로 프로토콜 ( 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


답변

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)
    }
}


답변

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

위에서 언급했듯이 문제는 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() }
}

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


답변

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