Swift 확장에서 메소드 재정의


133

필자는 필수 (저장된 속성, 이니셜 라이저) 만 클래스 정의에 넣고 다른 모든 것을 그룹으로 묶을 논리적 블록 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 를 사용하는 것이 실제로 잘못 되었습니까?


이러한 방식으로 확장을 사용하는 것이 Swift의 규칙입니다. Apple조차도 표준 라이브러리에서 수행합니다.
Alexander-복원 자 Monica Monica


1
@AMomchilov 당신이 프로토콜에 대해 이야기하는 문서를 연결했습니다.
Christian Schnorr

나는 그것이 '같은 메커니즘이야 용의자 모두를위한 작품
알렉산더 - 분석 재개 모니카

3
하위 클래스 확장에서 재정의 된 메서드에 Swift 디스패치 를 복제하는 것으로 나타납니다 . matt의 대답은 버그라는 것입니다 (그리고 그는 그것을 지원하기 위해 문서를 인용합니다).
jscs

답변:


229

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

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.


6
확장은 클래스에 정의 된 메소드 만 대체 할 수 없습니다. 부모 클래스에 정의 된 메소드를 대체 할 수 있습니다.
RJE

-Swift3-Well, 포함 된 프레임 워크에서 메소드를 재정의 할 수 있기 때문에 이상합니다 (여기서는 재정의와 같은 것을 의미합니다). 그 프레임 워크가 순식간에 신속하게 쓰여지더라도 ... 프레임 워크도 objc에 묶여 있고 이것이 🤔
farzadshbfn

@tymac 나는 그것을 얻지 못한다. 경우] 오브젝티브 C 런타임 이유 오브젝티브 C의 호환성을 위해서 필요 뭔가, 스위프트 컴파일러는 여전히 확장에 재정의 허용? 구문 오류로 Swift 확장에서 재정의를 표시하면 Objective-C 런타임에 어떤 영향을 줄 수 있습니까?
Alexander Vasenin

1
프로젝트에 이미 코드가 포함 된 프레임 워크를 만들려면 모든 것을 서브 클래 싱하고 이름을 바꿔야합니다.
thibaut noah

3
@AuRis 참조가 있습니까?
ricardopereira

18

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

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

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

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


이것은 변수에도 적용됩니까? 재정의하려는 경우 예를 들어, supportedInterfaceOrientationsUINavigationController(서로 다른 방향에서 다른 관점을 보여주는 목적), 사용자 정의 클래스가 아닌 확장을 사용해야합니까? 많은 답변은 재정의를 위해 확장을 사용하는 것이 supportedInterfaceOrientations좋지만 설명을 좋아할 것입니다. 감사!
Crashalot

10

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

별도의 신속한 소스 파일에 각 서브 클래스를 정의해야하는 경우 해당 구현을 확장으로 깔끔하게 구성한 상태에서 재정의에 대해 계산 된 변수를 사용할 수 있습니다. 이를 통해 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"

2
내 클래스에서 System Framework 메서드를 재정의 할 때 자체 메서드에는 효과가 있다고 생각합니다.
Christian Schnorr

이것은 속성 래퍼 조건부 프로토콜 확장을위한 올바른 길을 안내했습니다. 감사!
Chris Prince

1

이 답변은 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로 실행하려는 다른 사람들에게 도움이되기를 바랍니다. 재정의 문제.


1

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

1
이것은 프로그래머가 AProtocol에 의존 할 수 있도록 AClass의 원래 정의를 제어하고 있다고 가정합니다. AClass의 기능을 재정의하려는 상황에서는 일반적으로 그렇지 않습니다 (즉, AClass는 Apple에서 제공하는 표준 라이브러리 클래스 일 수 있음).
Jonathan Leonard

클래스의 원래 정의를 원하지 않거나 수정할 수없는 경우 확장 또는 서브 클래스에 프로토콜을 적용 할 수 있습니다 (일부 경우).
shim
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.