Skip to content

[SR-6066] observe NSKeyValueObservedChange oldValue and newValue double optionals incorrectly nil when observing an optional property #3802

@bobergj

Description

@bobergj
Previous ID SR-6066
Radar rdar://problem/39297127
Original Reporter @bobergj
Type Bug
Status Reopened
Resolution

Attachment: Download

Environment

Apple Swift version 5.1.3 (swiftlang-1100.0.282.1 clang-1100.0.33.15)

Additional Detail from JIRA
Votes 1
Component/s Foundation
Labels Bug
Assignee None
Priority Medium

md5: 71e8a3bdae50c0f3c476808e7f3096c9

Issue Description:

Test case:

import Foundation

print("Check what the print output of a double optional is..")
let optionalString: Optional<String> = nil
let optionalOptionalString: Optional<Optional<String>> = optionalString
print(optionalOptionalString)
print("OK, now to the actual test")


class A : NSObject {
    @objc dynamic var anOptionalString: String? = nil
}

func testObserve() {
    
    let a = A()
    let keyPath = \A.anOptionalString
    a.observe(keyPath, options: [.old, .new]) { (objectChanged, observedChange) in
        print("oldValue: \(observedChange.oldValue)")
        print("newValue: \(observedChange.newValue)")

    }
    
    a.anOptionalString = nil
    a.anOptionalString = "abc"
    a.anOptionalString = "def"
}


testObserve()

Output:

Check what the print output of a double optional is..
Optional(nil)
OK, now to the actual test
oldValue: nil
newValue: nil
oldValue: nil
newValue: Optional(Optional("abc"))
oldValue: Optional(Optional("abc"))
newValue: Optional(Optional("def"))

Expected output:

Check what the print output of a double optional is..
Optional(nil)
OK, now to the actual test
oldValue: Optional(nil)
newValue: Optional(nil)
oldValue: Optional(nil)
newValue: Optional(Optional("abc"))
oldValue: Optional(Optional("abc"))
newValue: Optional(Optional("def"))

In https://github.com/apple/swift/blob/master/stdlib/public/SDK/Foundation/NSObject.swift

public struct NSKeyValueObservedChange<Value> {
  ///newValue and oldValue will only be non-nil if .new/.old is passed to   `observe()`. In general, get the most up to date value by accessing it directly on the observed object instead.
  public let newValue: Value?
  public let oldValue: Value?
  ...
}

Since we are passing the options:

[.old, .new]

above, the expectation is that newValue and oldValue aren't nil.

I got bit by this when using observe in a generic context and using a guard statement to bind newValue and oldValue in the beginning of the observe callback closure.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions