[swift] Swift 확장에서 메소드 재정의

필자는 필수 (저장된 속성, 이니셜 라이저) 만 클래스 정의에 넣고 다른 모든 것을 그룹으로 묶을 논리적 블록 extension과 같은 자체로 옮기는 경향이 있습니다.extension// MARK:

예를 들어 UIView 하위 클래스의 경우 레이아웃 관련 항목, 이벤트 구독 및 처리 등을위한 확장으로 끝납니다. 이 확장에서는 필연적으로 일부 UIKit 메서드를 재정의해야합니다 (예 🙂 layoutSubviews. 나는 오늘까지이 접근법에 어떤 문제도 발견하지 못했다

이 클래스 계층 구조를 예로 들어 보겠습니다.

public class C: NSObject {
    public func method() { print("C") }
}

public class B: C {
}
extension B {
    override public func method() { print("B") }
}

public class A: B {
}
extension A {
    override public func method() { print("A") }
}

(A() as A).method()
(A() as B).method()
(A() as C).method()

출력은 A B C입니다. 그것은 나에게 의미가 없습니다. 프로토콜 확장이 정적으로 전달되는 것에 대해 읽었지만 이것은 프로토콜이 아닙니다. 이것은 일반 클래스이며 런타임에 메소드 호출이 동적으로 전달 될 것으로 예상됩니다. 분명히 호출은 C동적으로 전달되고 생성되어야 C합니까?

상속을 제거하고 루트 클래스를 NSObject만들면 C컴파일러 declarations in extensions cannot override yet는에 대해 이미 불평 합니다. 그러나 NSObject루트 클래스로서의 일이 어떻게 변화합니까?

자신의 클래스 선언에 모두 재정의를 이동하면 생산 A A A이 예상대로 만 이동하는 B‘생산에요 A B B단지 이동 A의가 생산’ C B C조차 하나가 정적에 입력 :, 그 중 마지막은 나에게 전혀 의미가 없습니다 A생산 A-output 더 이상!

dynamic정의 또는 재정의에 키워드를 추가해도 ‘클래스 계층 구조의 해당 시점부터 아래쪽으로’원하는 동작을 제공하는 것 같습니다 …

예제를 조금 덜 구성한 것으로 변경해 보자. 실제로이 질문을 게시하게했다 :

public class B: UIView {
}
extension B {
    override public func layoutSubviews() { print("B") }
}

public class A: B {
}
extension A {
    override public func layoutSubviews() { print("A") }
}


(A() as A).layoutSubviews()
(A() as B).layoutSubviews()
(A() as UIView).layoutSubviews()

우리는 지금 얻는다 A B A. 여기서는 UIView의 layoutSubviews를 동적으로 만들 수 없습니다.

두 재정의를 클래스 선언으로 옮기면 A A A다시 우리를 얻습니다 .A 또는 B 만 여전히 우리를 얻습니다 A B A. dynamic다시 내 문제를 해결합니다.

이론적으로는 내가 한 dynamic모든 일에 추가 할 수 override는 있지만 여기서 다른 일을하고있는 것처럼 느낍니다.

extension내가하는 것처럼 그룹화 코드에 s 를 사용하는 것이 실제로 잘못 되었습니까?



답변

확장은 재정의 할 수 없습니다.

Apple의 Swift Guide에 설명 된대로 확장의 기능 (예 : 속성 또는 메서드)을 재정의 할 수 없습니다.

확장은 유형에 새로운 기능을 추가 할 수 있지만 기존 기능을 무시할 수는 없습니다.

스위프트 개발자 안내서

컴파일러는 Objective-C와의 호환성을 위해 확장에서 재정의를 허용합니다. 그러나 실제로 언어 지침을 위반하고 있습니다.

? 그것은 아이작 아시모프의 ” 로봇 공학의 세 가지 법칙 ?

확장 ( syntactic sugar )은 자체 인수를받는 독립적 인 방법을 정의합니다. 즉, 호출되는 함수 layoutSubviews는 코드가 컴파일 될 때 컴파일러가 알고있는 컨텍스트에 따라 다릅니다. UIView는 NSObject에서 상속하는 UIResponder에서 상속 하므로 확장의 재정의는 허용되지만 허용되지 않아야합니다 .

따라서 그룹화에는 아무런 문제가 없지만 확장명이 아닌 클래스에서 재정의해야합니다.

지시어 노트

메소드가 Objective-C와 호환되는 경우 override수퍼 클래스 메소드, 즉 load() initialize()서브 클래스 확장 에서만 가능합니다.

따라서 우리는 왜를 사용하여 컴파일 할 수 있는지 볼 수 있습니다 layoutSubviews.

Swift 전용 런타임을 허용하는 순수한 Swift 전용 프레임 워크를 사용하는 경우를 제외하고 모든 Swift 앱은 Objective-C 런타임 내에서 실행됩니다.

Objective-C 런타임은 일반적으로 두 가지 주요 메소드를 호출 load()하고 initialize()앱 프로세스에서 클래스를 초기화 할 때 자동 으로 호출 합니다.

에 대하여 dynamic 수식

로부터 애플 개발자 도서관 (archive.org)

당신은 사용할 수 있습니다 dynamic수정자를 Objective-C 런타임을 통해 멤버에 대한 액세스를 동적으로 디스패치하도록 요구할 .

Objective-C 런타임에서 Swift API를 가져 오는 경우 특성, 메소드, 첨자 또는 이니셜 라이저에 대한 동적 디스패치가 보장되지 않습니다. Swift 컴파일러는 Objective-C 런타임을 우회하여 코드 성능을 최적화하기 위해 멤버 액세스를 가상으로 만들거나 인라인으로 만들 수 있습니다.?

따라서 Objective-C로 표시되고 해당 멤버에 대한 액세스는 항상 Objective-C 런타임을 사용하므로 ->에 dynamic적용될 수 있습니다 .layoutSubviewsUIView Class

그래서 컴파일러에서 overrideand 를 사용할 수 있습니다 dynamic.


답변

Swift의 목표 중 하나는 정적 디스패치 또는 동적 디스패치 감소입니다. 그러나 Obj-C는 매우 역동적 인 언어입니다. 당신이보고있는 상황은 두 언어와 서로 협력하는 방식 사이의 연결에서 비롯됩니다. 실제로 컴파일해서는 안됩니다.

확장에 대한 주요 요점 중 하나는 확장 / 교체가 아니라 확장을위한 것입니다. 이름과 문서에서 이것이 의도임을 분명히 알 수 있습니다. 실제로 코드에서 Obj-C에 대한 링크를 제거 NSObject하면 (수퍼 클래스로 제거 ) 컴파일되지 않습니다.

따라서 컴파일러는 정적으로 디스패치 할 수있는 것과 동적으로 디스패치해야 할 것을 결정하려고 시도하고 있으며 코드의 Obj-C 링크로 인해 차이가 발생합니다. 이유dynamic‘작동’ 는 Obj-C가 모든 것을 강제로 연결하기 때문에 항상 동적이기 때문입니다.

따라서 그룹화에 확장을 사용하는 것은 잘못된 것이 아닙니다. 그러나 확장에서 재정의하는 것은 잘못입니다. 재정의는 기본 클래스 자체에 있어야하며 확장 점을 불러야합니다.


답변

서브 클래스에서 재정의 할 수있는 기능을 유지하면서 클래스 서명과 구현 (확장자)을 명확하게 분리 할 수있는 방법이 있습니다. 비결은 함수 대신 변수를 사용하는 것입니다

별도의 신속한 소스 파일에 각 서브 클래스를 정의해야하는 경우 해당 구현을 확장으로 깔끔하게 구성한 상태에서 재정의에 대해 계산 된 변수를 사용할 수 있습니다. 이를 통해 Swift의 “규칙”을 우회하고 클래스의 API / 서명을 깔끔하게 정리할 수 있습니다.

// ---------- BaseClass.swift -------------

public class BaseClass
{
    public var method1:(Int) -> String { return doMethod1 }

    public init() {}
}

// the extension could also be in a separate file
extension BaseClass
{
    private func doMethod1(param:Int) -> String { return "BaseClass \(param)" }
}

// ---------- ClassA.swift ----------

public class A:BaseClass
{
   override public var method1:(Int) -> String { return doMethod1 }
}

// this extension can be in a separate file but not in the same
// file as the BaseClass extension that defines its doMethod1 implementation
extension A
{
   private func doMethod1(param:Int) -> String
   {
      return "A \(param) added to \(super.method1(param))"
   }
}

// ---------- ClassB.swift ----------
public class B:A
{
   override public var method1:(Int) -> String { return doMethod1 }
}

extension B
{
   private func doMethod1(param:Int) -> String
   {
      return "B \(param) added to \(super.method1(param))"
   }
}

각 클래스의 확장은 구현에 대해 동일한 메소드 이름을 사용할 수 있습니다 (비공개적이고 별도의 파일에있는 한) 서로에게 보이지 않기 때문입니다.

보시다시피 super.variablename을 사용하여 상속 (변수 이름 사용)이 올바르게 작동합니다.

BaseClass().method1(123)         --> "BaseClass 123"
A().method1(123)                 --> "A 123 added to BaseClass 123"
B().method1(123)                 --> "B 123 added to A 123 added to BaseClass 123"
(B() as A).method1(123)          --> "B 123 added to A 123 added to BaseClass 123"
(B() as BaseClass).method1(123)  --> "B 123 added to A 123 added to BaseClass 123"


답변

이 답변은 OP의 목표를 달성하기위한 것이 아니라 “필수 (저장된 속성, 이니셜 라이저)를 클래스 정의에 넣고 다른 모든 것을 자신의 확장으로 옮기는 경향이 있습니다.” .. “. 저는 주로 C # 프로그래머이며 C #에서는이 목적으로 부분 클래스를 사용할 수 있습니다. 예를 들어 Visual Studio는 부분 클래스를 사용하여 UI 관련 항목을 별도의 소스 파일에 배치하고 기본 소스 파일을 정리하지 않고 그대로 유지합니다.

“빠른 부분 클래스”를 검색하면 Swift 지원자가 확장을 사용할 수 있기 때문에 Swift에 부분 클래스가 필요하지 않다고 말하는 다양한 링크가 있습니다. 흥미롭게도 Google 검색 필드에 “빠른 확장 프로그램”을 입력하면 첫 번째 검색 제안은 “빠른 확장 프로그램 재정의”이며 현재이 스택 오버플로 질문이 첫 번째로 인기가 있습니다. 재정의 기능이 부족한 문제는 Swift 확장과 관련하여 가장 많이 검색되는 주제이며, Swift 확장이 적어도 파생 클래스를 사용하는 경우 부분 클래스를 대체 할 수 없다는 사실을 강조합니다. 프로그램 작성.

어쨌든, 긴 소개를 짧게 줄이기 위해 C # -to-Swift 프로그램에서 생성 한 Swift 클래스의 기본 소스 파일에서 상용구 / 수하물을 옮기려는 상황 에서이 문제가 발생했습니다. 이러한 메서드를 확장으로 옮긴 후 재정의가 허용되지 않는 문제가 발생하면 다음과 같은 간단한 해결 방법을 구현했습니다. 기본 Swift 소스 파일에는 여전히 확장 파일에서 실제 메소드를 호출하는 작은 스텁 메소드가 포함되어 있으며 이러한 확장 메소드에는 대체 문제점을 피하기 위해 고유 한 이름이 지정됩니다.

public protocol PCopierSerializable {

   static func getFieldTable(mCopier : MCopier) -> FieldTable
   static func createObject(initTable : [Int : Any?]) -> Any
   func doSerialization(mCopier : MCopier)
}

.

public class SimpleClass : PCopierSerializable {

   public var aMember : Int32

   public init(
               aMember : Int32
              ) {
      self.aMember = aMember
   }

   public class func getFieldTable(mCopier : MCopier) -> FieldTable {
      return getFieldTable_SimpleClass(mCopier: mCopier)
   }

   public class func createObject(initTable : [Int : Any?]) -> Any {
      return createObject_SimpleClass(initTable: initTable)
   }

   public func doSerialization(mCopier : MCopier) {
      doSerialization_SimpleClass(mCopier: mCopier)
   }
}

.

extension SimpleClass {

   class func getFieldTable_SimpleClass(mCopier : MCopier) -> FieldTable {
      var fieldTable : FieldTable = [ : ]
      fieldTable[376442881] = { () in try mCopier.getInt32A() }  // aMember
      return fieldTable
   }

   class func createObject_SimpleClass(initTable : [Int : Any?]) -> Any {
      return SimpleClass(
                aMember: initTable[376442881] as! Int32
               )
   }

   func doSerialization_SimpleClass(mCopier : MCopier) {
      mCopier.writeBinaryObjectHeader(367620, 1)
      mCopier.serializeProperty(376442881, .eInt32, { () in mCopier.putInt32(aMember) } )
   }
}

.

public class DerivedClass : SimpleClass {

   public var aNewMember : Int32

   public init(
               aNewMember : Int32,
               aMember : Int32
              ) {
      self.aNewMember = aNewMember
      super.init(
                 aMember: aMember
                )
   }

   public class override func getFieldTable(mCopier : MCopier) -> FieldTable {
      return getFieldTable_DerivedClass(mCopier: mCopier)
   }

   public class override func createObject(initTable : [Int : Any?]) -> Any {
      return createObject_DerivedClass(initTable: initTable)
   }

   public override func doSerialization(mCopier : MCopier) {
      doSerialization_DerivedClass(mCopier: mCopier)
   }
}

.

extension DerivedClass {

   class func getFieldTable_DerivedClass(mCopier : MCopier) -> FieldTable {
      var fieldTable : FieldTable = [ : ]
      fieldTable[376443905] = { () in try mCopier.getInt32A() }  // aNewMember
      fieldTable[376442881] = { () in try mCopier.getInt32A() }  // aMember
      return fieldTable
   }

   class func createObject_DerivedClass(initTable : [Int : Any?]) -> Any {
      return DerivedClass(
                aNewMember: initTable[376443905] as! Int32,
                aMember: initTable[376442881] as! Int32
               )
   }

   func doSerialization_DerivedClass(mCopier : MCopier) {
      mCopier.writeBinaryObjectHeader(367621, 2)
      mCopier.serializeProperty(376443905, .eInt32, { () in mCopier.putInt32(aNewMember) } )
      mCopier.serializeProperty(376442881, .eInt32, { () in mCopier.putInt32(aMember) } )
   }
}

소개에서 말했듯이, 이것은 OP의 질문에 실제로 대답하지는 않지만이 간단한 해결 방법은 기본 소스 파일에서 확장 파일로 메소드를 이동하고 no로 실행하려는 다른 사람들에게 도움이되기를 바랍니다. 재정의 문제.


답변

POP (프로토콜 지향 프로그래밍)를 사용하여 확장의 함수를 대체하십시오.

protocol AProtocol {
    func aFunction()
}

extension AProtocol {
    func aFunction() {
        print("empty")
    }
}

class AClass: AProtocol {

}

extension AClass {
    func aFunction() {
        print("not empty")
    }
}

let cls = AClass()
cls.aFunction()


답변