Swift에서 초기화하는 동안 didSet을 호출 할 수 있습니까?


217

질문

Apple의 문서는 다음을 지정합니다.

속성이 처음 초기화 될 때 willSet 및 didSet 옵저버가 호출되지 않습니다. 속성 값이 초기화 컨텍스트 외부에서 설정된 경우에만 호출됩니다.

초기화 중에 이들을 강제로 호출 할 수 있습니까?

왜?

이 수업이 있다고 가정 해 봅시다

class SomeClass {
    var someProperty: AnyObject {
        didSet {
            doStuff()
        }
    }

    init(someProperty: AnyObject) {
        self.someProperty = someProperty
        doStuff()
    }

    func doStuff() {
        // do stuff now that someProperty is set
    }
}

doStuff처리 호출을보다 간결하게하기 위해 메소드 를 만들었지 만 오히려 didSet함수 내에서 속성을 처리하려고 합니다. 초기화 중에 이것을 강제로 호출하는 방법이 있습니까?

최신 정보

내 클래스의 편의 초기화 도구를 제거하고 초기화 후에 속성을 설정하도록 결정했습니다. 이를 통해 didSet항상 부름을받을 수 있습니다. 이것이 전체적으로 더 좋은지 결정하지는 않았지만 내 상황에 잘 맞습니다.


솔직히 "이것은 신속한 작동 방식"을 받아들이는 것이 가장 좋습니다. var 문에 인라인 초기화 값을 설정하면 "didSet"을 호출하지 않습니다. 그렇기 때문에 "init ()"상황은 "didSet"을 호출하지 않아야합니다. 그것은 모두 의미가 있습니다.
Fattie

@Logan 질문 자체가 내 질문에 대답했습니다.) 감사합니다!
AamirR

2
편의 초기화 도구를 사용하려면 다음과 함께 사용할 수 있습니다 defer.convenience init(someProperty: AnyObject) { self.init() defer { self.someProperty = someProperty }
Darek Cieśla

답변:


100

고유 한 set-Method를 작성하고 init-Method 내에서 사용하십시오.

class SomeClass {
    var someProperty: AnyObject! {
        didSet {
            //do some Stuff
        }
    }

    init(someProperty: AnyObject) {
        setSomeProperty(someProperty)
    }

    func setSomeProperty(newValue:AnyObject) {
        self.someProperty = newValue
    }
}

somePropertytype :( AnyObject!암시 적으로 래핑되지 않은 옵션) 으로 선언 하면 someProperty설정 하지 않고 self를 완전히 초기화 할 수 있습니다. 전화를 걸면 setSomeProperty(someProperty)해당하는 전화를합니다 self.setSomeProperty(someProperty). 자체가 완전히 초기화되지 않았기 때문에 일반적으로이 작업을 수행 할 수 없습니다. 이후 someProperty초기화를 필요로하지 않으며 자기의 방법을 따라 호출, 스위프트는 초기화 문맥을 떠나 didSet가 실행됩니다.


3
왜 이것이 init의 self.someProperty = newValue와 다른가? 작동하는 테스트 사례가 있습니까?
mmmmmm

2
왜 작동하는지 모르겠지만 작동합니다. init ()-Method 내에서 새 값을 직접 설정하면 didSet ()을 호출하지 않지만 주어진 메소드 setSomeProperty ()를 사용하면 호출됩니다.
Oliver

50
나는 여기서 무슨 일이 일어나고 있는지 알고 있다고 생각합니다. somePropertytype :( AnyObject!암시 적으로 래핑되지 않은 옵션) 으로 선언 하면 설정 self하지 않고 완전히 초기화 할 수 someProperty있습니다. 전화를 걸면 setSomeProperty(someProperty)해당하는 전화를합니다 self.setSomeProperty(someProperty). 일반적 self으로 완전히 초기화되지 않았으므로이 작업을 수행 할 수 없습니다. 이후 someProperty초기화를 필요로하지 않으며, 당신이 방법에 의존에 호출되어 self, 스위프트는 초기화 문맥을 떠나 didSet실행됩니다.
Logan

3
실제 init () DOES get didSet 외부에서 설정되는 것 같습니다. 말 그대로 설정되지 않은 것은 init() { *HERE* }didSet을 호출합니다. 이 질문에는 훌륭하지만 보조 함수를 사용하여 일부 값을 설정하면 didSet이 호출됩니다. 해당 세터 기능을 재사용하려는 경우 좋지 않습니다.
WCByrne

4
@Mark는 Swift에 상식이나 논리를 적용하는 것은 터무니없는 일입니다. 그러나 업 보트를 신뢰하거나 직접 시도해 볼 수 있습니다. 방금했는데 작동합니다. 그리고 그렇습니다. 전혀 이해가되지 않습니다.
Dan Rosenstark

305

당신이 사용하는 경우 defer의 내부 초기화 , 당신은 이미 초기화 한 것을 선택적 속성 또는 추가 업데이트 비 선택적인 속성을 업데이트하기위한 후에 당신은 어떤라고 한 super.init()다음, 방법 willSet, didSet등 호출됩니다. 적절한 장소에서 전화를 추적 해야하는 별도의 메소드를 구현하는 것보다 이것이 더 편리하다는 것을 알았습니다.

예를 들면 다음과 같습니다.

public class MyNewType: NSObject {

    public var myRequiredField:Int

    public var myOptionalField:Float? {
        willSet {
            if let newValue = newValue {
                print("I'm going to change to \(newValue)")
            }
        }
        didSet {
            if let myOptionalField = self.myOptionalField {
                print("Now I'm \(myOptionalField)")
            }
        }
    }

    override public init() {
        self.myRequiredField = 1

        super.init()

        // Non-defered
        self.myOptionalField = 6.28

        // Defered
        defer {
            self.myOptionalField = 3.14
        }
    }
}

생산량 :

I'm going to change to 3.14
Now I'm 3.14

6
건방진 작은 트릭, 난 좋아 ... 스위프트의 이상한 기발한.
Chris Hatton

4
큰. 을 사용하는 또 다른 유용한 사례를 찾았습니다 defer. 감사.
Ryan

1
연기의 큰 사용! 하나 이상의 공감대를 줄 수 있기를 바랍니다.
Christian Schnorr

3
매우 흥미로운 .. 나는 이런 식으로 연기를 본 적이 없다. 웅변적이고 작동합니다.
aBikis 2016 년

그러나 myOptionalField를 두 번 설정하면 성능 측면에서 좋지 않습니다. 무거운 계산이 수행되면 어떻게됩니까?
Yakiv Kovalskyi

77

Oliver의 답변의 변형으로 줄을 마감으로 감쌀 수 있습니다. 예 :

class Classy {

    var foo: Int! { didSet { doStuff() } }

    init( foo: Int ) {
        // closure invokes didSet
        ({ self.foo = foo })()
    }

}

편집 : Brian Westphal의 답변이 더 좋습니다. 그의 좋은 점은 의도를 암시한다는 것입니다.


2
그것은 영리하며 중복 메소드로 클래스를 오염시키지 않습니다.
pointum

좋은 답변, 감사합니다! Swift 2.2에서는 클로저를 항상 괄호로 묶어야합니다.
Max

@Max Cheers, 이제 샘플 포함 parens
original_username

2
영리한 답변! 참고 사항 : 클래스가 다른 클래스의 하위 클래스 인 경우 ({self.foo = foo}) () 전에 super.init ()를 호출해야합니다.
kcstricks

1
@Hlung, 나는 다른 어떤 것보다 미래에 깰 가능성이 더 크다고 느끼지 않지만 여전히 코드에 주석을 달지 않으면 그것이하는 일이 명확하지 않다는 문제가 있습니다. 최소한 Brian의 "지연"답변은 독자에게 초기화 후 코드를 수행하고 싶다는 힌트를줍니다.
original_username

10

나는 같은 문제가 있었고 이것은 나를 위해 일한다.

class SomeClass {
    var someProperty: AnyObject {
        didSet {
            doStuff()
        }
    }

    init(someProperty: AnyObject) {
        defer { self.someProperty = someProperty }
    }

    func doStuff() {
        // do stuff now that someProperty is set
    }
}

어디서 구했어?
Vanya

3
이 방법은 더 이상 작동하지 않습니다. 컴파일러는 두 가지 오류를 발생시킵니다. – 모든 저장된 속성이 초기화되기 전에 메서드 호출 '$ defer'에 사용 된 'self'– 저장된 속성을 모두 초기화하지 않고 초기화 프로그램에서 반환
Edward B

당신 말이 맞지만 해결 방법이 있습니다. someProperty 만 암시 적 랩핑되지 않은 또는 선택적으로 선언하고 실패를 피하기 위해 무 연속으로 기본값을 제공해야합니다. tldr; someProperty는 선택적 유형이어야합니다.
Mark

2

서브 클래스 에서이 작업을 수행하면 작동합니다.

class Base {

  var someProperty: AnyObject {
    didSet {
      doStuff()
    }
  }

  required init() {
    someProperty = "hello"
  }

  func doStuff() {
    print(someProperty)
  }
}

class SomeClass: Base {

  required init() {
    super.init()

    someProperty = "hello"
  }
}

let a = Base()
let b = SomeClass()

에서 a예를 들어, didSet트리거되지 않습니다. 그러나 b예를 들어 didSet서브 클래스에 있기 때문에 트리거됩니다. 그것은 무엇 뭔가 관련이있다 initialization context, 정말 의미를이 경우에 superclass그것에 대해 한주의


1

이것이 해결책은 아니지만 클래스 생성자를 사용하는 다른 방법이 있습니다.

class SomeClass {
    var someProperty: AnyObject {
        didSet {
            // do stuff
        }
    }

    class func createInstance(someProperty: AnyObject) -> SomeClass {
        let instance = SomeClass() 
        instance.someProperty = someProperty
        return instance
    }  
}

1
이 솔루션은 someProperty초기화 중에 값을 제공하지 않으므로 옵션을 변경해야합니다 .
Mick MacCallum

1

수퍼 클래스에서 사용할 수있는 속성 을 호출 willSet하거나 didSet내부 에서 호출하려는 특정 경우에는 init단순히 수퍼 속성을 직접 할당 할 수 있습니다.

override init(frame: CGRect) {
    super.init(frame: frame)
    // this will call `willSet` and `didSet`
    someProperty = super.someProperty
}

참고 Charlesism의 폐쇄와 솔루션은 항상 그 경우에도 작동합니다. 그래서 내 솔루션은 대안입니다.


-1

obj-с 방식으로 해결할 수 있습니다.

class SomeClass {
    private var _someProperty: AnyObject!
    var someProperty: AnyObject{
        get{
            return _someProperty
        }
        set{
            _someProperty = newValue
            doStuff()
        }
    }
    init(someProperty: AnyObject) {
        self.someProperty = someProperty
        doStuff()
    }

    func doStuff() {
        // do stuff now that someProperty is set
    }
}

1
안녕하세요 @ yshilov, 이것이 self.someProperty초기화 범위를 떠날 때 기술적으로 기대했던 문제를 실제로 해결한다고 생각하지 않습니다 . 나는 같은 일을 할 수 있다고 생각합니다 var someProperty: AnyObject! = nil { didSet { ... } }. 그럼에도 불구하고, 일부는 추가에 대한 작업 주위로 감사를 감사 할 수
로건

@Logan nope, 작동하지 않습니다. didSet은 init ()에서 완전히 무시됩니다.
yshilov
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.