KVOTests.swift 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. ////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright 2015 Realm Inc.
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License");
  6. // you may not use this file except in compliance with the License.
  7. // You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. //
  17. ////////////////////////////////////////////////////////////////////////////
  18. import XCTest
  19. import RealmSwift
  20. var pkCounter = 0
  21. func nextPrimaryKey() -> Int {
  22. pkCounter += 1
  23. return pkCounter
  24. }
  25. class SwiftKVOObject: Object {
  26. @objc dynamic var pk = nextPrimaryKey() // primary key for equality
  27. @objc dynamic var ignored: Int = 0
  28. @objc dynamic var boolCol: Bool = false
  29. @objc dynamic var int8Col: Int8 = 1
  30. @objc dynamic var int16Col: Int16 = 2
  31. @objc dynamic var int32Col: Int32 = 3
  32. @objc dynamic var int64Col: Int64 = 4
  33. @objc dynamic var floatCol: Float = 5
  34. @objc dynamic var doubleCol: Double = 6
  35. @objc dynamic var stringCol: String = ""
  36. @objc dynamic var binaryCol: Data = Data()
  37. @objc dynamic var dateCol: Date = Date(timeIntervalSince1970: 0)
  38. @objc dynamic var objectCol: SwiftKVOObject?
  39. let arrayCol = List<SwiftKVOObject>()
  40. let optIntCol = RealmOptional<Int>()
  41. let optFloatCol = RealmOptional<Float>()
  42. let optDoubleCol = RealmOptional<Double>()
  43. let optBoolCol = RealmOptional<Bool>()
  44. @objc dynamic var optStringCol: String?
  45. @objc dynamic var optBinaryCol: Data?
  46. @objc dynamic var optDateCol: Date?
  47. let arrayBool = List<Bool>()
  48. let arrayInt8 = List<Int8>()
  49. let arrayInt16 = List<Int16>()
  50. let arrayInt32 = List<Int32>()
  51. let arrayInt64 = List<Int64>()
  52. let arrayFloat = List<Float>()
  53. let arrayDouble = List<Double>()
  54. let arrayString = List<String>()
  55. let arrayBinary = List<Data>()
  56. let arrayDate = List<Date>()
  57. let arrayOptBool = List<Bool?>()
  58. let arrayOptInt8 = List<Int8?>()
  59. let arrayOptInt16 = List<Int16?>()
  60. let arrayOptInt32 = List<Int32?>()
  61. let arrayOptInt64 = List<Int64?>()
  62. let arrayOptFloat = List<Float?>()
  63. let arrayOptDouble = List<Double?>()
  64. let arrayOptString = List<String?>()
  65. let arrayOptBinary = List<Data?>()
  66. let arrayOptDate = List<Date?>()
  67. override class func primaryKey() -> String { return "pk" }
  68. override class func ignoredProperties() -> [String] { return ["ignored"] }
  69. }
  70. // Most of the testing of KVO functionality is done in the obj-c tests
  71. // These tests just verify that it also works on Swift types
  72. class KVOTests: TestCase {
  73. var realm: Realm! = nil
  74. override func setUp() {
  75. super.setUp()
  76. realm = try! Realm()
  77. realm.beginWrite()
  78. }
  79. override func tearDown() {
  80. realm.cancelWrite()
  81. realm = nil
  82. super.tearDown()
  83. }
  84. var changeDictionary: [NSKeyValueChangeKey: Any]?
  85. override func observeValue(forKeyPath keyPath: String?, of object: Any?,
  86. change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
  87. changeDictionary = change
  88. }
  89. // swiftlint:disable:next cyclomatic_complexity
  90. func observeChange<T: Equatable>(_ obj: SwiftKVOObject, _ key: String, _ old: T?, _ new: T?,
  91. fileName: StaticString = #file, lineNumber: UInt = #line, _ block: () -> Void) {
  92. let kvoOptions: NSKeyValueObservingOptions = [.old, .new]
  93. obj.addObserver(self, forKeyPath: key, options: kvoOptions, context: nil)
  94. block()
  95. obj.removeObserver(self, forKeyPath: key)
  96. XCTAssert(changeDictionary != nil, "Did not get a notification", file: fileName, line: lineNumber)
  97. guard changeDictionary != nil else { return }
  98. let actualOld = changeDictionary![.oldKey]! as? T
  99. let actualNew = changeDictionary![.newKey]! as? T
  100. XCTAssert(old == actualOld,
  101. "Old value: expected \(String(describing: old)), got \(String(describing: actualOld))",
  102. file: fileName, line: lineNumber)
  103. XCTAssert(new == actualNew,
  104. "New value: expected \(String(describing: new)), got \(String(describing: actualNew))",
  105. file: fileName, line: lineNumber)
  106. changeDictionary = nil
  107. }
  108. func observeChange<T: Equatable>(_ obj: SwiftKVOObject, _ keyPath: KeyPath<SwiftKVOObject, T>, _ old: Any?, _ new: Any?,
  109. fileName: StaticString = #file, lineNumber: UInt = #line, _ block: () -> Void) {
  110. let kvoOptions: NSKeyValueObservingOptions = [.old, .new]
  111. var gotNotification = false
  112. let observation = obj.observe(keyPath, options: kvoOptions) { _, change in
  113. if let old = old {
  114. XCTAssertEqual(change.oldValue, (old as! T), file: fileName, line: lineNumber)
  115. } else {
  116. XCTAssertNil(change.oldValue, file: fileName, line: lineNumber)
  117. }
  118. if let new = new {
  119. XCTAssertEqual(change.newValue, (new as! T), file: fileName, line: lineNumber)
  120. } else {
  121. XCTAssertNil(change.newValue, file: fileName, line: lineNumber)
  122. }
  123. gotNotification = true
  124. }
  125. block()
  126. observation.invalidate()
  127. XCTAssertTrue(gotNotification, file: fileName, line: lineNumber)
  128. }
  129. func observeListChange(_ obj: NSObject, _ key: String, _ kind: NSKeyValueChange, _ indexes: NSIndexSet = NSIndexSet(index: 0),
  130. fileName: StaticString = #file, lineNumber: UInt = #line, _ block: () -> Void) {
  131. obj.addObserver(self, forKeyPath: key, options: [.old, .new], context: nil)
  132. block()
  133. obj.removeObserver(self, forKeyPath: key)
  134. XCTAssert(changeDictionary != nil, "Did not get a notification", file: fileName, line: lineNumber)
  135. guard changeDictionary != nil else { return }
  136. let actualKind = NSKeyValueChange(rawValue: (changeDictionary![NSKeyValueChangeKey.kindKey] as! NSNumber).uintValue)!
  137. let actualIndexes = changeDictionary![NSKeyValueChangeKey.indexesKey]! as! NSIndexSet
  138. XCTAssert(actualKind == kind, "Change kind: expected \(kind), got \(actualKind)", file: fileName,
  139. line: lineNumber)
  140. XCTAssert(actualIndexes.isEqual(indexes), "Changed indexes: expected \(indexes), got \(actualIndexes)",
  141. file: fileName, line: lineNumber)
  142. changeDictionary = nil
  143. }
  144. func getObject(_ obj: SwiftKVOObject) -> (SwiftKVOObject, SwiftKVOObject) {
  145. return (obj, obj)
  146. }
  147. // Actual tests follow
  148. func testAllPropertyTypes() {
  149. let (obj, obs) = getObject(SwiftKVOObject())
  150. observeChange(obs, "boolCol", false, true) { obj.boolCol = true }
  151. observeChange(obs, "int8Col", 1 as Int8, 10) { obj.int8Col = 10 }
  152. observeChange(obs, "int16Col", 2 as Int16, 10) { obj.int16Col = 10 }
  153. observeChange(obs, "int32Col", 3 as Int32, 10) { obj.int32Col = 10 }
  154. observeChange(obs, "int64Col", 4 as Int64, 10) { obj.int64Col = 10 }
  155. observeChange(obs, "floatCol", 5 as Float, 10) { obj.floatCol = 10 }
  156. observeChange(obs, "doubleCol", 6 as Double, 10) { obj.doubleCol = 10 }
  157. observeChange(obs, "stringCol", "", "abc") { obj.stringCol = "abc" }
  158. observeChange(obs, "objectCol", nil, obj) { obj.objectCol = obj }
  159. let data = "abc".data(using: String.Encoding.utf8, allowLossyConversion: false)!
  160. observeChange(obs, "binaryCol", Data(), data) { obj.binaryCol = data }
  161. let date = Date(timeIntervalSince1970: 1)
  162. observeChange(obs, "dateCol", Date(timeIntervalSince1970: 0), date) { obj.dateCol = date }
  163. observeListChange(obs, "arrayCol", .insertion) { obj.arrayCol.append(obj) }
  164. observeListChange(obs, "arrayCol", .removal) { obj.arrayCol.removeAll() }
  165. observeChange(obs, "optIntCol", nil, 10) { obj.optIntCol.value = 10 }
  166. observeChange(obs, "optFloatCol", nil, 10.0) { obj.optFloatCol.value = 10 }
  167. observeChange(obs, "optDoubleCol", nil, 10.0) { obj.optDoubleCol.value = 10 }
  168. observeChange(obs, "optBoolCol", nil, true) { obj.optBoolCol.value = true }
  169. observeChange(obs, "optStringCol", nil, "abc") { obj.optStringCol = "abc" }
  170. observeChange(obs, "optBinaryCol", nil, data) { obj.optBinaryCol = data }
  171. observeChange(obs, "optDateCol", nil, date) { obj.optDateCol = date }
  172. observeChange(obs, "optIntCol", 10, nil) { obj.optIntCol.value = nil }
  173. observeChange(obs, "optFloatCol", 10.0, nil) { obj.optFloatCol.value = nil }
  174. observeChange(obs, "optDoubleCol", 10.0, nil) { obj.optDoubleCol.value = nil }
  175. observeChange(obs, "optBoolCol", true, nil) { obj.optBoolCol.value = nil }
  176. observeChange(obs, "optStringCol", "abc", nil) { obj.optStringCol = nil }
  177. observeChange(obs, "optBinaryCol", data, nil) { obj.optBinaryCol = nil }
  178. observeChange(obs, "optDateCol", date, nil) { obj.optDateCol = nil }
  179. observeListChange(obs, "arrayBool", .insertion) { obj.arrayBool.append(true); }
  180. observeListChange(obs, "arrayInt8", .insertion) { obj.arrayInt8.append(10); }
  181. observeListChange(obs, "arrayInt16", .insertion) { obj.arrayInt16.append(10); }
  182. observeListChange(obs, "arrayInt32", .insertion) { obj.arrayInt32.append(10); }
  183. observeListChange(obs, "arrayInt64", .insertion) { obj.arrayInt64.append(10); }
  184. observeListChange(obs, "arrayFloat", .insertion) { obj.arrayFloat.append(10); }
  185. observeListChange(obs, "arrayDouble", .insertion) { obj.arrayDouble.append(10); }
  186. observeListChange(obs, "arrayString", .insertion) { obj.arrayString.append("abc"); }
  187. observeListChange(obs, "arrayOptBool", .insertion) { obj.arrayOptBool.append(true); }
  188. observeListChange(obs, "arrayOptInt8", .insertion) { obj.arrayOptInt8.append(10); }
  189. observeListChange(obs, "arrayOptInt16", .insertion) { obj.arrayOptInt16.append(10); }
  190. observeListChange(obs, "arrayOptInt32", .insertion) { obj.arrayOptInt32.append(10); }
  191. observeListChange(obs, "arrayOptInt64", .insertion) { obj.arrayOptInt64.append(10); }
  192. observeListChange(obs, "arrayOptFloat", .insertion) { obj.arrayOptFloat.append(10); }
  193. observeListChange(obs, "arrayOptDouble", .insertion) { obj.arrayOptDouble.append(10); }
  194. observeListChange(obs, "arrayOptString", .insertion) { obj.arrayOptString.append("abc"); }
  195. observeListChange(obs, "arrayOptBinary", .insertion) { obj.arrayOptBinary.append(data); }
  196. observeListChange(obs, "arrayOptDate", .insertion) { obj.arrayOptDate.append(date); }
  197. observeListChange(obs, "arrayOptBool", .insertion) { obj.arrayOptBool.insert(nil, at: 0); }
  198. observeListChange(obs, "arrayOptInt8", .insertion) { obj.arrayOptInt8.insert(nil, at: 0); }
  199. observeListChange(obs, "arrayOptInt16", .insertion) { obj.arrayOptInt16.insert(nil, at: 0); }
  200. observeListChange(obs, "arrayOptInt32", .insertion) { obj.arrayOptInt32.insert(nil, at: 0); }
  201. observeListChange(obs, "arrayOptInt64", .insertion) { obj.arrayOptInt64.insert(nil, at: 0); }
  202. observeListChange(obs, "arrayOptFloat", .insertion) { obj.arrayOptFloat.insert(nil, at: 0); }
  203. observeListChange(obs, "arrayOptDouble", .insertion) { obj.arrayOptDouble.insert(nil, at: 0); }
  204. observeListChange(obs, "arrayOptString", .insertion) { obj.arrayOptString.insert(nil, at: 0); }
  205. observeListChange(obs, "arrayOptDate", .insertion) { obj.arrayOptDate.insert(nil, at: 0); }
  206. observeListChange(obs, "arrayOptBinary", .insertion) { obj.arrayOptBinary.insert(nil, at: 0); }
  207. if obs.realm == nil {
  208. return
  209. }
  210. observeChange(obs, "invalidated", false, true) {
  211. self.realm.delete(obj)
  212. }
  213. let (obj2, obs2) = getObject(SwiftKVOObject())
  214. observeChange(obs2, "arrayCol.invalidated", false, true) {
  215. self.realm.delete(obj2)
  216. }
  217. }
  218. func testTypedObservation() {
  219. let (obj, obs) = getObject(SwiftKVOObject())
  220. observeChange(obs, \.boolCol, false, true) { obj.boolCol = true }
  221. observeChange(obs, \.int8Col, 1 as Int8, 10 as Int8) { obj.int8Col = 10 }
  222. observeChange(obs, \.int16Col, 2 as Int16, 10 as Int16) { obj.int16Col = 10 }
  223. observeChange(obs, \.int32Col, 3 as Int32, 10 as Int32) { obj.int32Col = 10 }
  224. observeChange(obs, \.int64Col, 4 as Int64, 10 as Int64) { obj.int64Col = 10 }
  225. observeChange(obs, \.floatCol, 5 as Float, 10 as Float) { obj.floatCol = 10 }
  226. observeChange(obs, \.doubleCol, 6 as Double, 10 as Double) { obj.doubleCol = 10 }
  227. observeChange(obs, \.stringCol, "", "abc") { obj.stringCol = "abc" }
  228. let data = "abc".data(using: String.Encoding.utf8, allowLossyConversion: false)!
  229. observeChange(obs, \.binaryCol, Data(), data) { obj.binaryCol = data }
  230. let date = Date(timeIntervalSince1970: 1)
  231. observeChange(obs, \.dateCol, Date(timeIntervalSince1970: 0), date) { obj.dateCol = date }
  232. #if swift(>=3.4) && (swift(>=4.1.50) || !swift(>=4))
  233. observeChange(obs, \.objectCol, nil, obj) { obj.objectCol = obj }
  234. observeChange(obs, \.optStringCol, nil, "abc") { obj.optStringCol = "abc" }
  235. observeChange(obs, \.optBinaryCol, nil, data) { obj.optBinaryCol = data }
  236. observeChange(obs, \.optDateCol, nil, date) { obj.optDateCol = date }
  237. observeChange(obs, \.optStringCol, "abc", nil) { obj.optStringCol = nil }
  238. observeChange(obs, \.optBinaryCol, data, nil) { obj.optBinaryCol = nil }
  239. observeChange(obs, \.optDateCol, date, nil) { obj.optDateCol = nil }
  240. #endif
  241. if obs.realm == nil {
  242. return
  243. }
  244. // FIXME: crashes xcode11b1 compiler even when disabled with #if
  245. // FB6115674
  246. /*
  247. observeChange(obs, \.isInvalidated, false, true) {
  248. self.realm.delete(obj)
  249. }
  250. */
  251. }
  252. func testReadSharedSchemaFromObservedObject() {
  253. let obj = SwiftKVOObject()
  254. obj.addObserver(self, forKeyPath: "boolCol", options: [.old, .new], context: nil)
  255. XCTAssertEqual(type(of: obj).sharedSchema(), SwiftKVOObject.sharedSchema())
  256. obj.removeObserver(self, forKeyPath: "boolCol")
  257. }
  258. }
  259. class KVOPersistedTests: KVOTests {
  260. override func getObject(_ obj: SwiftKVOObject) -> (SwiftKVOObject, SwiftKVOObject) {
  261. realm.add(obj)
  262. return (obj, obj)
  263. }
  264. }
  265. class KVOMultipleAccessorsTests: KVOTests {
  266. override func getObject(_ obj: SwiftKVOObject) -> (SwiftKVOObject, SwiftKVOObject) {
  267. realm.add(obj)
  268. return (obj, realm.object(ofType: SwiftKVOObject.self, forPrimaryKey: obj.pk)!)
  269. }
  270. }