1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
|
// RUN: %empty-directory(%t)
// RUN: %target-build-swift %s -o %t/a.out
// RUN: %target-codesign %t/a.out
// RUN: %target-run %t/a.out | grep 'check-prefix' > %t/prefix-option
// RUN: %target-run %t/a.out | %FileCheck -check-prefix=CHECK `cat %t/prefix-option` %s
// REQUIRES: executable_test
// REQUIRES: objc_interop
// FIXME: https://github.com/apple/swift/issues/52252
// Disable because it blocks PR testing.
// UNSUPPORTED: CPU=i386
import Foundation
struct Guts {
var internalValue = 42
var value: Int {
get {
return internalValue
}
}
init(value: Int) {
internalValue = value
}
init() {
}
}
class Target : NSObject, NSKeyValueObservingCustomization {
// This dynamic property is observed by KVO
@objc dynamic var objcValue: String
@objc dynamic var objcValue2: String {
willSet {
willChangeValue(for: \.objcValue2)
}
didSet {
didChangeValue(for: \.objcValue2)
}
}
@objc dynamic var objcValue3: String
// This Swift-typed property causes vtable usage on this class.
var swiftValue: Guts
override init() {
self.swiftValue = Guts()
self.objcValue = ""
self.objcValue2 = ""
self.objcValue3 = ""
super.init()
}
static func keyPathsAffectingValue(for key: AnyKeyPath) -> Set<AnyKeyPath> {
if (key == \Target.objcValue) {
return [\Target.objcValue2, \Target.objcValue3]
} else {
return []
}
}
static func automaticallyNotifiesObservers(for key: AnyKeyPath) -> Bool {
if key == \Target.objcValue2 || key == \Target.objcValue3 {
return false
}
return true
}
func print() {
Swift.print("swiftValue \(self.swiftValue.value), objcValue \(objcValue)")
}
}
class ObserverKVO : NSObject {
var target: Target?
var observation: NSKeyValueObservation? = nil
override init() { target = nil; super.init() }
func observeTarget(_ target: Target) {
self.target = target
observation = target.observe(\.objcValue) { (object, change) in
Swift.print("swiftValue \(object.swiftValue.value), objcValue \(object.objcValue)")
}
}
func removeTarget() {
observation!.invalidate()
}
}
var t2 = Target()
var o2 = ObserverKVO()
print("unobserved 2")
t2.objcValue = "one"
t2.objcValue = "two"
print("registering observer 2")
o2.observeTarget(t2)
print("Now witness the firepower of this fully armed and operational panopticon!")
t2.objcValue = "three"
t2.objcValue = "four"
t2.swiftValue = Guts(value: 13)
t2.objcValue2 = "six" //should fire
t2.objcValue3 = "nothing" //should not fire
o2.removeTarget()
t2.objcValue = "five" //make sure that we don't crash or keep posting changes if you deallocate an observation after invalidating it
print("target removed")
// CHECK: registering observer 2
// CHECK-NEXT: Now witness the firepower of this fully armed and operational panopticon!
// CHECK-NEXT: swiftValue 42, objcValue three
// CHECK-NEXT: swiftValue 42, objcValue four
// CHECK-NEXT: swiftValue 13, objcValue four
// The next 2 logs are actually a bug and shouldn't happen
// CHECK-NEXT: swiftValue 13, objcValue four
// CHECK-NEXT: swiftValue 13, objcValue four
// CHECK-NEXT: target removed
//===----------------------------------------------------------------------===//
// Test NSKeyValueObservingCustomization issue with observing from the callbacks
//===----------------------------------------------------------------------===//
// The following tests are only expected to pass when running with the
// Swift 5.1 and later libraries.
if #available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) {
print("-check-prefix=CHECK-51")
} else {
print("-check-prefix=DONT-CHECK")
// Need at least one check, otherwise FileCheck will complain.
// DONT-CHECK: {{.}}
}
class Target2 : NSObject, NSKeyValueObservingCustomization {
@objc dynamic var name: String?
class Dummy : NSObject {
@objc dynamic var name: String?
}
// In both of the callbacks, observe another property with the same key path.
// We do it in both because we're not sure which callback is invoked first.
// This ensures that using KVO with key paths from one callback doesn't interfere
// with the ability to look up the key path using the other.
static func keyPathsAffectingValue(for key: AnyKeyPath) -> Set<AnyKeyPath> {
print("keyPathsAffectingValue: key == \\.name:", key == \Target2.name)
withExtendedLifetime(Dummy()) { (dummy) in
_ = dummy.observe(\.name) { (_, _) in }
}
return []
}
static func automaticallyNotifiesObservers(for key: AnyKeyPath) -> Bool {
print("automaticallyNotifiesObservers: key == \\.name:", key == \Target2.name)
withExtendedLifetime(Dummy()) { (dummy) in
_ = dummy.observe(\.name) { (_, _) in }
}
return true
}
}
print("registering observer for Target2")
withExtendedLifetime(Target2()) { (target) in
_ = target.observe(\.name) { (_, _) in }
}
print("observer removed")
// CHECK-51-LABEL: registering observer for Target2
// CHECK-51-DAG: keyPathsAffectingValue: key == \.name: true
// CHECK-51-DAG: automaticallyNotifiesObservers: key == \.name: true
// CHECK-51-NEXT: observer removed
//===----------------------------------------------------------------------===//
// Test NSSortDescriptor keyPath support
//===----------------------------------------------------------------------===//
// This one doesn't really match the context of "KVO KeyPaths" but it's close enough
class Sortable1 : NSObject {
@objc var name: String?
}
class Sortable2 : NSObject {
@objc var name: String?
}
print("creating NSSortDescriptor")
let descriptor = NSSortDescriptor(keyPath: \Sortable1.name, ascending: true)
_ = NSSortDescriptor(keyPath: \Sortable2.name, ascending: true)
print("keyPath == \\Sortable1.name:", descriptor.keyPath == \Sortable1.name)
// CHECK-51-LABEL: creating NSSortDescriptor
// CHECK-51-NEXT: keyPath == \Sortable1.name: true
//===----------------------------------------------------------------------===//
// Test keyPath with optional value has correct oldValue/newValue behavior
//===----------------------------------------------------------------------===//
class TestClassForOptionalKeyPath : NSObject {
// Should not use NSObject? as object type
@objc dynamic var optionalObject: String?
}
let testObjectForOptionalKeyPath = TestClassForOptionalKeyPath()
print("observe keyPath with optional value")
let optionalKeyPathObserver = testObjectForOptionalKeyPath.observe(\.optionalObject, options: [.initial, .old, .new]) { (_, change) in
Swift.print("oldValue = \(change.oldValue as String??), newValue = \(change.newValue as String??)")
}
testObjectForOptionalKeyPath.optionalObject = nil
testObjectForOptionalKeyPath.optionalObject = "foo"
// CHECK-51-LABEL: observe keyPath with optional value
// CHECK-51-NEXT: oldValue = {{Optional\(nil\)|nil}}, newValue = {{Optional\(nil\)|nil}}
// CHECK-51-NEXT: oldValue = {{Optional\(nil\)|nil}}, newValue = {{Optional\(nil\)|nil}}
// CHECK-51-NEXT: oldValue = {{Optional\(nil\)|nil}}, newValue = Optional(Optional("foo"))
|