Observer는 initializer에서는 동작을 하지 않는다.

특히 Objective C에서 넘어온 swift개발자라면 swift의 observer가 얼마나 큰 축복인지 잘 아시리라 생각합니다.

그런데, 생각없이 여기저기 사용하던 중, 에러를 만나게된 경우가 있어 공유를 하려고합니다. 뒷북인 분들도 계시겠지만, 저처럼 아직 앞북이신 분들도 계시리라 생각합니다.

제가 만든 클래스는 객체 생성시 인수로 받은 클래스의 내용으로 다른 변수에 내부에서 사용할 값을 만들어 선언하는 작업을 편하게 하기 위해서 옵저버를 사용했습니다.

그런데, 다른 변수에 들어가야할 값이 생성되지 않고, nil로 되어 있어 앱에서 크래쉬가 발생하고 있었습니다. 디버깅을 해보니 옵저버가 호출되지 않고 있더군요.

이것저것 테스트를 몇 번 해보며 문제를 파악할 수 있었습니다.

apple api에도 아래처럼 주의할 내용이 나와있습니다.

NOTE

The willSet and didSet observers of superclass properties are called when a property is set in a subclass initializer, after the superclass initializer has been called. They are not called while a class is setting its own properties, before the superclass initializer has been called.

For more information about initializer delegation, see Initializer Delegation for Value Types and Initializer Delegation for Class Types.

initializer는 객체를 생성하는 과정에서 초기화를 담당하는 함수로, 그 안에서 값을 선언하거나 하면 옵저버고 뭐고 다 무시하고 다이렉트로 값을 집어넣습니다.

여기 제가 어떤 실수를 범했는지 세세한 문법은 좀 생략한 예제를 써보겠습니다.

class XMLTokenizer: NSObject {
    var characterSet: NSCharacterSet
    var scanner: NSScanner {
        // scanner의 옵저버
        didSet {
            // 사실 이렇게 할 필요가 없었는데, 그저 재미삼아 써본 옵저버입니다.
            // 내용을 보면 아시겠지만, 그냥 characterSet에 처음부터 선언해도 됩니다 ㅡㅡa ㅎㅎ
            let characterSet = NSCharacterSet.whitespaceAndNewlineCharacterSet().mutableCopy()
            characterSet.addCharactersInString("=>")
            self.characterSet = characterSet
        }
    }
    
    init(scanner: NSScanner) {
        super.init()
        // 이 과정에서 저는 characterSet이 생성되기를 기대했습니다.
        // 결과는 꽝이지요.
        self.scanner = scanner
    }
}

init안에서 scanner의 didSet observer가 호출되기를 기대하고 앱을 실행하니 짜짠, 크래쉬!

검색을 해보니 역시 같은 문제를 겪은 선현들이 있어, 몇가지 해결방법을 나열해보겠습니다. 요점은 간단한데, init scope안에서 self.scanner를 대입하지 말고 scope밖에서 대입하라는 것이 요점입니다.

1.
init(scanner: NSScanner) {
    super.init() // NSObject를 상속하지 않았다면 오히려 에러가 날 겁니다. 반대로, 제 경우 NSObject를 상속하고 있기 때문에, super의 init을 호출해야 에러가 없습니다.
    // init scope의 밖에서 scanner를 설정하기 위해 이름없는 closure를 하나 만들어 실행해줍니다. 
    ({self.scanner = scanner})() 
}
2.
init(scanner: NSScanner) {
    super.init()
    // 세터를 별도로 만들어서 건네줍니다.
    // 이 경우도 NSObject를 상속했기 때문에, setScanner메소드를 쓰지 못했습니다. 컴파일러가 생성해버리거든요. NSObject를 상속하지 않는다면, setScanner로 하셔도 무방합니다.
    setInnerScanner(scanner)
}

func setInnerScanner(scanner: NSScanner) {
    self.scanner = scanner
}

이정도면, init안에서 값을 넣는다 하더라도 옵저버를 호출할 수 있습니다.

Posted by eyeman

https://singcodes.wordpress.com/2016/07/06/mixing-swift-and-objective-c-for-framework/


swift로 혹은 Objective C로 framework를 구성하는 것은 어렵지 않습니다.

swift + Objective C로 framework를 구성하는 것도 사실 크게 어렵지 않습니다. 

문제는 Objective C의 특정헤더가 private scope인 경우 같은 framework안의 swift는 해당 객체를 bridging하지 못하는 문제가 발생합니다.

저도 작업 도중 그 문제에 맞딱드려서 해결법을 찾다 modulemap을 구성하는 방법을 찾아 해결했습니다.

저처럼 곤란한 분들이 계시면 조금이라도 도움이 되었으면 좋겠네요.

Posted by eyeman

Performancing swift 3

이 글은 WWDC 2016 session 416 Understanding swift performance을 중심으로 작성하고 있습니다.

상속 혹은 레퍼런스 참조 없는 다형성(Polymorphism)

영화 이야기로 시작해볼께요. 영화 친절한 금자씨의 후반부에 보면 연쇄살인마 최민식에게 그 동안 당했던 피해자의 가족들이 차례대로 각종 연장으로 민식 아저씨를 고문합니다. 여기서 사람들의 얼굴도, 연장도 다르지만, 고문한다는 기능에서 동일한 능력을 가지고 있습니다. 사람->객체, 연장->함수, 아야야야->결과

연장마다 상처의 모습은 좀 다르지만, 결과적으로 공통의 목적을 이뤘습니다. 이게 객체의 다형성(의 일부)입니다.

https://singcodes.wordpress.com/2016/07/05/performancing-swift-3/

Posted by eyeman