Swift 클래스 오류 : super.init 호출에서 속성이 초기화되지 않았습니다


219

나는 두 개의 수업이 Shape있고Square

class Shape {
    var numberOfSides = 0
    var name: String
    init(name:String) {
        self.name = name
    }
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

class Square: Shape {
    var sideLength: Double

    init(sideLength:Double, name:String) {
        super.init(name:name) // Error here
        self.sideLength = sideLength
        numberOfSides = 4
    }
    func area () -> Double {
        return sideLength * sideLength
    }
}

위의 구현으로 오류가 발생합니다.

property 'self.sideLength' not initialized at super.init call
    super.init(name:name)

self.sideLength전화하기 전에 왜 설정 해야 super.init합니까?


이것이 실제 기술적 인 제한이 아니라 좋은 프로그래밍 관행과 관련이 있다는 것을 확신하십시오. Shape가 Square가 재정의 한 함수를 호출하는 경우 Square는 sideLength를 사용하려고하지만 아직 초기화되지 않았습니다. 스위프트는 아마도 기본 클래스를 호출하기 전에 인스턴스를 먼저 초기화하도록하여 우발적 으로이 작업을 수행하지 못하게 할 수 있습니다.
cwharris 2016 년

3
이 책의 예는 나쁘다. 모든 속성이 초기화되었는지 확인 하려면 항상 super.init ()를 마지막 으로 호출 해야합니다 ( 아래 설명 참조).
파스칼

답변:


173

귀하의 질문에 대답하는 Swift Programming Language에서 인용하십시오 :

"Swift의 컴파일러는 4 단계의 유용한 안전 점검을 수행하여 오류없이 2 단계 초기화가 완료되었는지 확인합니다."

안전 점검 1 "지정된 이니셜 라이저는 클래스에 의해 도입 된 모든 속성이 수퍼 클래스 이니셜 라이저로 위임되기 전에 초기화되어야합니다."

발췌 : Apple Inc.“Swift Programming Language.” iBooks. https://itunes.apple.com/us/book/swift-programming-language/id881256329?mt=11


47
이제 C ++, C # 또는 Java와 비교할 때 엄청난 변화입니다.
MDJ

12
@MDJ 물론입니다. 솔직히 그것의 부가 가치도 보지 못합니다.
Ruben

20
실제로는 하나 인 것 같습니다. C #에서 슈퍼 클래스 생성자는 재정의 가능한 (가상) 메소드를 호출하면 안됩니다. 왜냐하면 아무도 완전히 초기화되지 않은 하위 클래스와 어떻게 반응하는지 알 수 없기 때문입니다. 수퍼 클래스 생성자가 실행될 때 서브 클래스 추가 상태가 양호하므로 Swift는 정상입니다. 또한 Swift에서는 최종 방법을 제외한 모든 방법을 무시할 수 있습니다.
MDJ

17
자체 하위보기를 만드는 UIView 하위 클래스를 만들려면 프레임이없는 하위보기를 초기화하고 나중에보기를 참조 할 수 없으므로 프레임을 추가해야한다는 것을 의미하기 때문에 특히 성가신 것으로 나타났습니다. AFTER가 super.init를 호출 할 때까지 바인딩됩니다.
Ash

5
@Janos 속성을 선택적으로 설정하면에서 초기화 할 필요가 없습니다 init.
JeremyP

105

Swift에는 초기화 프로그램에서 수행되는 매우 명확하고 구체적인 작업 순서가 있습니다. 몇 가지 기본 예제로 시작하여 일반적인 경우까지 진행해 보겠습니다.

객체 A를 봅시다. 다음과 같이 정의하겠습니다.

class A {
    var x: Int
    init(x: Int) {
        self.x = x
    }
}

A에는 수퍼 클래스가 없으므로 super.init () 함수가 존재하지 않으므로이를 호출 할 수 없습니다.

자 이제 B라는 새로운 클래스로 A를 서브 클래스하자.

class B: A {
    var y: Int
    init(x: Int, y: Int) {
        self.y = y
        super.init(x: x)
    }
}

이것은 Objective-C에서 출발하여 [super init]일반적으로 다른 것보다 먼저 호출됩니다. 스위프트에서는 그렇지 않습니다. 메소드 호출 (수퍼 클래스의 이니셜 라이저 포함)을 포함하여 다른 작업을 수행하기 전에 인스턴스 변수가 일관된 상태인지 확인해야합니다.


1
이것은 매우 도움이되는 명확한 예입니다. 감사합니다!
FullMetalFist

초기화 (예 : INT) I는 예를 들어 계산에 Y 값이 필요한 경우 어떻게 {self.y로는 Y *의 self.x는 super.init의 () =}
6rod9

1
init (y : Int, x : Int = 0) 같은 것을 사용하십시오. {self.y = y * x; self.x = x; super.init (x : x)}, 슈퍼 클래스 이름 A에는 빈 생성자가 없기 때문에 위 예제를 참조하여 수퍼 클래스의 빈 생성자를 직접 호출 할 수 없습니다.
Hitendra Solanki

43

로부터 문서

안전 점검 1

지정된 이니셜 라이저는 클래스가 도입 한 모든 속성이 수퍼 클래스 이니셜 라이저로 위임되기 전에 초기화되어야합니다.


왜 이런 안전 점검이 필요한가요?

이에 대한 답을 얻으려면 초기화 과정을 신속하게 진행하십시오.

2 단계 초기화

Swift의 클래스 초기화는 2 단계 프로세스입니다. 첫 번째 단계에서는 저장된 각 속성에 속성을 도입 한 클래스에 의해 초기 값이 할당됩니다. 모든 저장된 속성의 초기 상태가 결정되면 두 번째 단계가 시작되고 새 클래스를 사용할 준비가 된 것으로 간주되기 전에 각 클래스에 저장된 속성을 사용자 지정할 수있는 기회가 주어집니다.

2 단계 초기화 프로세스를 사용하면 클래스 계층 구조의 각 클래스에 완전한 유연성을 제공하면서 초기화가 안전 해집니다. 2 단계 초기화는 속성 값이 초기화되기 전에 속성 값에 액세스 하지 못하게하고 다른 초기화 프로그램이 속성 값을 예기치 않게 다른 값으로 설정하지 못하게합니다.

따라서 2 단계 초기화 프로세스가 위에 정의 된대로 수행되도록하기 위해 4 가지 안전 점검이 있습니다.

안전 점검 1

지정된 이니셜 라이저는 클래스가 도입 한 모든 속성이 수퍼 클래스 이니셜 라이저로 위임되기 전에 초기화되어야합니다.

이제 2 단계 초기화는 순서에 대해 이야기하지 않지만이 안전 검사 super.init는 모든 속성을 초기화 한 후에 순서를 지정합니다.

안전 점검 1은 2 단계 초기화로 인해이 안전 점검 1없이 속성 값을 초기화하기 전에 액세스 할 수 없도록 하기 때문에 관련이없는 것처럼 보일 수 있습니다.

이 샘플 에서처럼

class Shape {
    var name: String
    var sides : Int
    init(sides:Int, named: String) {
        self.sides = sides
        self.name = named
    }
}

class Triangle: Shape {
    var hypotenuse: Int
    init(hypotenuse:Int) {
        super.init(sides: 3, named: "Triangle") 
        self.hypotenuse = hypotenuse
    }
}

Triangle.init사용하기 전에 모든 속성을 초기화했습니다. 안전 점검 1은 관련이없는 것 같습니다.

하지만 약간 복잡한 시나리오가있을 수 있습니다.

class Shape {
    var name: String
    var sides : Int
    init(sides:Int, named: String) {
        self.sides = sides
        self.name = named
        printShapeDescription()
    }
    func printShapeDescription() {
        print("Shape Name :\(self.name)")
        print("Sides :\(self.sides)")
    }
}

class Triangle: Shape {
    var hypotenuse: Int
    init(hypotenuse:Int) {
        self.hypotenuse = hypotenuse
        super.init(sides: 3, named: "Triangle")
    }

    override func printShapeDescription() {
        super.printShapeDescription()
        print("Hypotenuse :\(self.hypotenuse)")
    }
}

let triangle = Triangle(hypotenuse: 12)

출력 :

Shape Name :Triangle
Sides :3
Hypotenuse :12

여기에서 super.init를 설정하기 전에를 호출했다면 호출은를 호출했을 것이고 hypotenuse, 그 이후에는 우선의 삼각형 클래스 구현으로 대체 될 것입니다 . 삼각형 클래스 액세스는 비 선택적 특성 아직 초기화되지 않았습니다. 그리고 2 단계 초기화로 인해 속성 값이 초기화되기 전에 액세스 할 수 없으므로 허용되지 않습니다.super.initprintShapeDescription()printShapeDescription()printShapeDescription()hypotenuse

따라서 2 단계 초기화가 정의 된대로 수행되고, 특정 호출 순서 가 있어야하며, 즉 클래스에 super.init의해 도입 된 모든 속성을 초기화 한 후 안전 점검이self 필요합니다.


1
훌륭한 설명, 상위 답변에 추가해야합니까?
Guy Daher

따라서 기본적으로 슈퍼 클래스 는 (오버라이드 된) 함수를 호출 init 할 수 있기 때문에 그 함수는 하위 클래스 속성에 액세스하고 값을 설정하지 않으 super려면 모든 값을 설정 한 후에 호출 해야합니다. 알겠습니다. Objective-C가 어떻게 그랬으며 왜 super먼저 전화해야했는지 궁금 하십니까?
Honey

기본적으로 무엇을 당신이 지적하고 것은 유사한 배치 님 printShapeDescription() 전에 self.sides = sides; self.name = named; 이 오류를 생성 할 것이다 : use of 'self' in method call 'printShapeDescription' before all stored properties are initialized. OP의 오류는 런타임 오류의 '가능성'을 줄이기 위해 제공됩니다.

'possibility'라는 단어를 구체적으로 사용했습니다. 왜냐하면 `print ( "nothing")과 같은 printShapeDescription함수 self가 아니었다면 아무런 문제가 없었기 때문입니다. (이 아니기 때문에 그러나 컴파일러는 오류가 발생 것이라고도에 대한 슈퍼 스마트)

음 objc는 안전하지 않았습니다. 스위프트는 형식이 안전하므로 선택 사항이 아닌 객체는 실제로 nonnil이어야합니다!
Daij-Djan

36

모든 인스턴스 변수를 초기화 한 후 "super.init ()"를 호출해야합니다.

약 28:40에 Apple의 "Intermediate Swift"비디오 (Apple 개발자 비디오 리소스 페이지 https://developer.apple.com/videos/wwdc/2014/ 에서 찾을 수 있음 )에서 모든 초기화 프로그램이 인스턴스 변수를 초기화 한 후 수퍼 클래스를 호출해야합니다.

Objective-C에서는 그 반대였습니다. Swift에서는 모든 속성을 사용하기 전에 초기화해야하므로 먼저 속성을 초기화해야합니다. 이는 속성을 먼저 초기화하지 않고 수퍼 클래스의 "init ()"메서드에서 재정의 된 함수를 호출하지 못하도록하기위한 것입니다.

따라서 "Square"의 구현은 다음과 같아야합니다.

class Square: Shape {
    var sideLength: Double

    init(sideLength:Double, name:String) {
        self.sideLength = sideLength
        numberOfSides = 4
        super.init(name:name) // Correct position for "super.init()"
    }
    func area () -> Double {
        return sideLength * sideLength
    }
}

1
결코 추측하지 않았을 것입니다. super로 초기화하는 것은 주먹 진술이어야한다고 생각했습니다. !! 흠. 신속한 변화.
mythicalcoder

왜 이런 일이 필요합니까? 기술적 인 이유를 알려주십시오
Masih

14

못생긴 형식으로 죄송합니다. 선언 후에 질문 문자를 넣으면 모든 것이 정상입니다. 질문은 컴파일러에게 값이 선택적이라는 것을 알려줍니다.

class Square: Shape {
    var sideLength: Double?   // <=== like this ..

    init(sideLength:Double, name:String) {
        super.init(name:name) // Error here
        self.sideLength = sideLength
        numberOfSides = 4
    }
    func area () -> Double {
        return sideLength * sideLength
    }
}

편집 1 :

이 오류를 건너 뛰는 더 좋은 방법이 있습니다. jmaschad의 의견에 따르면 귀하의 경우 선택 사항을 사용해야 할 이유가 없습니다. 따라서 선언 후 멤버를 초기화하면됩니다.

class Square: Shape {
    var sideLength: Double=Double()   

    init(sideLength:Double, name:String) {
        super.init(name:name)
        self.sideLength = sideLength
        numberOfSides = 4
    }
    func area () -> Double {
        return sideLength * sideLength
    }
}

편집 2 :

이 대답에 두 가지 마이너스를 얻은 후에 더 나은 방법을 찾았습니다. 생성자에서 클래스 멤버를 초기화하려면 생성자 내부와 super.init () 호출 전에 초기 값을 지정해야합니다. 이처럼 :

class Square: Shape {
    var sideLength: Double  

    init(sideLength:Double, name:String) {
        self.sideLength = sideLength   // <= before super.init call..
        super.init(name:name)
        numberOfSides = 4
    }
    func area () -> Double {
        return sideLength * sideLength
    }
}

Swift를 배우는 행운을 빕니다.


그냥 스위치 super.init(name:name)self.sideLength = sideLength. sideLength선택 사항으로 선언 하는 것은 잘못된 것이며 나중에 포장을 풀어야 할 때 추가적인 번거 로움이 생깁니다.
Johannes Luong

예, 이것은 옵션입니다. 감사합니다
fnc12

실제로 var sideLength: Double초기 값을 할당 할 필요가 없습니다
Jarsen

실제로 선택적 상수가 있으면 어떻게됩니까? 그것으로 무엇을해야합니까? 생성자에서 초기화해야합니까? 왜 그렇게해야하는지 모르겠지만 컴파일러는 Swift 1.2에서 불만을 제기했습니다
Van Du Tran

1
완벽 해! 세 가지 솔루션 모두 "?", "String ()"으로 작동했지만 문제는 속성 중 하나를 '할당'하지 않았으며 작동했을 때 작동한다는 것입니다! 고마워 친구
Naishta

9

swift는 모든 멤버를 사용하기 전에 var를 초기화하도록합니다. 슈퍼 턴일 때 어떤 일이 발생하는지 확신 할 수 없기 때문에 오류가 발생합니다. 죄송합니다.


1
부모 클래스에는 자식에 선언 된 속성에 대한 가시성이 없어야하므로 IMO에 적합하지 않습니다!
Andy Hin

1
그것은 아니지만 당신은 물건을 재정의하고 '슈퍼 만들기 전에'자기 사용을 시작할 수 있습니다
Daij-Djan

여기에서 볼 수 있고 그 뒤에 나오는 의견이 있습니까? 나는 정확히 당신이 말하는 것을 말하고 있다고 생각합니다. 즉 컴파일러가 미안하기보다는 안전하고 싶다고 말하고 있습니다. 제 유일한 질문은 objective-c 가이 문제를 어떻게 해결 했습니까? 아니면 그렇지 않습니까? 그렇지 않다면 왜 여전히 super.init첫 줄 에 글을 써야 합니까?

7

에드워드,

예제에서 다음과 같이 코드를 수정할 수 있습니다.

var playerShip:PlayerShip!
var deltaPoint = CGPointZero

init(size: CGSize)
{
    super.init(size: size)
    playerLayerNode.addChild(playerShip)        
}

이것은 암시 적으로 랩핑되지 않은 옵션을 사용하고 있습니다.

문서에서 우리는 읽을 수 있습니다 :

"선택적 옵션과 마찬가지로 암시 적으로 래핑되지 않은 선택적 변수 또는 속성을 선언 할 때 초기 값을 제공하지 않으면 자동으로 값이 nil로 설정됩니다."


이것이 가장 깨끗한 옵션이라고 생각합니다. 첫 번째 Swift 시도에서 AVCaptureDevice 유형의 멤버 변수를 사용하여 직접 인스턴스화 할 수 없으므로 init () 코드가 필요했습니다. 그러나 ViewController에는 여러 이니셜 라이저가 필요하므로 init ()에서 일반적인 초기화 메소드를 호출 할 수 없으므로이 답변은 모든 이니셜 라이저에서 중복 코드 복사 / 붙여 넣기를 피하는 유일한 옵션 인 것 같습니다.
sunetos 2016 년


6

선언의 끝에 nil을 추가하십시오.


// Must be nil or swift complains
var someProtocol:SomeProtocol? = nil

// Init the view
override init(frame: CGRect)
    super.init(frame: frame)
    ...

이것은 내 경우에는 효과가 있었지만 귀하의 경우에는 효과가 없을 수 있습니다.


프로토콜이있는 UIViewController와 함께 UIView와 함께 사용하는 것이
좋습니다

1

잘못된 순서로 초기화하고 있습니다.

     class Shape2 {
        var numberOfSides = 0
        var name: String
        init(name:String) {
            self.name = name
        }
        func simpleDescription() -> String {
            return "A shape with \(numberOfSides) sides."
        }
    }

    class Square2: Shape2 {
        var sideLength: Double

        init(sideLength:Double, name:String) {

            self.sideLength = sideLength
            super.init(name:name) // It should be behind "self.sideLength = sideLength"
            numberOfSides = 4
        }
        func area () -> Double {
            return sideLength * sideLength
        }
    }

0

아마 약간의 다운 보트를받을 것입니다. 그러나 솔직히 말하면 인생은이 방법으로 더 쉽습니다

class CSListServerData<ListItem: CSJsonData>: CSServerData {
    var key: String!
    var type: ListItem.Type!
    var property: CSJsonDataList<ListItem>!

    func construct(_ key: String, _ type: ListItem.Type) -> Self {
        self.key = key
        self.type = type
        property = CSJsonDataList(self, type, key)
        return self
    }

    func construct(_ type: ListItem.Type) { construct("list", type) }

    var list: [ListItem] { property.list }
}

-4

엄청나게 바보 같은 디자인입니다.

다음과 같은 것을 고려하십시오.

.
.
.
var playerShip:PlayerShip
var deltaPoint = CGPointZero

init(size: CGSize)
{
    super.init(size: size)
    playerShip = PlayerShip(pos: CGPointMake(self.size.width / 2.0, 100))
    playerLayerNode.addChild(playerShip)        
}
.
.
.

위에서 언급했듯이 이것은 유효하지 않습니다. 그러나 다음과 같습니다.

.
.
.
var playerShip:PlayerShip = PlayerShip(pos: CGPointMake(self.size.width / 2.0, 100))
var deltaPoint = CGPointZero

init(size: CGSize)
{
    super.init(size: size)
    playerLayerNode.addChild(playerShip)        
}
.
.
.

'자기'가 초기화되지 않았기 때문입니다.

이 버그가 곧 수정되기를 진심으로 바랍니다.

(예, 빈 객체를 만든 다음 크기를 설정할 수 있지만 어리석은 것입니다).


2
이것은 OOP의 새로운 프로그래밍 기초 인 버그가 아닙니다.
Hitendra Solanki
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.