왜 스위프트에서 편의 키워드가 필요한가요?


132

Swift는 메소드와 이니셜 라이저 오버로드를 지원하므로 여러 개를 init나란히 놓고 편리한 것으로 간주 할 수 있습니다.

class Person {
    var name:String

    init(name: String) {
        self.name = name
    }

    init() {
        self.name = "John"
    }
}

그렇다면 왜 convenience키워드가 존재합니까? 다음이 실질적으로 더 나은 이유는 무엇입니까?

class Person {
    var name:String

    init(name: String) {
        self.name = name
    }

    convenience init() {
        self.init(name: "John")
    }
}

13
설명서 에서이 내용을 읽고 있었고 혼란 스러웠습니다. : /
boidkan

답변:


235

기존 답변은 이야기의 절반 만 알려줍니다 convenience. 이야기의 나머지 절반, 기존 답변 중 어느 것도 다루지 않는 절반은 의견에 Desmond가 게시 한 질문에 대답합니다.

왜 스위프트 convenience가 호출해야하기 때문에 초기화 프로그램 앞에 강제로 배치 해야 self.init합니까?`

나는 약간 그것에 감동 이 대답 I 커버하는, 몇 가지 세부 사항에 스위프트의 초기화 규칙, 그러나 주요 초점은 거기에 있었다 required단어. 그러나 그 대답은 여전히이 질문 과이 대답과 관련된 것을 다루고있었습니다. Swift 이니셜 라이저 상속이 어떻게 작동하는지 이해해야합니다.

Swift는 초기화되지 않은 변수를 허용하지 않기 때문에 상속받은 클래스에서 모든 (또는) 초기화를 상속받을 수는 없습니다. 초기화되지 않은 인스턴스 변수를 서브 클래스로 서브 클래스하고 추가하면 이니셜 라이저 상속이 중지됩니다. 그리고 우리 자신의 이니셜 라이저를 추가 할 때까지 컴파일러는 우리에게 소리 칠 것입니다.

분명히 초기화되지 않은 인스턴스 변수는 기본값이 제공되지 않은 인스턴스 변수입니다 (옵션 및 암시 적으로 래핑되지 않은 옵션은 기본값을 자동으로 가정합니다 nil).

따라서이 경우 :

class Foo {
    var a: Int
}

a초기화되지 않은 인스턴스 변수입니다. a기본값을 지정 하지 않으면 컴파일되지 않습니다 .

class Foo {
    var a: Int = 0
}

또는 이니셜 a라이저 메소드에서 초기화하십시오.

class Foo {
    var a: Int

    init(a: Int) {
        self.a = a
    }
}

자, 만약 우리가 서브 클래스 Foo라면 어떻게 될까요?

class Bar: Foo {
    var b: Int

    init(a: Int, b: Int) {
        self.b = b
        super.init(a: a)
    }
}

권리? 변수를 추가하고 초기화 할 값을 설정하여 b컴파일되도록 초기화 도구를 추가했습니다 . 어떤 언어를 사용 하느냐에 따라 Bar상속 된 Foo이니셜 라이저 인 것으로 예상 할 수 있습니다 init(a: Int). 그러나 그렇지 않습니다. 그리고 어떻게 할 수 있습니까? 어떻게 Foo'의 init(a: Int)노하우는 방법에 값 할당하는 b것을 변수 Bar추가를? 그렇지 않습니다. 따라서 Bar모든 값을 초기화 할 수없는 이니셜 라이저로 인스턴스를 초기화 할 수 없습니다.

이것과 어떤 관련이 convenience있습니까?

이니셜 라이저 상속대한 규칙을 살펴 보자 .

규칙 1

서브 클래스가 지정된 이니셜 라이저를 정의하지 않으면 모든 수퍼 클래스 지정 이니셜 라이저를 자동으로 상속합니다.

규칙 2

서브 클래스가 규칙 1에 따라 상속 받거나 정의의 일부로 사용자 정의 구현을 제공하여 모든 수퍼 클래스 지정 이니셜 라이저의 구현을 제공하는 경우 모든 수퍼 클래스 편의 이니셜 라이저를 자동으로 상속합니다.

편의 초기화에 대해 언급 한 규칙 2에 주목하십시오.

뭐라고 그래서 convenience키워드가 수행 할 IS는 초기화가되는 우리에게 표시 상속 될 수 있습니다 서브 클래스가 디폴트 값없이 추가 인스턴스 변수.

이 예제 Base클래스를 보자 .

class Base {
    let a: Int
    let b: Int

    init(a: Int, b: Int) {
        self.a = a
        self.b = b
    }

    convenience init() {
        self.init(a: 0, b: 0)
    }

    convenience init(a: Int) {
        self.init(a: a, b: 0)
    }

    convenience init(b: Int) {
        self.init(a: 0, b: b)
    }
}

convenience여기에 이니셜 라이저 가 3 개 있습니다. 그것은 상속받을 수있는 세 개의 이니셜 라이저가 있다는 것을 의미합니다. 그리고 우리는 하나의 지정된 이니셜 라이저를 가지고 있습니다 (지정된 이니셜 라이저는 편의 이니셜 라이저가 아닌 모든 이니셜 라이저입니다).

기본 클래스의 인스턴스를 네 가지 방법으로 인스턴스화 할 수 있습니다.

여기에 이미지 설명을 입력하십시오

자, 서브 클래스를 만들어 봅시다.

class NonInheritor: Base {
    let c: Int

    init(a: Int, b: Int, c: Int) {
        self.c = c
        super.init(a: a, b: b)
    }
}

에서 상속받습니다 Base. 우리는 우리 자신의 인스턴스 변수를 추가했고 기본값을주지 않았으므로 우리 자신의 초기화자를 추가해야합니다. 우리는 하나를 추가 init(a: Int, b: Int, c: Int)했지만 Base클래스의 지정된 초기화 프로그램의 서명과 일치하지 않습니다 init(a: Int, b: Int). 즉, 우리는 다음 에서 이니셜 라이저를 상속 하지 않습니다 Base.

여기에 이미지 설명을 입력하십시오

그래서 우리가에서 상속 받으면 어떤 일이 일어날 까요? Base그러나 우리는 계속해서 지정된 이니셜 라이저와 일치하는 이니셜 라이저를 구현했습니다 Base.

class Inheritor: Base {
    let c: Int

    init(a: Int, b: Int, c: Int) {
        self.c = c
        super.init(a: a, b: b)
    }

    convenience override init(a: Int, b: Int) {
        self.init(a: a, b: b, c: 0)
    }
}

이제이 클래스에서 직접 구현 한 두 개의 이니셜 라이저 외에 Base클래스의 지정된 이니셜 라이저와 일치하는 이니셜 라이저를 구현했기 때문에 모든 Base클래스의 convenience이니셜 라이저 를 상속합니다 .

여기에 이미지 설명을 입력하십시오

일치하는 서명을 가진 이니셜 라이저가 convenience여기에 차이가 없음을 표시 합니다. Inheritor지정된 초기화 프로그램이 하나만 있음을 의미합니다 . 따라서에서 상속받은 경우 Inheritor하나의 지정된 이니셜 라이저를 구현 한 다음 Inheritor편의 이니셜 라이저를 상속 하면 모든 Base지정된 이니셜 라이저를 구현하고 해당 convenience이니셜 라이저를 상속 할 수 있습니다 .


16
실제로 질문에 대답하고 문서를 따르는 유일한 대답입니다. 내가 OP라면 받아 들일 것입니다.
FreeNickname

12
당신은 책을 써야합니다;)
coolbeet

1
@SLN 이 답변 은 Swift 이니셜 라이저 상속이 작동하는 방식에 대해 많이 다루고 있습니다.
nhgrif

1
@SLN 바를 생성하면 초기화되지 않은 init(a: Int)상태로 남게되기 때문 b입니다.
Ian Warburton

2
@IanWarburton 나는이 "왜"에 대한 답을 모른다. 의견의 두 번째 부분에있는 당신의 논리는 나에게 들리는 것처럼 보이지만 설명서에는 이것이 그것이 작동하는 방식이라고 명시되어 있으며 놀이터에서 요구하는 것에 대한 예를 던지면 행동이 문서화 된 것과 일치 함을 확인합니다.
nhgrif

9

대부분 명확성. 두 번째 예에서

init(name: String) {
    self.name = name
}

필수이거나 지정되어 있습니다. 모든 상수와 변수를 초기화해야합니다. 편의 이니셜 라이저는 선택 사항이며 일반적으로 초기화를 쉽게하기 위해 사용할 수 있습니다. 예를 들어, Person 클래스에 선택적 변수 성별이 있다고 가정하십시오.

var gender: Gender?

성별은 열거입니다

enum Gender {
  case Male, Female
}

이처럼 편리한 초기화를 할 수 있습니다

convenience init(maleWithName: String) {
   self.init(name: name)
   gender = .Male
}

convenience init(femaleWithName: String) {
   self.init(name: name)
   gender = .Female
}

편의 초기화 프로그램은 지정된 초기화 또는 필수 초기화 프로그램을 호출해야 합니다. 만약 당신의 클래스가 서브 클래스라면, super.init() 그것의 초기화 안에서 호출해야합니다 .


2
따라서 convenience키워드가 없어도 여러 이니셜 라이저로 수행하려는 작업은 컴파일러에게 분명 하지만 Swift는 여전히 버그가 있습니다. Apple에서 기대했던 단순함이 아닙니다. =)
Desmond Hume

2
이 답변은 아무것도 대답하지 않습니다. 당신은 "명확성"이라고 말했지만 그것이 무엇을 더 명확하게 만드는지 설명하지 않았습니다.
Robo Robok '

7

글쎄, 가장 먼저 생각 나는 것은 코드 구성 및 가독성을 위해 클래스 상속에 사용된다는 것입니다. Person수업을 계속하면서 다음 과 같은 시나리오를 생각해 보십시오.

class Person{
    var name: String
    init(name: String){
        self.name = name
    }

    convenience init(){
        self.init(name: "Unknown")
    }
}


class Employee: Person{
    var salary: Double
    init(name:String, salary:Double){
        self.salary = salary
        super.init(name: name)
    }

    override convenience init(name: String) {
        self.init(name:name, salary: 0)
    }
}

let employee1 = Employee() // {{name "Unknown"} salary 0}
let john = Employee(name: "John") // {{name "John"} salary 0}
let jane = Employee(name: "Jane", salary: 700) // {{name "Jane"} salary 700}

편의 이니셜 라이저를 사용하면 Employee()값이없는 객체 를 만들 수 있으므로 단어convenience


2
convenience키워드를 삭제 하면 Swift가 동일한 방식으로 작동하기에 충분한 정보를 얻지 못합니까?
Desmond Hume

아니요, convenience키워드를 제거 Employee하면 인수없이 객체를 초기화 할 수 없습니다 .
u54r

특히, 호출 Employee()은 (로 인해 상속 된 convenience) initializer init()를 호출합니다 self.init(name: "Unknown"). init(name: String)에 대한 편의 초기화 도구 인 Employee지정된 초기화 도구를 호출합니다.
BallpointBen

1

다른 사용자가 여기에 설명 한 점 외에도 내 이해가 조금 있습니다.

편의 초기화 프로그램과 확장 프로그램 사이의 연결을 강력하게 느낍니다. 나를 위해 편의 이니셜 라이저는 기존 클래스의 초기화를 수정 (대부분의 경우 짧거나 쉽게)하려는 경우 가장 유용합니다.

예를 들어 사용하는 일부 타사 클래스에는 init네 개의 매개 변수가 있지만 응용 프로그램에서 마지막 두 클래스 는 동일한 값을 갖습니다. 더 많은 입력을 피하고 코드를 깨끗하게 만들려면 convenience init두 개의 매개 변수만으로 a 를 정의 하고 그 안에 self.init기본값이있는 매개 변수를 마지막으로 호출 할 수 있습니다.


1
왜 Swift convenience가 호출해야한다고해서 초기화 프로그램 앞에 강제로 배치 해야 self.init합니까? 이것은 중복되고 다소 불편한 것처럼 보입니다.
Desmond Hume

1

에 따르면 스위프트 2.1 문서 , convenience초기화는 일부 특정 규칙을 준수 있습니다 :

  1. convenience초기화는 (하지 최대 만에서)하지 슈퍼 클래스에서, 같은 클래스에 intializers를 호출 할 수 있습니다

  2. convenience초기화는 체인 어딘가에 초기화 지정된 호출 할 수있다

  3. convenience초기화는 변경할 수 없습니다 모든 지정된 초기화는 반면에 -가 다른 초기화라고하기 전에 속성을 하는 또 다른 초기화를 호출하기 전에 현재의 클래스에 의해 도입되는 속성을 초기화합니다.

convenienceSwift 컴파일러는 키워드 를 사용하여 이러한 조건을 확인해야한다는 것을 알고 있습니다. 그렇지 않으면 검색 할 수 없습니다.


아마도 컴파일러는 convenience키워드 없이 이것을 정렬 할 수 있습니다.
nhgrif

또한 세 번째 요점은 오해의 소지가 있습니다. 편의 초기화 프로그램은 속성 만 변경할 수 있으며 속성은 변경할 수 없습니다 let. 속성을 초기화 할 수 없습니다. 지정된 이니셜 라이저는 지정된 이니셜 라이저를 호출하기 전에 모든 도입 된 특성을 초기화 할 책임이 super있습니다.
nhgrif

1
최소한 편의 키워드는 개발자에게 명확하게 해주 며, 가독성도 중요합니다 (개발자의 기대와 비교하여 이니셜 라이저 검사). 두 번째 요점은 좋은 것입니다. 나는 그에 따라 대답을 변경했습니다.
TheEye

1

클래스에는 지정된 이니셜 라이저가 둘 이상있을 수 있습니다. 편의 초기화 프로그램은 동일한 클래스의 지정된 초기화 프로그램을 호출해야하는 보조 초기화 프로그램입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.