web-dev-qa-db-ja.com

FoundationでremoveObserver:forKeyPath:を使用してクラッシュする

Xcodeの「クラッシュ」セクションから取得した次のクラッシュログに問題があります。このクラッシュレポートの影響を受けるデバイスはごくわずかです。

問題を分析しましたが、Appleフレームワークのバグだと思います。しかし、それを再現する方法が見つかりません。

ここで同様の議論: removeObserver:forKeyPath: でのクラッシュのヘルプ。

ヒントはありますか?

スレッド0の名前:スレッド0がクラッシュしました:

0財団
0x23507591 _NSKeyValueReplaceObservationInfoForObject + 69(NSKeyValueObserving.m:1166)

1財団
0x23506fe7- [NSObject(NSKeyValueObserverRegistration)_removeObserver:forProperty:] + 327(NSKeyValueObserving.m:1552)

2財団
0x23506b03- [NSObject(NSKeyValueObserverRegistration)removeObserver:forKeyPath:] + 163(NSKeyValueObserving.m:1696)

3財団
0x235069a7- [NSObject(NSKeyValueObserverRegistration)removeObserver:forKeyPath:context:] + 219(NSKeyValueObserving.m:1663)

4 ApplicationName 0x0002e233- [Supervisor removeObjectObserver:forKeyPath:] + 115(Supervisor.m:344)

どこ removeObjectObserver:forKeyPath:

- (void) removeObjectObserver:(id)object forKeyPath:(NSString *)keyPath { 

    @try {        
        [object removeObserver:self forKeyPath:keyPath context:PrivateKVOContext];

    } @catch (NSException *exception) { }
}
20
Lorenzo B

Observers in Objective-Cは特に注意して使用する必要があります。同じオブザーバーを同じオブジェクトのプロパティに複数回追加しないでください。削除があればラップします。

  if ([self observationInfo]) {
        @try {
            [self removeObserver:self forKeyPath:keyPath];
        }
        @catch (NSException *exception) {}
    }

オブザーバーを2回削除しようとしたか、存在しないオブザーバーを削除しようとしたために、クラッシュが発生しています。

この方法でobserversを追加する必要があります:

[yourObject addObserver:self forKeyPath:keypath options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionInitial context:nil/yourContext];

編集:すでに割り当て解除されたオブジェクトのオブザーバーを削除すると、このクラッシュが発生する可能性があります。

  if (object && [self observationInfo]) {
    @try {
                [self removeObserver:self forKeyPath:keyPath];
            }
            @catch (NSException *exception) {}
}
8
Loegic

通常、オブジェクトのキーパスが現時点で監視されているかどうかを知ることができるivarがあります。 @property(...)のようにBOOL textFieldTextObserving;また、add/remove-observingメソッドは、オブザーバーを2回追加/削除しないように、追加/削除する前にこのプロパティを確認する必要があります。監視オブジェクトとキーパスが多数ある場合は、NSDictionaryを使用することもできます(@(BOOL)をオブジェクトとして保持し、-識別子をキーとして保持するため)。

とにかく、@ try-exceptionを使用して何かを行うことは、Objective-Cの推奨される方法ではありません。 Appleドキュメントによると:

"You should not use a try-catch block in place of standard programming checks for Objective-C methods. In the case of an NSArray, for example, you should always check the array’s count to determine the number of items before trying to access an object at a given index. The objectAtIndex: method throws an exception if you make an out-of-bounds request so that you can find the bug in your code early in the development cycle—you should avoid throwing exceptions in an app that you ship to users."https://developer.Apple.com/library/mac/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/ErrorHandling/ErrorHandling.html

5
Vladlex

答えるには遅すぎますが、私は同じ問題に直面しています。だから私は他の人のためにこれを書くことにしました。

注:クラッシュの主な理由は、追加する前にオブザーバーを削除しようとすることです。

オブザーバーを安全に削除するのに役立つ拡張機能をいくつか作成しました。 Swift 5。

クラッシュすることなく、追加する前に削除できるようになりました。 deinitでオブザーバーも削除してください。

使用法:

objectToObserve.safeRemoveObserver(self, keyPath: "myDate", context: &myContext)

拡張機能:

extension NSRegularExpression {

convenience init(_ pattern: String) {
    do {
        try self.init(pattern: pattern)
    } catch {
        preconditionFailure("Illegal regular expression: \(pattern).")
    }
}

func matches(_ string: String) -> Bool {
    let range = NSRange(location: 0, length: string.utf16.count)
    return firstMatch(in: string, options: [], range: range) != nil
   }
 }

extension NSObject {

func safeRemoveObserver(_ observer: NSObject, keyPath: String, context: inout Int) {
    let result = checkIfAlreadyAdded(keyPath: keyPath, context: &context)

    if result {
        removeObserver(observer, forKeyPath: keyPath, context: &context)
    }
}

fileprivate func address(_ o: UnsafeRawPointer) -> Int {
    return Int(bitPattern: o)
}

fileprivate func checkIfAlreadyAdded(keyPath: String, context: inout Int) -> Bool {

    guard self.observationInfo != nil else { return false }

    let info = Unmanaged<AnyObject>
           .fromOpaque(self.observationInfo!)
           .takeUnretainedValue()

    let contextStr = NSString(format: "%p", address(&context))
    let infoStr = info.description ?? ""

    let regex = NSRegularExpression("\(keyPath).*[a-z].*\(contextStr)")
    let result = regex.matches(infoStr)

    return result
  }
}
0
isHidden