2016-10-19 2 views
14

스위프트 개체의 속성 집합이 언제 변경되는지 알고 싶습니다. 이전에는 Objective-C에서이 기능을 구현했지만 Swift로 변환하는 데 어려움이 있습니다.스위프트 3에서 KVO를 사용하여 값이 변경되었는지 확인합니다.

내 이전의 목표 - C 코드는 다음과 같습니다 신속한 솔루션에서

- (void) observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context { 
    if (![change[@"new"] isEqual:change[@"old"]]) 
     [self edit]; 
} 

내 첫 번째 패스했다 :

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 
    if change?[.newKey] != change?[.oldKey] { // Compiler: "Binary operator '!=' cannot be applied to two 'Any?' operands" 
     edit() 
    } 
} 

그러나, 컴파일러는 불평 : "이진 연산자를 할 수 없다 '=!' 2 개의 'Any?'에 적용됩니다. 피연산자 "

내 두 번째 시도 :

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 
    if let newValue = change?[.newKey] as? NSObject { 
     if let oldValue = change?[.oldKey] as? NSObject { 
      if !newValue.isEqual(oldValue) { 
       edit() 
      } 
     } 
    } 
} 

그러나,이 생각에, 나는이 NSObject를 상속하지 않습니다 (I 가정) int와 같은 빠른 객체의 프리미티브에 대한 작동합니다 생각하지 않습니다 Objective-C와는 달리 버전은 변경 사전에 포함될 때 NSNumber로 묶이지 않습니다.

그래서 Swift3에서 KVO를 사용하여 값이 실제로 변경되고 있는지를 판단하는 일은 쉬운 일입니다.

또한 보너스 질문, 어떻게 '오브제'변수를 사용합니까? 그것은 내가 이름을 바꾸게하지 않을 것이고, 물론 그들 안에 공백이있는 변수를 좋아하지 않는다.

답변

22

아래는 원래 Swift 3 답변이지만 Swift 4는 공정을 단순화하여 주조의 필요성을 없애줍니다.

class Foo: NSObject { 
    @objc dynamic var bar: Int = 42 
} 

class ViewController: UIViewController { 

    let foo = Foo() 
    var token: NSKeyValueObservation? 

    override func viewDidLoad() { 
     super.viewDidLoad() 

     token = foo.observe(\.bar, options: [.new, .old]) { [weak self] object, change in 
      if change.oldValue != change.newValue { 
       self?.edit() 
      } 
     } 
    } 

    func edit() { ... } 
} 

참고이 폐쇄 기반의 접근 방식 : 당신이 Int 속성을 관찰하는 경우 예를 들어, foo 개체의 bar라고

  • 별도의 observeValue 방법을 구현하기 위해 필요의 당신을 가져옵니다을;

  • context을 지정하고 해당 컨텍스트를 확인할 필요가 없습니다.

  • change.newValuechange.oldValue은 올바르게 입력되므로 수동으로 캐스팅 할 필요가 없습니다. 속성이 선택 사항 인 경우 안전하게 포장을 풀어야하지만 캐스팅은 필요하지 않습니다.

확인하십시오 폐쇄 강한 참조주기 ([weak self] 패턴을 따라서 사용)을 소개하지 않습니다하고 있습니다에 대해 당신이 조심해야 할 유일한 것은.


내 원래 Swift 3 답변은 아래와 같습니다.


당신은 말했다 :

그러나,이 생각에, 나는이 NSObject에서 상속하지 않습니다 같은 (I 가정) Int로 빠른 객체의 기본 요소 일 것이라고 생각하지 않으며, Objective-C 버전과 달리 변경 사전에 배치 될 때 NSNumber에 박스로 묶이지 않습니다. 당신이 그 값을 보면 관찰 된 속성이 Int 경우

사실, 그것은 NSNumber로 사전을 통해 올 않습니다. 이 일부 dynamic 재산의 Int 값이 있다면 내가 거라고,

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 
    if let newValue = change?[.newKey] as? NSNumber, 
     let oldValue = change?[.oldKey] as? NSNumber, 
     newValue.intValue != oldValue.intValue { 
      edit() 
    } 
} 

을 또는 :

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 
    if let newValue = change?[.newKey] as? NSObject, 
     let oldValue = change?[.oldKey] as? NSObject, 
     !newValue.isEqual(oldValue) { 
      edit() 
    } 
} 

또는 사용을 NSNumber 같이

그래서, 당신은 어느 NSObject 세계에 머물 수 일부 빠른 수업에서 나는 앞으로 나아가서 Int :

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 
    if let newValue = change?[.newKey] as? Int, let oldValue = change?[.oldKey] as? Int, newValue != oldValue { 
     edit() 
    } 
} 

당신은 질문 :

는 또한, 보너스 질문은, 어떻게이 of object 변수의 사용을해야합니까? 그것은 내가 이름을 바꾸게하지 않을 것이고, 물론 그들 안에 공백이있는 변수를 좋아하지 않는다. 이 경우, OS가 우리를 위해이를 호출, 그래서 우리는 방법이 외부 라벨 짧은을 사용하지 않습니다

of는이 메소드를 호출 한 경우 경우에 사용이 매개 변수 (에 대한 외부 레이블입니다 서명). object은 내부 레이블 (메서드 자체 내에서 사용됨)입니다. Swift는 잠시 동안 매개 변수에 대한 외부 및 내부 라벨 기능을 가지고 있지만 Swift 3의 API에서만 진정으로 받아 들여졌습니다.

change 매개 변수를 사용할 때 사용하면 ' 하나 이상의 개체의 속성을 관찰 다시,이 개체는 KVO 예에 다른 처리를 필요로하는 경우 : 다음

foo.addObserver(self, forKeyPath: #keyPath(Foo.bar), options: [.new, .old], context: &observerContext) 
baz.addObserver(self, forKeyPath: #keyPath(Foo.qux), options: [.new, .old], context: &observerContext) 

그리고 :

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 
    guard context == &observerContext else { 
     super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) 
     return 
    } 

    if (object as? Foo) == foo { 
     // handle `foo` related notifications here 
    } 
    if (object as? Baz) == baz { 
     // handle `baz` related notifications here 
    } 
} 

제쳐두고, 일반적으로 context을 사용하는 것이 좋습니다.그 컨텍스트를 사용하여 관찰자를 추가 한 후

private var observerContext = 0 

: 그리고하는 private var을 내 observeValue 수 있도록이 다음

foo.addObserver(self, forKeyPath: #keyPath(Foo.bar), options: [.new, .old], context: &observerContext) 

그리고 있는지 수퍼 클래스에 의해 설립의 context, 그리고 하나 :

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 
    guard context == &observerContext else { 
     super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) 
     return 
    } 

    if let newValue = change?[.newKey] as? Int, let oldValue = change?[.oldKey] as? Int, newValue != oldValue { 
     edit() 
    } 
} 
+0

흥미 값이 될 수 있지만 아무것도 하나가 '변화'의 모든 값이 NSObject의 것을 실현되면 첫 번째 시도는 실제로 고정 할 수 있습니다 (Int, 즉 String, Date, enum 또는 사용자 정의 클래스 등)가 아닙니다. 그래서 아마도 NSObject 트릭이 작동 할 것입니다. 귀하의 답변에 감사드립니다. – aepryus

+0

그래,'NSObject' 기술은 괜찮습니다. 방금 당신에게 옵션을 보여주고있었습니다. 그건 그렇고, 당신은 Objective-C로 표현할 수있는 타입을 가진 KVO 만 할 수 있습니다 (예를 들어,'enum'을 관찰 할 수 없다; 원시 값으로'enum'을 가져야하고'. rawValue'가 아니라'enum' 그 자체). – Rob

+0

OK,이 포괄적 인 답변에 대해 감사드립니다. – aepryus

0

내 원래 '두 번째 시도'에는 값이 0 또는 0으로 변경 될 때 감지하지 못하는 버그가 있습니다. 당신이 원하는 경우

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 
    let oldValue: NSObject? = change?[.oldKey] as? NSObject 
    let newValue: NSObject? = change?[.newKey] as? NSObject 
    if newValue != oldValue { 
     edit() 
    } 
} 

또는 더 간결한 버전 :

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 
    if change?[.newKey] as? NSObject != change?[.oldKey] as? NSObject { 
     edit() 
    } 
} 
관련 문제