KVOTests.mm 65 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007
  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 "RLMTestCase.h"
  19. #import "RLMObjectSchema_Private.hpp"
  20. #import "RLMObjectStore.h"
  21. #import "RLMObject_Private.hpp"
  22. #import "RLMRealmConfiguration_Private.hpp"
  23. #import "RLMRealm_Private.hpp"
  24. #import "RLMSchema_Private.h"
  25. #import "shared_realm.hpp"
  26. #import <realm/group.hpp>
  27. #import <atomic>
  28. #import <memory>
  29. #import <objc/runtime.h>
  30. #import <vector>
  31. RLM_ARRAY_TYPE(KVOObject)
  32. RLM_ARRAY_TYPE(KVOLinkObject1)
  33. @interface KVOObject : RLMObject
  34. @property int pk; // Primary key for isEqual:
  35. @property int ignored;
  36. @property BOOL boolCol;
  37. @property int16_t int16Col;
  38. @property int32_t int32Col;
  39. @property int64_t int64Col;
  40. @property float floatCol;
  41. @property double doubleCol;
  42. @property bool cBoolCol;
  43. @property NSString *stringCol;
  44. @property NSData *binaryCol;
  45. @property NSDate *dateCol;
  46. @property KVOObject *objectCol;
  47. @property RLMArray<RLMBool> *boolArray;
  48. @property RLMArray<RLMInt> *intArray;
  49. @property RLMArray<RLMFloat> *floatArray;
  50. @property RLMArray<RLMDouble> *doubleArray;
  51. @property RLMArray<RLMString> *stringArray;
  52. @property RLMArray<RLMData> *dataArray;
  53. @property RLMArray<RLMDate> *dateArray;
  54. @property RLMArray<KVOObject> *objectArray;
  55. @property NSNumber<RLMInt> *optIntCol;
  56. @property NSNumber<RLMFloat> *optFloatCol;
  57. @property NSNumber<RLMDouble> *optDoubleCol;
  58. @property NSNumber<RLMBool> *optBoolCol;
  59. @end
  60. @implementation KVOObject
  61. + (NSString *)primaryKey {
  62. return @"pk";
  63. }
  64. + (NSArray *)ignoredProperties {
  65. return @[@"ignored"];
  66. }
  67. @end
  68. @interface KVOLinkObject1 : RLMObject
  69. @property int pk; // Primary key for isEqual:
  70. @property KVOObject *obj;
  71. @property RLMArray<KVOObject> *array;
  72. @end
  73. @implementation KVOLinkObject1
  74. + (NSString *)primaryKey {
  75. return @"pk";
  76. }
  77. @end
  78. @interface KVOLinkObject2 : RLMObject
  79. @property int pk; // Primary key for isEqual:
  80. @property KVOLinkObject1 *obj;
  81. @property RLMArray<KVOLinkObject1> *array;
  82. @end
  83. @implementation KVOLinkObject2
  84. + (NSString *)primaryKey {
  85. return @"pk";
  86. }
  87. @end
  88. @interface PlainKVOObject : NSObject
  89. @property int ignored;
  90. @property BOOL boolCol;
  91. @property int16_t int16Col;
  92. @property int32_t int32Col;
  93. @property int64_t int64Col;
  94. @property float floatCol;
  95. @property double doubleCol;
  96. @property bool cBoolCol;
  97. @property NSString *stringCol;
  98. @property NSData *binaryCol;
  99. @property NSDate *dateCol;
  100. @property PlainKVOObject *objectCol;
  101. @property NSMutableArray *boolArray;
  102. @property NSMutableArray *intArray;
  103. @property NSMutableArray *floatArray;
  104. @property NSMutableArray *doubleArray;
  105. @property NSMutableArray *stringArray;
  106. @property NSMutableArray *dataArray;
  107. @property NSMutableArray *dateArray;
  108. @property NSMutableArray *objectArray;
  109. @property NSNumber<RLMInt> *optIntCol;
  110. @property NSNumber<RLMFloat> *optFloatCol;
  111. @property NSNumber<RLMDouble> *optDoubleCol;
  112. @property NSNumber<RLMBool> *optBoolCol;
  113. @end
  114. @implementation PlainKVOObject
  115. @end
  116. @interface PlainLinkObject1 : NSObject
  117. @property PlainKVOObject *obj;
  118. @property NSMutableArray *array;
  119. @end
  120. @implementation PlainLinkObject1
  121. @end
  122. @interface PlainLinkObject2 : NSObject
  123. @property PlainLinkObject1 *obj;
  124. @property NSMutableArray *array;
  125. @end
  126. @implementation PlainLinkObject2
  127. @end
  128. // Tables with no links (or backlinks) preserve the order of rows on
  129. // insertion/deletion, while tables with links do not, so we need an object
  130. // class known to have no links to test the ordered case
  131. @interface ObjectWithNoLinksToOrFrom : RLMObject
  132. @property int value;
  133. @end
  134. @implementation ObjectWithNoLinksToOrFrom
  135. @end
  136. // An object which removes a KVO registration when it's deallocated, for use
  137. // as an associated object
  138. @interface KVOUnregisterHelper : NSObject
  139. @end
  140. @implementation KVOUnregisterHelper {
  141. __unsafe_unretained id _obj;
  142. __unsafe_unretained id _observer;
  143. NSString *_keyPath;
  144. }
  145. + (void)automaticallyUnregister:(id)observer object:(id)obj keyPath:(NSString *)keyPath {
  146. KVOUnregisterHelper *helper = [self new];
  147. helper->_observer = observer;
  148. helper->_obj = obj;
  149. helper->_keyPath = keyPath;
  150. objc_setAssociatedObject(obj, (__bridge void *)helper, helper, OBJC_ASSOCIATION_RETAIN);
  151. }
  152. - (void)dealloc {
  153. [_obj removeObserver:_observer forKeyPath:_keyPath];
  154. }
  155. @end
  156. // A KVO observer which retains the given object until it observes a change
  157. @interface ReleaseOnObservation : NSObject
  158. @property (strong) id object;
  159. @end
  160. @implementation ReleaseOnObservation
  161. - (void)observeValueForKeyPath:(NSString *)keyPath
  162. ofObject:(id)object
  163. change:(__unused NSDictionary *)change
  164. context:(void *)context
  165. {
  166. [object removeObserver:self forKeyPath:keyPath context:context];
  167. _object = nil;
  168. }
  169. @end
  170. @interface KVOTests : RLMTestCase
  171. // get an object that should be observed for the given object being mutated
  172. // used by some of the subclasses to observe a different accessor for the same row
  173. - (id)observableForObject:(id)obj;
  174. @end
  175. // subscribes to kvo notifications on the passed object on creation, records
  176. // all change notifications sent and makes them available in `notifications`,
  177. // and automatically unsubscribes on destruction
  178. class KVORecorder {
  179. id _observer;
  180. id _obj;
  181. NSString *_keyPath;
  182. RLMRealm *_mutationRealm;
  183. RLMRealm *_observationRealm;
  184. NSMutableArray *_notifications;
  185. public:
  186. // construct a new recorder for the given `keyPath` on `obj`, using `observer`
  187. // as the NSObject helper to actually add as an observer
  188. KVORecorder(id observer, id obj, NSString *keyPath,
  189. int options = NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew)
  190. : _observer(observer)
  191. , _obj([observer observableForObject:obj])
  192. , _keyPath(keyPath)
  193. , _mutationRealm([obj respondsToSelector:@selector(realm)] ? (RLMRealm *)[obj realm] : nil)
  194. , _observationRealm([_obj respondsToSelector:@selector(realm)] ? (RLMRealm *)[_obj realm] : nil)
  195. , _notifications([NSMutableArray new])
  196. {
  197. [_obj addObserver:observer forKeyPath:keyPath options:options context:this];
  198. }
  199. ~KVORecorder() {
  200. id self = _observer;
  201. @try {
  202. [_obj removeObserver:_observer forKeyPath:_keyPath context:this];
  203. }
  204. @catch (NSException *e) {
  205. XCTFail(@"%@", e.description);
  206. }
  207. XCTAssertEqual(0U, _notifications.count);
  208. static_cast<void>(self); // Pre-12 versions of Xcode require the self variable but 12 doesn't use it
  209. }
  210. // record a single notification
  211. void operator()(NSString *key, id obj, NSDictionary *changeDictionary) {
  212. id self = _observer;
  213. XCTAssertEqual(obj, _obj);
  214. XCTAssertEqualObjects(key, _keyPath);
  215. [_notifications addObject:changeDictionary.copy];
  216. static_cast<void>(self); // Pre-12 versions of Xcode require the self variable but 12 doesn't use it
  217. }
  218. // ensure that the observed object is updated for any changes made to the
  219. // object being mutated if they are different
  220. void refresh() {
  221. if (_mutationRealm != _observationRealm) {
  222. [_mutationRealm commitWriteTransaction];
  223. [_observationRealm refresh];
  224. [_mutationRealm beginWriteTransaction];
  225. }
  226. }
  227. NSDictionary *pop_front() {
  228. NSDictionary *value = [_notifications firstObject];
  229. if (value) {
  230. [_notifications removeObjectAtIndex:0U];
  231. }
  232. return value;
  233. }
  234. NSUInteger size() const {
  235. return _notifications.count;
  236. }
  237. bool empty() const {
  238. return _notifications.count == 0;
  239. }
  240. };
  241. // Assert that `recorder` has a notification at `index` and return it if so
  242. #define AssertNotification(recorder) ([&]{ \
  243. (recorder).refresh(); \
  244. NSDictionary *value = recorder.pop_front(); \
  245. XCTAssertNotNil(value, @"Did not get a notification when expected"); \
  246. return value; \
  247. })()
  248. // Validate that `recorder` has at least one notification, and that the first
  249. // notification is the expected one
  250. #define AssertChanged(recorder, from, to) do { \
  251. if (NSDictionary *note = AssertNotification((recorder))) { \
  252. XCTAssertEqualObjects(@(NSKeyValueChangeSetting), note[NSKeyValueChangeKindKey]); \
  253. XCTAssertEqualObjects((from), note[NSKeyValueChangeOldKey]); \
  254. XCTAssertEqualObjects((to), note[NSKeyValueChangeNewKey]); \
  255. } \
  256. else { \
  257. return; \
  258. } \
  259. } while (false)
  260. // Validate that `r` has a notification with the given kind and changed indexes,
  261. // remove it, and verify that there are no more notifications
  262. #define AssertIndexChange(kind, indexes) do { \
  263. if (NSDictionary *note = AssertNotification(r)) { \
  264. XCTAssertEqual([note[NSKeyValueChangeKindKey] intValue], static_cast<int>(kind)); \
  265. XCTAssertEqualObjects(note[NSKeyValueChangeIndexesKey], indexes); \
  266. } \
  267. XCTAssertTrue(r.empty()); \
  268. } while (0)
  269. // Tests for plain Foundation key-value observing to verify that we correctly
  270. // match the standard semantics. Each of the subclasses of KVOTests runs the
  271. // same set of tests on RLMObjects in difference scenarios
  272. @implementation KVOTests
  273. // forward a KVO notification to the KVORecorder stored in the context
  274. - (void)observeValueForKeyPath:(NSString *)keyPath
  275. ofObject:(id)object
  276. change:(NSDictionary *)change
  277. context:(void *)context
  278. {
  279. (*static_cast<KVORecorder *>(context))(keyPath, object, change);
  280. }
  281. // overridden in the multiple accessors, one realm and multiple realms cases
  282. - (id)observableForObject:(id)obj {
  283. return obj;
  284. }
  285. // overridden in the multiple realms case because `-refresh` does not send
  286. // notifications for intermediate states
  287. - (bool)collapsesNotifications {
  288. return false;
  289. }
  290. // overridden in all subclases to return the appropriate object
  291. // base class runs the tests on a plain NSObject using stock KVO to ensure that
  292. // the tests are actually covering the correct behavior, since there's a great
  293. // deal that the documentation doesn't specify
  294. - (id)createObject {
  295. PlainKVOObject *obj = [PlainKVOObject new];
  296. obj.int16Col = 1;
  297. obj.int32Col = 2;
  298. obj.int64Col = 3;
  299. obj.binaryCol = NSData.data;
  300. obj.stringCol = @"";
  301. obj.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:0];
  302. obj.boolArray = [NSMutableArray array];
  303. obj.intArray = [NSMutableArray array];
  304. obj.floatArray = [NSMutableArray array];
  305. obj.doubleArray = [NSMutableArray array];
  306. obj.stringArray = [NSMutableArray array];
  307. obj.dataArray = [NSMutableArray array];
  308. obj.dateArray = [NSMutableArray array];
  309. obj.objectArray = [NSMutableArray array];
  310. return obj;
  311. }
  312. - (id)createLinkObject {
  313. PlainLinkObject1 *obj1 = [PlainLinkObject1 new];
  314. obj1.obj = [self createObject];
  315. obj1.array = [NSMutableArray new];
  316. PlainLinkObject2 *obj2 = [PlainLinkObject2 new];
  317. obj2.obj = obj1;
  318. obj2.array = [NSMutableArray new];
  319. return obj2;
  320. }
  321. // actual tests follow
  322. - (void)testRegisterForUnknownProperty {
  323. KVOObject *obj = [self createObject];
  324. XCTAssertNoThrow([obj addObserver:self forKeyPath:@"non-existent" options:0 context:nullptr]);
  325. XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"non-existent"]);
  326. XCTAssertNoThrow([obj addObserver:self forKeyPath:@"non-existent" options:NSKeyValueObservingOptionOld context:nullptr]);
  327. XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"non-existent"]);
  328. XCTAssertNoThrow([obj addObserver:self forKeyPath:@"non-existent" options:NSKeyValueObservingOptionPrior context:nullptr]);
  329. XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"non-existent"]);
  330. }
  331. - (void)testRemoveObserver {
  332. KVOObject *obj = [self createObject];
  333. XCTAssertThrowsSpecificNamed([obj removeObserver:self forKeyPath:@"int32Col"], NSException, NSRangeException);
  334. XCTAssertThrowsSpecificNamed([obj removeObserver:self forKeyPath:@"int32Col" context:nullptr], NSException, NSRangeException);
  335. XCTAssertNoThrow([obj addObserver:self forKeyPath:@"int32Col" options:0 context:nullptr]);
  336. XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"int32Col"]);
  337. XCTAssertThrowsSpecificNamed([obj removeObserver:self forKeyPath:@"int32Col"], NSException, NSRangeException);
  338. // `context` parameter must match if it's passed, but the overload that doesn't
  339. // take one will unregister any context
  340. void *context1 = (void *)1;
  341. void *context2 = (void *)2;
  342. XCTAssertNoThrow([obj addObserver:self forKeyPath:@"int32Col" options:0 context:context1]);
  343. XCTAssertThrows([obj removeObserver:self forKeyPath:@"int32Col" context:context2]);
  344. XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"int32Col" context:context1]);
  345. XCTAssertNoThrow([obj addObserver:self forKeyPath:@"int32Col" options:0 context:context2]);
  346. XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"int32Col" context:context2]);
  347. XCTAssertNoThrow([obj addObserver:self forKeyPath:@"int32Col" options:0 context:context2]);
  348. XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"int32Col"]);
  349. XCTAssertThrows([obj removeObserver:self forKeyPath:@"int32Col"]);
  350. XCTAssertNoThrow([obj addObserver:self forKeyPath:@"int32Col" options:0 context:context1]);
  351. XCTAssertNoThrow([obj addObserver:self forKeyPath:@"int32Col" options:0 context:context2]);
  352. XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"int32Col" context:context1]);
  353. XCTAssertThrows([obj removeObserver:self forKeyPath:@"int32Col" context:context1]);
  354. XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"int32Col" context:context2]);
  355. XCTAssertThrows([obj removeObserver:self forKeyPath:@"int32Col" context:context2]);
  356. XCTAssertNoThrow([obj addObserver:self forKeyPath:@"int32Col" options:0 context:context1]);
  357. XCTAssertNoThrow([obj addObserver:self forKeyPath:@"int32Col" options:0 context:context2]);
  358. XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"int32Col" context:context2]);
  359. XCTAssertThrows([obj removeObserver:self forKeyPath:@"int32Col" context:context2]);
  360. XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"int32Col" context:context1]);
  361. XCTAssertThrows([obj removeObserver:self forKeyPath:@"int32Col" context:context1]);
  362. // no context version should only unregister one (unspecified) observer
  363. XCTAssertNoThrow([obj addObserver:self forKeyPath:@"int32Col" options:0 context:context1]);
  364. XCTAssertNoThrow([obj addObserver:self forKeyPath:@"int32Col" options:0 context:context2]);
  365. XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"int32Col"]);
  366. XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"int32Col"]);
  367. XCTAssertThrows([obj removeObserver:self forKeyPath:@"int32Col"]);
  368. }
  369. - (void)testRemoveObserverInObservation {
  370. auto helper = [ReleaseOnObservation new];
  371. __unsafe_unretained id obj;
  372. __weak id weakObj;
  373. @autoreleasepool {
  374. obj = weakObj = helper.object = [self createObject];
  375. [obj addObserver:helper forKeyPath:@"int32Col" options:NSKeyValueObservingOptionOld context:nullptr];
  376. }
  377. [obj setInt32Col:0];
  378. XCTAssertNil(helper.object);
  379. XCTAssertNil(weakObj);
  380. }
  381. - (void)testSimple {
  382. KVOObject *obj = [self createObject];
  383. {
  384. KVORecorder r(self, obj, @"int32Col");
  385. obj.int32Col = 10;
  386. AssertChanged(r, @2, @10);
  387. }
  388. {
  389. KVORecorder r(self, obj, @"int32Col");
  390. obj.int32Col = 1;
  391. AssertChanged(r, @10, @1);
  392. }
  393. }
  394. - (void)testSelfAssignmentNotifies {
  395. KVOObject *obj = [self createObject];
  396. {
  397. KVORecorder r(self, obj, @"int32Col");
  398. obj.int32Col = obj.int32Col;
  399. AssertChanged(r, @2, @2);
  400. }
  401. }
  402. - (void)testMultipleObserversAreNotified {
  403. KVOObject *obj = [self createObject];
  404. {
  405. KVORecorder r1(self, obj, @"int32Col");
  406. KVORecorder r2(self, obj, @"int32Col");
  407. KVORecorder r3(self, obj, @"int32Col");
  408. obj.int32Col = 10;
  409. AssertChanged(r1, @2, @10);
  410. AssertChanged(r2, @2, @10);
  411. AssertChanged(r3, @2, @10);
  412. }
  413. }
  414. - (void)testOnlyObserversForTheCorrectPropertyAreNotified {
  415. KVOObject *obj = [self createObject];
  416. {
  417. KVORecorder r16(self, obj, @"int16Col");
  418. KVORecorder r32(self, obj, @"int32Col");
  419. KVORecorder r64(self, obj, @"int64Col");
  420. obj.int16Col = 2;
  421. AssertChanged(r16, @1, @2);
  422. XCTAssertTrue(r16.empty());
  423. XCTAssertTrue(r32.empty());
  424. XCTAssertTrue(r64.empty());
  425. obj.int32Col = 2;
  426. AssertChanged(r32, @2, @2);
  427. XCTAssertTrue(r16.empty());
  428. XCTAssertTrue(r32.empty());
  429. XCTAssertTrue(r64.empty());
  430. obj.int64Col = 2;
  431. AssertChanged(r64, @3, @2);
  432. XCTAssertTrue(r16.empty());
  433. XCTAssertTrue(r32.empty());
  434. XCTAssertTrue(r64.empty());
  435. }
  436. }
  437. - (void)testMultipleChangesWithSingleObserver {
  438. KVOObject *obj = [self createObject];
  439. KVORecorder r(self, obj, @"int32Col");
  440. obj.int32Col = 1;
  441. obj.int32Col = 2;
  442. obj.int32Col = 3;
  443. obj.int32Col = 3;
  444. if (self.collapsesNotifications) {
  445. AssertChanged(r, @2, @3);
  446. }
  447. else {
  448. AssertChanged(r, @2, @1);
  449. AssertChanged(r, @1, @2);
  450. AssertChanged(r, @2, @3);
  451. AssertChanged(r, @3, @3);
  452. }
  453. }
  454. - (void)testOnlyObserversForTheCorrectObjectAreNotified {
  455. KVOObject *obj1 = [self createObject];
  456. KVOObject *obj2 = [self createObject];
  457. KVORecorder r1(self, obj1, @"int32Col");
  458. KVORecorder r2(self, obj2, @"int32Col");
  459. obj1.int32Col = 10;
  460. AssertChanged(r1, @2, @10);
  461. XCTAssertEqual(0U, r2.size());
  462. obj2.int32Col = 5;
  463. AssertChanged(r2, @2, @5);
  464. }
  465. - (void)testOptionsInitial {
  466. KVOObject *obj = [self createObject];
  467. {
  468. KVORecorder r(self, obj, @"int32Col", 0);
  469. XCTAssertEqual(0U, r.size());
  470. }
  471. {
  472. KVORecorder r(self, obj, @"int32Col", NSKeyValueObservingOptionInitial);
  473. r.pop_front();
  474. }
  475. }
  476. - (void)testOptionsOld {
  477. KVOObject *obj = [self createObject];
  478. {
  479. KVORecorder r(self, obj, @"int32Col", 0);
  480. obj.int32Col = 0;
  481. if (NSDictionary *note = AssertNotification(r)) {
  482. XCTAssertNil(note[NSKeyValueChangeOldKey]);
  483. }
  484. }
  485. {
  486. KVORecorder r(self, obj, @"int32Col", NSKeyValueObservingOptionOld);
  487. obj.int32Col = 0;
  488. if (NSDictionary *note = AssertNotification(r)) {
  489. XCTAssertNotNil(note[NSKeyValueChangeOldKey]);
  490. }
  491. }
  492. }
  493. - (void)testOptionsNew {
  494. KVOObject *obj = [self createObject];
  495. {
  496. KVORecorder r(self, obj, @"int32Col", 0);
  497. obj.int32Col = 0;
  498. if (NSDictionary *note = AssertNotification(r)) {
  499. XCTAssertNil(note[NSKeyValueChangeNewKey]);
  500. }
  501. }
  502. {
  503. KVORecorder r(self, obj, @"int32Col", NSKeyValueObservingOptionNew);
  504. obj.int32Col = 0;
  505. if (NSDictionary *note = AssertNotification(r)) {
  506. XCTAssertNotNil(note[NSKeyValueChangeNewKey]);
  507. }
  508. }
  509. }
  510. - (void)testOptionsPrior {
  511. KVOObject *obj = [self createObject];
  512. KVORecorder r(self, obj, @"int32Col", NSKeyValueObservingOptionNew|NSKeyValueObservingOptionPrior);
  513. obj.int32Col = 0;
  514. r.refresh();
  515. XCTAssertEqual(2U, r.size());
  516. if (NSDictionary *note = AssertNotification(r)) {
  517. XCTAssertNil(note[NSKeyValueChangeNewKey]);
  518. XCTAssertEqualObjects(@YES, note[NSKeyValueChangeNotificationIsPriorKey]);
  519. }
  520. if (NSDictionary *note = AssertNotification(r)) {
  521. XCTAssertNotNil(note[NSKeyValueChangeNewKey]);
  522. XCTAssertNil(note[NSKeyValueChangeNotificationIsPriorKey]);
  523. }
  524. }
  525. - (void)testAllPropertyTypes {
  526. KVOObject *obj = [self createObject];
  527. {
  528. KVORecorder r(self, obj, @"boolCol");
  529. obj.boolCol = YES;
  530. AssertChanged(r, @NO, @YES);
  531. }
  532. {
  533. KVORecorder r(self, obj, @"int16Col");
  534. obj.int16Col = 0;
  535. AssertChanged(r, @1, @0);
  536. }
  537. {
  538. KVORecorder r(self, obj, @"int32Col");
  539. obj.int32Col = 0;
  540. AssertChanged(r, @2, @0);
  541. }
  542. {
  543. KVORecorder r(self, obj, @"int64Col");
  544. obj.int64Col = 0;
  545. AssertChanged(r, @3, @0);
  546. }
  547. {
  548. KVORecorder r(self, obj, @"floatCol");
  549. obj.floatCol = 1.0f;
  550. AssertChanged(r, @0, @1);
  551. }
  552. {
  553. KVORecorder r(self, obj, @"doubleCol");
  554. obj.doubleCol = 1.0;
  555. AssertChanged(r, @0, @1);
  556. }
  557. {
  558. KVORecorder r(self, obj, @"cBoolCol");
  559. obj.cBoolCol = YES;
  560. AssertChanged(r, @NO, @YES);
  561. }
  562. {
  563. KVORecorder r(self, obj, @"stringCol");
  564. obj.stringCol = @"abc";
  565. AssertChanged(r, @"", @"abc");
  566. obj.stringCol = nil;
  567. AssertChanged(r, @"abc", NSNull.null);
  568. }
  569. {
  570. KVORecorder r(self, obj, @"binaryCol");
  571. NSData *data = [@"abc" dataUsingEncoding:NSUTF8StringEncoding];
  572. obj.binaryCol = data;
  573. AssertChanged(r, NSData.data, data);
  574. obj.binaryCol = nil;
  575. AssertChanged(r, data, NSNull.null);
  576. }
  577. {
  578. KVORecorder r(self, obj, @"dateCol");
  579. NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:1];
  580. obj.dateCol = date;
  581. AssertChanged(r, [NSDate dateWithTimeIntervalSinceReferenceDate:0], date);
  582. obj.dateCol = nil;
  583. AssertChanged(r, date, NSNull.null);
  584. }
  585. {
  586. KVORecorder r(self, obj, @"objectCol");
  587. obj.objectCol = obj;
  588. AssertChanged(r, NSNull.null, [self observableForObject:obj]);
  589. obj.objectCol = nil;
  590. AssertChanged(r, [self observableForObject:obj], NSNull.null);
  591. }
  592. {
  593. KVORecorder r(self, obj, @"intArray");
  594. obj.intArray = obj.intArray;
  595. r.refresh();
  596. r.pop_front(); // asserts that there's something to pop
  597. }
  598. {
  599. KVORecorder r(self, obj, @"boolArray");
  600. obj.boolArray = obj.boolArray;
  601. r.refresh();
  602. r.pop_front(); // asserts that there's something to pop
  603. }
  604. {
  605. KVORecorder r(self, obj, @"floatArray");
  606. obj.floatArray = obj.floatArray;
  607. r.refresh();
  608. r.pop_front(); // asserts that there's something to pop
  609. }
  610. {
  611. KVORecorder r(self, obj, @"doubleArray");
  612. obj.doubleArray = obj.doubleArray;
  613. r.refresh();
  614. r.pop_front(); // asserts that there's something to pop
  615. }
  616. {
  617. KVORecorder r(self, obj, @"stringArray");
  618. obj.stringArray = obj.stringArray;
  619. r.refresh();
  620. r.pop_front(); // asserts that there's something to pop
  621. }
  622. {
  623. KVORecorder r(self, obj, @"dataArray");
  624. obj.dataArray = obj.dataArray;
  625. r.refresh();
  626. r.pop_front(); // asserts that there's something to pop
  627. }
  628. {
  629. KVORecorder r(self, obj, @"dateArray");
  630. obj.dateArray = obj.dateArray;
  631. r.refresh();
  632. r.pop_front(); // asserts that there's something to pop
  633. }
  634. {
  635. KVORecorder r(self, obj, @"objectArray");
  636. obj.objectArray = obj.objectArray;
  637. r.refresh();
  638. r.pop_front(); // asserts that there's something to pop
  639. }
  640. {
  641. KVORecorder r(self, obj, @"optIntCol");
  642. obj.optIntCol = @1;
  643. AssertChanged(r, NSNull.null, @1);
  644. obj.optIntCol = nil;
  645. AssertChanged(r, @1, NSNull.null);
  646. }
  647. {
  648. KVORecorder r(self, obj, @"optFloatCol");
  649. obj.optFloatCol = @1.1f;
  650. AssertChanged(r, NSNull.null, @1.1f);
  651. obj.optFloatCol = nil;
  652. AssertChanged(r, @1.1f, NSNull.null);
  653. }
  654. {
  655. KVORecorder r(self, obj, @"optDoubleCol");
  656. obj.optDoubleCol = @1.1;
  657. AssertChanged(r, NSNull.null, @1.1);
  658. obj.optDoubleCol = nil;
  659. AssertChanged(r, @1.1, NSNull.null);
  660. }
  661. {
  662. KVORecorder r(self, obj, @"optBoolCol");
  663. obj.optBoolCol = @YES;
  664. AssertChanged(r, NSNull.null, @YES);
  665. obj.optBoolCol = nil;
  666. AssertChanged(r, @YES, NSNull.null);
  667. }
  668. }
  669. - (void)testAllPropertyTypesKVC {
  670. KVOObject *obj = [self createObject];
  671. {
  672. KVORecorder r(self, obj, @"boolCol");
  673. [obj setValue:@YES forKey:@"boolCol"];
  674. AssertChanged(r, @NO, @YES);
  675. }
  676. {
  677. KVORecorder r(self, obj, @"int16Col");
  678. [obj setValue:@0 forKey:@"int16Col"];
  679. AssertChanged(r, @1, @0);
  680. }
  681. {
  682. KVORecorder r(self, obj, @"int32Col");
  683. [obj setValue:@0 forKey:@"int32Col"];
  684. AssertChanged(r, @2, @0);
  685. }
  686. {
  687. KVORecorder r(self, obj, @"int64Col");
  688. [obj setValue:@0 forKey:@"int64Col"];
  689. AssertChanged(r, @3, @0);
  690. }
  691. {
  692. KVORecorder r(self, obj, @"floatCol");
  693. [obj setValue:@1.0f forKey:@"floatCol"];
  694. AssertChanged(r, @0, @1);
  695. }
  696. {
  697. KVORecorder r(self, obj, @"doubleCol");
  698. [obj setValue:@1.0 forKey:@"doubleCol"];
  699. AssertChanged(r, @0, @1);
  700. }
  701. {
  702. KVORecorder r(self, obj, @"cBoolCol");
  703. [obj setValue:@YES forKey:@"cBoolCol"];
  704. AssertChanged(r, @NO, @YES);
  705. }
  706. {
  707. KVORecorder r(self, obj, @"stringCol");
  708. [obj setValue:@"abc" forKey:@"stringCol"];
  709. AssertChanged(r, @"", @"abc");
  710. [obj setValue:nil forKey:@"stringCol"];
  711. AssertChanged(r, @"abc", NSNull.null);
  712. }
  713. {
  714. KVORecorder r(self, obj, @"binaryCol");
  715. NSData *data = [@"abc" dataUsingEncoding:NSUTF8StringEncoding];
  716. [obj setValue:data forKey:@"binaryCol"];
  717. AssertChanged(r, NSData.data, data);
  718. [obj setValue:nil forKey:@"binaryCol"];
  719. AssertChanged(r, data, NSNull.null);
  720. }
  721. {
  722. KVORecorder r(self, obj, @"dateCol");
  723. NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:1];
  724. [obj setValue:date forKey:@"dateCol"];
  725. AssertChanged(r, [NSDate dateWithTimeIntervalSinceReferenceDate:0], date);
  726. [obj setValue:nil forKey:@"dateCol"];
  727. AssertChanged(r, date, NSNull.null);
  728. }
  729. {
  730. KVORecorder r(self, obj, @"objectCol");
  731. [obj setValue:obj forKey:@"objectCol"];
  732. AssertChanged(r, NSNull.null, [self observableForObject:obj]);
  733. [obj setValue:nil forKey:@"objectCol"];
  734. AssertChanged(r, [self observableForObject:obj], NSNull.null);
  735. }
  736. {
  737. KVORecorder r(self, obj, @"objectArray");
  738. [obj setValue:obj.objectArray forKey:@"objectArray"];
  739. r.refresh();
  740. r.pop_front(); // asserts that there's something to pop
  741. }
  742. {
  743. KVORecorder r(self, obj, @"optIntCol");
  744. [obj setValue:@1 forKey:@"optIntCol"];
  745. AssertChanged(r, NSNull.null, @1);
  746. [obj setValue:nil forKey:@"optIntCol"];
  747. AssertChanged(r, @1, NSNull.null);
  748. }
  749. {
  750. KVORecorder r(self, obj, @"optFloatCol");
  751. [obj setValue:@1.1f forKey:@"optFloatCol"];
  752. AssertChanged(r, NSNull.null, @1.1f);
  753. [obj setValue:nil forKey:@"optFloatCol"];
  754. AssertChanged(r, @1.1f, NSNull.null);
  755. }
  756. {
  757. KVORecorder r(self, obj, @"optDoubleCol");
  758. [obj setValue:@1.1 forKey:@"optDoubleCol"];
  759. AssertChanged(r, NSNull.null, @1.1);
  760. [obj setValue:nil forKey:@"optDoubleCol"];
  761. AssertChanged(r, @1.1, NSNull.null);
  762. }
  763. {
  764. KVORecorder r(self, obj, @"optBoolCol");
  765. [obj setValue:@YES forKey:@"optBoolCol"];
  766. AssertChanged(r, NSNull.null, @YES);
  767. [obj setValue:nil forKey:@"optBoolCol"];
  768. AssertChanged(r, @YES, NSNull.null);
  769. }
  770. }
  771. - (void)testAllPropertyTypesDynamic {
  772. KVOObject *obj = [self createObject];
  773. if (![obj respondsToSelector:@selector(setObject:forKeyedSubscript:)]) {
  774. return;
  775. }
  776. {
  777. KVORecorder r(self, obj, @"boolCol");
  778. obj[@"boolCol"] = @YES;
  779. AssertChanged(r, @NO, @YES);
  780. }
  781. {
  782. KVORecorder r(self, obj, @"int16Col");
  783. obj[@"int16Col"] = @0;
  784. AssertChanged(r, @1, @0);
  785. }
  786. {
  787. KVORecorder r(self, obj, @"int32Col");
  788. obj[@"int32Col"] = @0;
  789. AssertChanged(r, @2, @0);
  790. }
  791. {
  792. KVORecorder r(self, obj, @"int64Col");
  793. obj[@"int64Col"] = @0;
  794. AssertChanged(r, @3, @0);
  795. }
  796. {
  797. KVORecorder r(self, obj, @"floatCol");
  798. obj[@"floatCol"] = @1.0f;
  799. AssertChanged(r, @0, @1);
  800. }
  801. {
  802. KVORecorder r(self, obj, @"doubleCol");
  803. obj[@"doubleCol"] = @1.0;
  804. AssertChanged(r, @0, @1);
  805. }
  806. {
  807. KVORecorder r(self, obj, @"cBoolCol");
  808. obj[@"cBoolCol"] = @YES;
  809. AssertChanged(r, @NO, @YES);
  810. }
  811. {
  812. KVORecorder r(self, obj, @"stringCol");
  813. obj[@"stringCol"] = @"abc";
  814. AssertChanged(r, @"", @"abc");
  815. obj[@"stringCol"] = nil;
  816. AssertChanged(r, @"abc", NSNull.null);
  817. }
  818. {
  819. KVORecorder r(self, obj, @"binaryCol");
  820. NSData *data = [@"abc" dataUsingEncoding:NSUTF8StringEncoding];
  821. obj[@"binaryCol"] = data;
  822. AssertChanged(r, NSData.data, data);
  823. obj[@"binaryCol"] = nil;
  824. AssertChanged(r, data, NSNull.null);
  825. }
  826. {
  827. KVORecorder r(self, obj, @"dateCol");
  828. NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:1];
  829. obj[@"dateCol"] = date;
  830. AssertChanged(r, [NSDate dateWithTimeIntervalSinceReferenceDate:0], date);
  831. obj[@"dateCol"] = nil;
  832. AssertChanged(r, date, NSNull.null);
  833. }
  834. {
  835. KVORecorder r(self, obj, @"objectCol");
  836. obj[@"objectCol"] = obj;
  837. AssertChanged(r, NSNull.null, [self observableForObject:obj]);
  838. obj[@"objectCol"] = nil;
  839. AssertChanged(r, [self observableForObject:obj], NSNull.null);
  840. }
  841. {
  842. KVORecorder r(self, obj, @"objectArray");
  843. obj[@"objectArray"] = obj.objectArray;
  844. r.refresh();
  845. r.pop_front(); // asserts that there's something to pop
  846. }
  847. {
  848. KVORecorder r(self, obj, @"optIntCol");
  849. obj[@"optIntCol"] = @1;
  850. AssertChanged(r, NSNull.null, @1);
  851. obj[@"optIntCol"] = nil;
  852. AssertChanged(r, @1, NSNull.null);
  853. }
  854. {
  855. KVORecorder r(self, obj, @"optFloatCol");
  856. obj[@"optFloatCol"] = @1.1f;
  857. AssertChanged(r, NSNull.null, @1.1f);
  858. obj[@"optFloatCol"] = nil;
  859. AssertChanged(r, @1.1f, NSNull.null);
  860. }
  861. {
  862. KVORecorder r(self, obj, @"optDoubleCol");
  863. obj[@"optDoubleCol"] = @1.1;
  864. AssertChanged(r, NSNull.null, @1.1);
  865. obj[@"optDoubleCol"] = nil;
  866. AssertChanged(r, @1.1, NSNull.null);
  867. }
  868. {
  869. KVORecorder r(self, obj, @"optBoolCol");
  870. obj[@"optBoolCol"] = @YES;
  871. AssertChanged(r, NSNull.null, @YES);
  872. obj[@"optBoolCol"] = nil;
  873. AssertChanged(r, @YES, NSNull.null);
  874. }
  875. }
  876. - (void)testArrayDiffs {
  877. KVOLinkObject2 *obj = [self createLinkObject];
  878. KVORecorder r(self, obj, @"array");
  879. id mutator = [obj mutableArrayValueForKey:@"array"];
  880. [mutator addObject:obj.obj];
  881. AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndex:0]);
  882. [mutator addObject:obj.obj];
  883. AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndex:1]);
  884. [mutator removeObjectAtIndex:0];
  885. AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndex:0]);
  886. [mutator replaceObjectAtIndex:0 withObject:obj.obj];
  887. AssertIndexChange(NSKeyValueChangeReplacement, [NSIndexSet indexSetWithIndex:0]);
  888. NSMutableIndexSet *indexes = [NSMutableIndexSet new];
  889. [indexes addIndex:0];
  890. [indexes addIndex:2];
  891. [mutator insertObjects:@[obj.obj, obj.obj] atIndexes:indexes];
  892. AssertIndexChange(NSKeyValueChangeInsertion, indexes);
  893. [mutator removeObjectsAtIndexes:indexes];
  894. AssertIndexChange(NSKeyValueChangeRemoval, indexes);
  895. if (![obj.array isKindOfClass:[NSArray class]]) {
  896. // We deliberately diverge from NSMutableArray for `removeAllObjects` and
  897. // `addObjectsFromArray:`, because generating a separate notification for
  898. // each object added or removed is needlessly pessimal.
  899. [mutator addObjectsFromArray:@[obj.obj, obj.obj]];
  900. AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, 2)]);
  901. // NSArray sends multiple notifications for exchange, which we can't do
  902. // on refresh
  903. [mutator exchangeObjectAtIndex:0 withObjectAtIndex:1];
  904. AssertIndexChange(NSKeyValueChangeReplacement, [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)]);
  905. // NSArray doesn't have move
  906. [mutator moveObjectAtIndex:1 toIndex:0];
  907. AssertIndexChange(NSKeyValueChangeReplacement, [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)]);
  908. [mutator removeLastObject];
  909. AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndex:2]);
  910. [mutator removeAllObjects];
  911. AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)]);
  912. }
  913. }
  914. - (void)testPrimitiveArrayDiffs {
  915. KVOObject *obj = [self createObject];
  916. KVORecorder r(self, obj, @"intArray");
  917. id mutator = [obj mutableArrayValueForKey:@"intArray"];
  918. [mutator addObject:@1];
  919. AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndex:0]);
  920. [mutator addObject:@2];
  921. AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndex:1]);
  922. [mutator removeObjectAtIndex:0];
  923. AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndex:0]);
  924. [mutator replaceObjectAtIndex:0 withObject:@3];
  925. AssertIndexChange(NSKeyValueChangeReplacement, [NSIndexSet indexSetWithIndex:0]);
  926. NSMutableIndexSet *indexes = [NSMutableIndexSet new];
  927. [indexes addIndex:0];
  928. [indexes addIndex:2];
  929. [mutator insertObjects:@[@4, @5] atIndexes:indexes];
  930. AssertIndexChange(NSKeyValueChangeInsertion, indexes);
  931. [mutator removeObjectsAtIndexes:indexes];
  932. AssertIndexChange(NSKeyValueChangeRemoval, indexes);
  933. if (![obj.intArray isKindOfClass:[NSArray class]]) {
  934. // We deliberately diverge from NSMutableArray for `removeAllObjects` and
  935. // `addObjectsFromArray:`, because generating a separate notification for
  936. // each object added or removed is needlessly pessimal.
  937. [mutator addObjectsFromArray:@[@6, @7]];
  938. AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, 2)]);
  939. // NSArray sends multiple notifications for exchange, which we can't do
  940. // on refresh
  941. [mutator exchangeObjectAtIndex:0 withObjectAtIndex:1];
  942. AssertIndexChange(NSKeyValueChangeReplacement, [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)]);
  943. // NSArray doesn't have move
  944. [mutator moveObjectAtIndex:1 toIndex:0];
  945. AssertIndexChange(NSKeyValueChangeReplacement, [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)]);
  946. [mutator removeLastObject];
  947. AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndex:2]);
  948. [mutator removeAllObjects];
  949. AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)]);
  950. }
  951. }
  952. - (void)testIgnoredProperty {
  953. KVOObject *obj = [self createObject];
  954. KVORecorder r(self, obj, @"ignored");
  955. obj.ignored = 10;
  956. AssertChanged(r, @0, @10);
  957. }
  958. - (void)testChangeEndOfKeyPath {
  959. KVOLinkObject2 *obj = [self createLinkObject];
  960. std::unique_ptr<KVORecorder> r;
  961. @autoreleasepool {
  962. r = std::make_unique<KVORecorder>(self, obj, @"obj.obj.boolCol");
  963. }
  964. obj.obj.obj.boolCol = YES;
  965. AssertChanged(*r, @NO, @YES);
  966. }
  967. - (void)testChangeMiddleOfKeyPath {
  968. KVOLinkObject2 *obj = [self createLinkObject];
  969. KVOObject *oldObj = obj.obj.obj;
  970. KVOObject *newObj = [self createObject];
  971. newObj.boolCol = YES;
  972. KVORecorder r(self, obj, @"obj.obj.boolCol");
  973. obj.obj.obj = newObj;
  974. AssertChanged(r, @NO, @YES);
  975. newObj.boolCol = NO;
  976. AssertChanged(r, @YES, @NO);
  977. oldObj.boolCol = YES;
  978. }
  979. - (void)testNullifyMiddleOfKeyPath {
  980. KVOLinkObject2 *obj = [self createLinkObject];
  981. KVORecorder r(self, obj, @"obj.obj.boolCol");
  982. obj.obj = nil;
  983. AssertChanged(r, @NO, NSNull.null);
  984. }
  985. - (void)testChangeMiddleOfKeyPathToNonNil {
  986. KVOLinkObject2 *obj = [self createLinkObject];
  987. KVOLinkObject1 *obj2 = obj.obj;
  988. obj.obj = nil;
  989. obj2.obj.boolCol = YES;
  990. KVORecorder r(self, obj, @"obj.obj.boolCol");
  991. obj.obj = obj2;
  992. AssertChanged(r, NSNull.null, @YES);
  993. }
  994. - (void)testArrayKVC {
  995. KVOObject *obj = [self createObject];
  996. [obj.objectArray addObject:obj];
  997. KVORecorder r(self, obj, @"boolCol");
  998. [obj.objectArray setValue:@YES forKey:@"boolCol"];
  999. AssertChanged(r, @NO, @YES);
  1000. }
  1001. - (void)testSharedSchemaOnObservedObjectGivesOriginalSchema {
  1002. KVOObject *obj = [self createObject];
  1003. if (![obj isKindOfClass:RLMObjectBase.class]) {
  1004. return;
  1005. }
  1006. RLMObjectSchema *original = [obj.class sharedSchema];
  1007. KVORecorder r(self, obj, @"boolCol");
  1008. XCTAssertEqual(original, [obj.class sharedSchema]); // note: intentionally not EqualObjects
  1009. }
  1010. // RLMArray doesn't support @count at all
  1011. //- (void)testObserveArrayCount {
  1012. // KVOObject *obj = [self createObject];
  1013. // KVORecorder r(self, obj, @"objectArray.@count");
  1014. // id mutator = [obj mutableArrayValueForKey:@"objectArray"];
  1015. // [mutator addObject:obj];
  1016. // AssertChanged(r, @0, @1);
  1017. //}
  1018. @end
  1019. // Run tests on an unmanaged RLMObject instance
  1020. @interface KVOUnmanagedObjectTests : KVOTests
  1021. @end
  1022. @implementation KVOUnmanagedObjectTests
  1023. - (id)createObject {
  1024. static int pk = 0;
  1025. KVOObject *obj = [KVOObject new];
  1026. obj.pk = pk++;
  1027. obj.int16Col = 1;
  1028. obj.int32Col = 2;
  1029. obj.int64Col = 3;
  1030. obj.binaryCol = NSData.data;
  1031. obj.stringCol = @"";
  1032. obj.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:0];
  1033. return obj;
  1034. }
  1035. - (id)createLinkObject {
  1036. static int pk = 0;
  1037. KVOLinkObject1 *obj1 = [KVOLinkObject1 new];
  1038. obj1.pk = pk++;
  1039. obj1.obj = [self createObject];
  1040. KVOLinkObject2 *obj2 = [KVOLinkObject2 new];
  1041. obj2.pk = pk++;
  1042. obj2.obj = obj1;
  1043. return obj2;
  1044. }
  1045. - (void)testAddToRealmAfterAddingObservers {
  1046. RLMRealm *realm = RLMRealm.defaultRealm;
  1047. [realm beginWriteTransaction];
  1048. KVOObject *obj = [self createObject];
  1049. {
  1050. KVORecorder r(self, obj, @"int32Col");
  1051. XCTAssertThrows([realm addObject:obj]);
  1052. }
  1053. XCTAssertNoThrow([realm addObject:obj]);
  1054. [realm cancelWriteTransaction];
  1055. }
  1056. - (void)testObserveInvalidArrayProperty {
  1057. KVOObject *obj = [self createObject];
  1058. XCTAssertThrows([obj.objectArray addObserver:self forKeyPath:@"self" options:0 context:0]);
  1059. XCTAssertNoThrow([obj.objectArray addObserver:self forKeyPath:RLMInvalidatedKey options:0 context:0]);
  1060. XCTAssertNoThrow([obj.objectArray removeObserver:self forKeyPath:RLMInvalidatedKey context:0]);
  1061. }
  1062. - (void)testUnregisteringViaAnAssociatedObject {
  1063. @autoreleasepool {
  1064. __attribute__((objc_precise_lifetime)) KVOObject *obj = [self createObject];
  1065. [obj addObserver:self forKeyPath:@"boolCol" options:0 context:0];
  1066. [KVOUnregisterHelper automaticallyUnregister:self object:obj keyPath:@"boolCol"];
  1067. }
  1068. // Throws if the unregistration doesn't succeed
  1069. }
  1070. @end
  1071. // Run tests on a managed object, modifying the actual object instance being
  1072. // observed
  1073. @interface KVOManagedObjectTests : KVOTests
  1074. @property (nonatomic, strong) RLMRealm *realm;
  1075. @end
  1076. @implementation KVOManagedObjectTests
  1077. - (void)setUp {
  1078. [super setUp];
  1079. _realm = [self getRealm];
  1080. [_realm beginWriteTransaction];
  1081. }
  1082. - (void)tearDown {
  1083. [self.realm cancelWriteTransaction];
  1084. self.realm = nil;
  1085. [super tearDown];
  1086. }
  1087. - (RLMRealm *)getRealm {
  1088. RLMRealmConfiguration *configuration = [[RLMRealmConfiguration alloc] init];
  1089. configuration.inMemoryIdentifier = @"test";
  1090. configuration.schemaMode = realm::SchemaMode::Additive;
  1091. return [RLMRealm realmWithConfiguration:configuration error:nil];
  1092. }
  1093. - (id)createObject {
  1094. static std::atomic<int> pk{0};
  1095. return [KVOObject createInRealm:_realm withValue:@[@(++pk),
  1096. @NO, @1, @2, @3, @0, @0, @NO, @"",
  1097. NSData.data, [NSDate dateWithTimeIntervalSinceReferenceDate:0]]];
  1098. }
  1099. - (id)createLinkObject {
  1100. static std::atomic<int> pk{0};
  1101. return [KVOLinkObject2 createInRealm:_realm withValue:@[@(++pk), @[@(++pk), [self createObject], @[]], @[]]];
  1102. }
  1103. - (void)testDeleteObservedObject {
  1104. KVOObject *obj = [self createObject];
  1105. KVORecorder r1(self, obj, @"boolCol");
  1106. KVORecorder r2(self, obj, RLMInvalidatedKey);
  1107. [self.realm deleteObject:obj];
  1108. AssertChanged(r2, @NO, @YES);
  1109. // should not crash
  1110. }
  1111. - (void)testDeleteMultipleObservedObjects {
  1112. KVOObject *obj1 = [self createObject];
  1113. KVOObject *obj2 = [self createObject];
  1114. KVOObject *obj3 = [self createObject];
  1115. KVORecorder r1(self, obj1, RLMInvalidatedKey);
  1116. KVORecorder r2(self, obj2, RLMInvalidatedKey);
  1117. KVORecorder r3(self, obj3, RLMInvalidatedKey);
  1118. [self.realm deleteObject:obj2];
  1119. AssertChanged(r2, @NO, @YES);
  1120. XCTAssertTrue(r1.empty());
  1121. XCTAssertTrue(r3.empty());
  1122. [self.realm deleteObject:obj3];
  1123. AssertChanged(r3, @NO, @YES);
  1124. XCTAssertTrue(r1.empty());
  1125. XCTAssertTrue(r2.empty());
  1126. [self.realm deleteObject:obj1];
  1127. AssertChanged(r1, @NO, @YES);
  1128. XCTAssertTrue(r2.empty());
  1129. XCTAssertTrue(r3.empty());
  1130. }
  1131. - (void)testDeleteMiddleOfKeyPath {
  1132. KVOLinkObject2 *obj = [self createLinkObject];
  1133. KVORecorder r(self, obj, @"obj.obj.boolCol");
  1134. [self.realm deleteObject:obj.obj];
  1135. AssertChanged(r, @NO, NSNull.null);
  1136. }
  1137. - (void)testDeleteParentOfObservedRLMArray {
  1138. KVOObject *obj = [self createObject];
  1139. KVORecorder r1(self, obj, @"objectArray");
  1140. KVORecorder r2(self, obj, @"objectArray.invalidated");
  1141. KVORecorder r3(self, obj.objectArray, RLMInvalidatedKey);
  1142. [self.realm deleteObject:obj];
  1143. AssertChanged(r2, @NO, @YES);
  1144. AssertChanged(r3, @NO, @YES);
  1145. }
  1146. - (void)testDeleteAllObjects {
  1147. KVOObject *obj = [self createObject];
  1148. KVORecorder r1(self, obj, @"boolCol");
  1149. KVORecorder r2(self, obj, RLMInvalidatedKey);
  1150. [self.realm deleteAllObjects];
  1151. AssertChanged(r2, @NO, @YES);
  1152. // should not crash
  1153. }
  1154. - (void)testClearTable {
  1155. KVOObject *obj = [self createObject];
  1156. KVORecorder r1(self, obj, @"boolCol");
  1157. KVORecorder r2(self, obj, RLMInvalidatedKey);
  1158. [self.realm deleteObjects:[KVOObject allObjectsInRealm:self.realm]];
  1159. AssertChanged(r2, @NO, @YES);
  1160. // should not crash
  1161. }
  1162. - (void)testClearQuery {
  1163. KVOObject *obj = [self createObject];
  1164. KVORecorder r1(self, obj, @"boolCol");
  1165. KVORecorder r2(self, obj, RLMInvalidatedKey);
  1166. [self.realm deleteObjects:[KVOObject objectsInRealm:self.realm where:@"TRUEPREDICATE"]];
  1167. AssertChanged(r2, @NO, @YES);
  1168. // should not crash
  1169. }
  1170. - (void)testClearLinkView {
  1171. KVOObject *obj = [self createObject];
  1172. KVOObject *obj2 = [self createObject];
  1173. [obj2.objectArray addObject:obj];
  1174. KVORecorder r1(self, obj, @"boolCol");
  1175. KVORecorder r2(self, obj, RLMInvalidatedKey);
  1176. [self.realm deleteObjects:obj2.objectArray];
  1177. AssertChanged(r2, @NO, @YES);
  1178. // should not crash
  1179. }
  1180. - (void)testCreateObserverAfterDealloc {
  1181. @autoreleasepool {
  1182. KVOObject *obj = [self createObject];
  1183. KVORecorder r(self, obj, @"boolCol");
  1184. obj.boolCol = YES;
  1185. AssertChanged(r, @NO, @YES);
  1186. }
  1187. @autoreleasepool {
  1188. KVOObject *obj = [self createObject];
  1189. KVORecorder r(self, obj, @"boolCol");
  1190. obj.boolCol = YES;
  1191. AssertChanged(r, @NO, @YES);
  1192. }
  1193. }
  1194. - (void)testDirectlyDeleteLinkedToObject {
  1195. KVOLinkObject2 *obj = [self createLinkObject];
  1196. KVOLinkObject1 *linked = obj.obj;
  1197. KVORecorder r(self, obj, @"obj");
  1198. KVORecorder r2(self, obj, @"obj.invalidated");
  1199. [self.realm deleteObject:linked];
  1200. if (NSDictionary *note = AssertNotification(r)) {
  1201. XCTAssertTrue([note[NSKeyValueChangeOldKey] isKindOfClass:[RLMObjectBase class]]);
  1202. XCTAssertEqualObjects(note[NSKeyValueChangeNewKey], NSNull.null);
  1203. }
  1204. AssertChanged(r2, @NO, NSNull.null);
  1205. }
  1206. - (void)testDeleteLinkedToObjectViaTableClear {
  1207. KVOLinkObject2 *obj = [self createLinkObject];
  1208. KVORecorder r(self, obj, @"obj");
  1209. KVORecorder r2(self, obj, @"obj.invalidated");
  1210. [self.realm deleteObjects:[KVOLinkObject1 allObjectsInRealm:self.realm]];
  1211. if (NSDictionary *note = AssertNotification(r)) {
  1212. XCTAssertTrue([note[NSKeyValueChangeOldKey] isKindOfClass:[RLMObjectBase class]]);
  1213. XCTAssertEqualObjects(note[NSKeyValueChangeNewKey], NSNull.null);
  1214. }
  1215. AssertChanged(r2, @NO, NSNull.null);
  1216. }
  1217. - (void)testDeleteLinkedToObjectViaQueryClear {
  1218. KVOLinkObject2 *obj = [self createLinkObject];
  1219. KVORecorder r(self, obj, @"obj");
  1220. KVORecorder r2(self, obj, @"obj.invalidated");
  1221. [self.realm deleteObjects:[KVOLinkObject1 objectsInRealm:self.realm where:@"TRUEPREDICATE"]];
  1222. if (NSDictionary *note = AssertNotification(r)) {
  1223. XCTAssertTrue([note[NSKeyValueChangeOldKey] isKindOfClass:[RLMObjectBase class]]);
  1224. XCTAssertEqualObjects(note[NSKeyValueChangeNewKey], NSNull.null);
  1225. }
  1226. AssertChanged(r2, @NO, NSNull.null);
  1227. }
  1228. - (void)testDeleteObjectInArray {
  1229. KVOLinkObject2 *obj = [self createLinkObject];
  1230. KVOLinkObject1 *linked = obj.obj;
  1231. [obj.array addObject:linked];
  1232. KVORecorder r(self, obj, @"array");
  1233. [self.realm deleteObject:linked];
  1234. AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndex:0]);
  1235. }
  1236. - (void)testDeleteObjectsInArrayViaTableClear {
  1237. KVOLinkObject2 *obj = [self createLinkObject];
  1238. KVOLinkObject2 *obj2 = [self createLinkObject];
  1239. [obj.array addObject:obj.obj];
  1240. [obj.array addObject:obj.obj];
  1241. [obj.array addObject:obj2.obj];
  1242. KVORecorder r(self, obj, @"array");
  1243. [self.realm deleteObjects:[KVOLinkObject1 allObjectsInRealm:self.realm]];
  1244. AssertIndexChange(NSKeyValueChangeRemoval, ([NSIndexSet indexSetWithIndexesInRange:{0, 3}]));
  1245. }
  1246. - (void)testDeleteObjectsInArrayViaTableViewClear {
  1247. KVOLinkObject2 *obj = [self createLinkObject];
  1248. KVOLinkObject2 *obj2 = [self createLinkObject];
  1249. [obj.array addObject:obj2.obj];
  1250. [obj.array addObject:obj.obj];
  1251. [obj.array addObject:obj.obj];
  1252. KVORecorder r(self, obj, @"array");
  1253. RLMResults *results = [KVOLinkObject1 objectsInRealm:self.realm where:@"TRUEPREDICATE"];
  1254. [results lastObject];
  1255. [self.realm deleteObjects:results];
  1256. AssertIndexChange(NSKeyValueChangeRemoval, ([NSIndexSet indexSetWithIndexesInRange:{0, 3}]));
  1257. }
  1258. - (void)testDeleteObjectsInArrayViaQueryClear {
  1259. KVOLinkObject2 *obj = [self createLinkObject];
  1260. KVOLinkObject2 *obj2 = [self createLinkObject];
  1261. [obj.array addObject:obj.obj];
  1262. [obj.array addObject:obj.obj];
  1263. [obj.array addObject:obj2.obj];
  1264. KVORecorder r(self, obj, @"array");
  1265. [self.realm deleteObjects:[KVOLinkObject1 objectsInRealm:self.realm where:@"TRUEPREDICATE"]];
  1266. AssertIndexChange(NSKeyValueChangeRemoval, ([NSIndexSet indexSetWithIndexesInRange:{0, 3}]));
  1267. }
  1268. - (void)testObserveInvalidArrayProperty {
  1269. KVOObject *obj = [self createObject];
  1270. RLMArray *array = obj.objectArray;
  1271. XCTAssertThrows([array addObserver:self forKeyPath:@"self" options:0 context:0]);
  1272. XCTAssertNoThrow([array addObserver:self forKeyPath:RLMInvalidatedKey options:0 context:0]);
  1273. XCTAssertNoThrow([array removeObserver:self forKeyPath:RLMInvalidatedKey context:0]);
  1274. }
  1275. - (void)testInvalidOperationOnObservedArray {
  1276. KVOLinkObject2 *obj = [self createLinkObject];
  1277. KVOLinkObject1 *linked = obj.obj;
  1278. [obj.array addObject:linked];
  1279. KVORecorder r(self, obj, @"array");
  1280. XCTAssertThrows([obj.array exchangeObjectAtIndex:2 withObjectAtIndex:3]);
  1281. // A KVO notification is still sent to observers on the same thread since we
  1282. // can't cancel willChange, but the data is not very meaningful so don't check it
  1283. if (!self.collapsesNotifications) {
  1284. AssertNotification(r);
  1285. }
  1286. }
  1287. @end
  1288. // Mutate a different accessor backed by the same row as the accessor being observed
  1289. @interface KVOMultipleAccessorsTests : KVOManagedObjectTests
  1290. @end
  1291. @implementation KVOMultipleAccessorsTests
  1292. - (id)observableForObject:(id)value {
  1293. if (RLMObject *obj = RLMDynamicCast<RLMObject>(value)) {
  1294. RLMObject *copy = RLMCreateManagedAccessor(obj.objectSchema.accessorClass, obj->_info);
  1295. copy->_row = obj->_row;
  1296. return copy;
  1297. }
  1298. else if (RLMArray *array = RLMDynamicCast<RLMArray>(value)) {
  1299. return array;
  1300. }
  1301. else {
  1302. XCTFail(@"unsupported type");
  1303. return nil;
  1304. }
  1305. }
  1306. - (void)testIgnoredProperty {
  1307. // ignored properties do not notify other accessors for the same row
  1308. }
  1309. - (void)testAddOrUpdate {
  1310. KVOObject *obj = [self createObject];
  1311. KVOObject *obj2 = [[KVOObject alloc] initWithValue:obj];
  1312. KVORecorder r(self, obj, @"boolCol");
  1313. obj2.boolCol = true;
  1314. XCTAssertTrue(r.empty());
  1315. [self.realm addOrUpdateObject:obj2];
  1316. AssertChanged(r, @NO, @YES);
  1317. }
  1318. - (void)testCreateOrUpdate {
  1319. KVOObject *obj = [self createObject];
  1320. KVOObject *obj2 = [[KVOObject alloc] initWithValue:obj];
  1321. KVORecorder r(self, obj, @"boolCol");
  1322. obj2.boolCol = true;
  1323. XCTAssertTrue(r.empty());
  1324. [KVOObject createOrUpdateInRealm:self.realm withValue:obj2];
  1325. AssertChanged(r, @NO, @YES);
  1326. }
  1327. // The following tests aren't really multiple-accessor-specific, but they're
  1328. // conceptually similar and don't make sense in the multiple realm instances case
  1329. - (void)testCancelWriteTransactionWhileObservingNewObject {
  1330. KVOObject *obj = [self createObject];
  1331. KVORecorder r(self, obj, RLMInvalidatedKey);
  1332. KVORecorder r2(self, obj, @"boolCol");
  1333. [self.realm cancelWriteTransaction];
  1334. AssertChanged(r, @NO, @YES);
  1335. r2.pop_front();
  1336. [self.realm beginWriteTransaction];
  1337. }
  1338. - (void)testCancelWriteTransactionWhileObservingChangedProperty {
  1339. KVOObject *obj = [self createObject];
  1340. [self.realm commitWriteTransaction];
  1341. [self.realm beginWriteTransaction];
  1342. obj.boolCol = YES;
  1343. KVORecorder r(self, obj, @"boolCol");
  1344. [self.realm cancelWriteTransaction];
  1345. AssertChanged(r, @YES, @NO);
  1346. [self.realm beginWriteTransaction];
  1347. }
  1348. - (void)testCancelWriteTransactionWhileObservingLinkToExistingObject {
  1349. KVOObject *obj = [self createObject];
  1350. KVOObject *obj2 = [self createObject];
  1351. [self.realm commitWriteTransaction];
  1352. [self.realm beginWriteTransaction];
  1353. obj.objectCol = obj2;
  1354. KVORecorder r(self, obj, @"objectCol");
  1355. [self.realm cancelWriteTransaction];
  1356. AssertChanged(r, obj2, NSNull.null);
  1357. [self.realm beginWriteTransaction];
  1358. }
  1359. - (void)testCancelWriteTransactionWhileObservingLinkToNewObject {
  1360. KVOObject *obj = [self createObject];
  1361. [self.realm commitWriteTransaction];
  1362. [self.realm beginWriteTransaction];
  1363. obj.objectCol = [self createObject];
  1364. KVORecorder r(self, obj, @"objectCol");
  1365. [self.realm cancelWriteTransaction];
  1366. if (NSDictionary *note = AssertNotification(r)) {
  1367. XCTAssertTrue([note[NSKeyValueChangeOldKey] isKindOfClass:[RLMObjectBase class]]);
  1368. XCTAssertEqualObjects(note[NSKeyValueChangeNewKey], NSNull.null);
  1369. }
  1370. [self.realm beginWriteTransaction];
  1371. }
  1372. - (void)testCancelWriteTransactionWhileObservingNewObjectLinkingToNewObject {
  1373. KVOObject *obj = [self createObject];
  1374. obj.objectCol = [self createObject];
  1375. KVORecorder r(self, obj, RLMInvalidatedKey);
  1376. KVORecorder r2(self, obj, @"objectCol");
  1377. KVORecorder r3(self, obj, @"objectCol.boolCol");
  1378. [self.realm cancelWriteTransaction];
  1379. AssertChanged(r, @NO, @YES);
  1380. [self.realm beginWriteTransaction];
  1381. }
  1382. - (void)testCancelWriteWithArrayChanges {
  1383. KVOObject *obj = [self createObject];
  1384. [obj.objectArray addObject:obj];
  1385. [self.realm commitWriteTransaction];
  1386. [self.realm beginWriteTransaction];
  1387. {
  1388. [obj.objectArray addObject:obj];
  1389. KVORecorder r(self, obj, @"objectArray");
  1390. [self.realm cancelWriteTransaction];
  1391. [self.realm beginWriteTransaction];
  1392. AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndex:1]);
  1393. }
  1394. {
  1395. [obj.objectArray removeLastObject];
  1396. KVORecorder r(self, obj, @"objectArray");
  1397. [self.realm cancelWriteTransaction];
  1398. [self.realm beginWriteTransaction];
  1399. AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndex:0]);
  1400. }
  1401. {
  1402. obj.objectArray[0] = obj;
  1403. KVORecorder r(self, obj, @"objectArray");
  1404. [self.realm cancelWriteTransaction];
  1405. [self.realm beginWriteTransaction];
  1406. AssertIndexChange(NSKeyValueChangeReplacement, [NSIndexSet indexSetWithIndex:0]);
  1407. }
  1408. // test batching with multiple items changed
  1409. [obj.objectArray addObject:obj];
  1410. [self.realm commitWriteTransaction];
  1411. [self.realm beginWriteTransaction];
  1412. {
  1413. [obj.objectArray removeAllObjects];
  1414. KVORecorder r(self, obj, @"objectArray");
  1415. [self.realm cancelWriteTransaction];
  1416. [self.realm beginWriteTransaction];
  1417. AssertIndexChange(NSKeyValueChangeInsertion, ([NSIndexSet indexSetWithIndexesInRange:{0, 2}]));
  1418. }
  1419. {
  1420. [obj.objectArray removeLastObject];
  1421. [obj.objectArray removeLastObject];
  1422. KVORecorder r(self, obj, @"objectArray");
  1423. [self.realm cancelWriteTransaction];
  1424. [self.realm beginWriteTransaction];
  1425. AssertIndexChange(NSKeyValueChangeInsertion, ([NSIndexSet indexSetWithIndexesInRange:{0, 2}]));
  1426. }
  1427. {
  1428. [obj.objectArray insertObject:obj atIndex:1];
  1429. [obj.objectArray insertObject:obj atIndex:0];
  1430. KVORecorder r(self, obj, @"objectArray");
  1431. [self.realm cancelWriteTransaction];
  1432. [self.realm beginWriteTransaction];
  1433. NSMutableIndexSet *expected = [NSMutableIndexSet new];
  1434. [expected addIndex:0];
  1435. [expected addIndex:2]; // shifted due to inserting at 0 after 1
  1436. AssertIndexChange(NSKeyValueChangeRemoval, expected);
  1437. }
  1438. {
  1439. [obj.objectArray insertObject:obj atIndex:0];
  1440. [obj.objectArray removeLastObject];
  1441. KVORecorder r(self, obj, @"objectArray");
  1442. [self.realm cancelWriteTransaction];
  1443. [self.realm beginWriteTransaction];
  1444. AssertChanged(r, obj.objectArray, obj.objectArray);
  1445. }
  1446. }
  1447. - (void)testCancelWriteWithPrimitiveArrayChanges {
  1448. KVOObject *obj = [self createObject];
  1449. [obj.intArray addObject:@1];
  1450. [self.realm commitWriteTransaction];
  1451. [self.realm beginWriteTransaction];
  1452. {
  1453. [obj.intArray addObject:@2];
  1454. KVORecorder r(self, obj, @"intArray");
  1455. [self.realm cancelWriteTransaction];
  1456. [self.realm beginWriteTransaction];
  1457. AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndex:1]);
  1458. }
  1459. {
  1460. [obj.intArray removeLastObject];
  1461. KVORecorder r(self, obj, @"intArray");
  1462. [self.realm cancelWriteTransaction];
  1463. [self.realm beginWriteTransaction];
  1464. AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndex:0]);
  1465. }
  1466. {
  1467. obj.intArray[0] = @3;
  1468. KVORecorder r(self, obj, @"intArray");
  1469. [self.realm cancelWriteTransaction];
  1470. [self.realm beginWriteTransaction];
  1471. AssertIndexChange(NSKeyValueChangeReplacement, [NSIndexSet indexSetWithIndex:0]);
  1472. }
  1473. // test batching with multiple items changed
  1474. [obj.intArray addObject:@4];
  1475. [self.realm commitWriteTransaction];
  1476. [self.realm beginWriteTransaction];
  1477. {
  1478. [obj.intArray removeAllObjects];
  1479. KVORecorder r(self, obj, @"intArray");
  1480. [self.realm cancelWriteTransaction];
  1481. [self.realm beginWriteTransaction];
  1482. AssertIndexChange(NSKeyValueChangeInsertion, ([NSIndexSet indexSetWithIndexesInRange:{0, 2}]));
  1483. }
  1484. {
  1485. [obj.intArray removeLastObject];
  1486. [obj.intArray removeLastObject];
  1487. KVORecorder r(self, obj, @"intArray");
  1488. [self.realm cancelWriteTransaction];
  1489. [self.realm beginWriteTransaction];
  1490. AssertIndexChange(NSKeyValueChangeInsertion, ([NSIndexSet indexSetWithIndexesInRange:{0, 2}]));
  1491. }
  1492. {
  1493. [obj.intArray insertObject:@5 atIndex:1];
  1494. [obj.intArray insertObject:@6 atIndex:0];
  1495. KVORecorder r(self, obj, @"intArray");
  1496. [self.realm cancelWriteTransaction];
  1497. [self.realm beginWriteTransaction];
  1498. NSMutableIndexSet *expected = [NSMutableIndexSet new];
  1499. [expected addIndex:0];
  1500. [expected addIndex:2]; // shifted due to inserting at 0 after 1
  1501. AssertIndexChange(NSKeyValueChangeRemoval, expected);
  1502. }
  1503. {
  1504. [obj.intArray insertObject:@7 atIndex:0];
  1505. [obj.intArray removeLastObject];
  1506. KVORecorder r(self, obj, @"intArray");
  1507. [self.realm cancelWriteTransaction];
  1508. [self.realm beginWriteTransaction];
  1509. AssertChanged(r, obj.intArray, obj.intArray);
  1510. }
  1511. }
  1512. - (void)testCancelWriteWithLinkedObjectedRemoved {
  1513. KVOLinkObject2 *obj = [self createLinkObject];
  1514. [obj.array addObject:obj.obj];
  1515. [self.realm commitWriteTransaction];
  1516. [self.realm beginWriteTransaction];
  1517. {
  1518. [self.realm deleteObject:obj.obj];
  1519. KVORecorder r(self, obj, @"array");
  1520. KVORecorder r2(self, obj, @"obj");
  1521. [self.realm cancelWriteTransaction];
  1522. [self.realm beginWriteTransaction];
  1523. AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndex:0]);
  1524. AssertChanged(r2, NSNull.null, [KVOLinkObject1 allObjectsInRealm:self.realm].firstObject);
  1525. }
  1526. {
  1527. [self.realm deleteObjects:[KVOLinkObject1 allObjectsInRealm:self.realm]];
  1528. KVORecorder r(self, obj, @"array");
  1529. KVORecorder r2(self, obj, @"obj");
  1530. [self.realm cancelWriteTransaction];
  1531. [self.realm beginWriteTransaction];
  1532. AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndex:0]);
  1533. AssertChanged(r2, NSNull.null, [KVOLinkObject1 allObjectsInRealm:self.realm].firstObject);
  1534. }
  1535. {
  1536. [self.realm deleteObjects:obj.array];
  1537. KVORecorder r(self, obj, @"array");
  1538. KVORecorder r2(self, obj, @"obj");
  1539. [self.realm cancelWriteTransaction];
  1540. [self.realm beginWriteTransaction];
  1541. AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndex:0]);
  1542. AssertChanged(r2, NSNull.null, [KVOLinkObject1 allObjectsInRealm:self.realm].firstObject);
  1543. }
  1544. }
  1545. - (void)testInvalidateRealm {
  1546. KVOObject *obj = [self createObject];
  1547. [self.realm commitWriteTransaction];
  1548. KVORecorder r1(self, obj, RLMInvalidatedKey);
  1549. KVORecorder r2(self, obj, @"objectArray.invalidated");
  1550. [self.realm invalidate];
  1551. [self.realm beginWriteTransaction];
  1552. AssertChanged(r1, @NO, @YES);
  1553. AssertChanged(r2, @NO, @YES);
  1554. }
  1555. - (void)testRenamedProperties {
  1556. auto obj = [RenamedProperties1 createInRealm:self.realm withValue:@[@1, @"a"]];
  1557. [self.realm commitWriteTransaction];
  1558. [self.realm beginWriteTransaction];
  1559. KVORecorder r(self, obj, @"propA");
  1560. obj.propA = 2;
  1561. AssertChanged(r, @1, @2);
  1562. obj[@"propA"] = @3;
  1563. AssertChanged(r, @2, @3);
  1564. [obj setValue:@4 forKey:@"propA"];
  1565. AssertChanged(r, @3, @4);
  1566. // Only rollback will notify objects of different types with the same table,
  1567. // not direct modification. Probably not worth fixing this.
  1568. RenamedProperties2 *obj2 = [RenamedProperties2 allObjectsInRealm:self.realm].firstObject;
  1569. KVORecorder r2(self, obj2, @"propC");
  1570. [self.realm cancelWriteTransaction];
  1571. [self.realm beginWriteTransaction];
  1572. AssertChanged(r, @4, @1);
  1573. AssertChanged(r2, @4, @1);
  1574. }
  1575. @end
  1576. // Observing an object from a different RLMRealm instance backed by the same
  1577. // row as the managed object being mutated
  1578. @interface KVOMultipleRealmsTests : KVOManagedObjectTests
  1579. @property RLMRealm *secondaryRealm;
  1580. @end
  1581. @implementation KVOMultipleRealmsTests
  1582. - (void)setUp {
  1583. [super setUp];
  1584. RLMRealmConfiguration *config = self.realm.configuration;
  1585. config.cache = false;
  1586. self.secondaryRealm = [RLMRealm realmWithConfiguration:config error:nil];
  1587. }
  1588. - (void)tearDown {
  1589. self.secondaryRealm = nil;
  1590. [super tearDown];
  1591. }
  1592. - (id)observableForObject:(id)value {
  1593. [self.realm commitWriteTransaction];
  1594. [self.realm beginWriteTransaction];
  1595. [self.secondaryRealm refresh];
  1596. if (RLMObject *obj = RLMDynamicCast<RLMObject>(value)) {
  1597. RLMObject *copy = RLMCreateManagedAccessor(obj.objectSchema.accessorClass,
  1598. &self.secondaryRealm->_info[obj.objectSchema.className]);
  1599. copy->_row = (*copy->_info->table()).get_object(obj->_row.get_key());
  1600. return copy;
  1601. }
  1602. else if (RLMArray *array = RLMDynamicCast<RLMArray>(value)) {
  1603. return array;
  1604. }
  1605. else {
  1606. XCTFail(@"unsupported type");
  1607. return nil;
  1608. }
  1609. }
  1610. - (bool)collapsesNotifications {
  1611. return true;
  1612. }
  1613. - (void)testIgnoredProperty {
  1614. // ignored properties do not notify other accessors for the same row
  1615. }
  1616. - (void)testBatchArrayChanges {
  1617. KVOObject *obj = [self createObject];
  1618. [obj.objectArray addObject:obj];
  1619. [obj.objectArray addObject:obj];
  1620. [obj.objectArray addObject:obj];
  1621. {
  1622. KVORecorder r(self, obj, @"objectArray");
  1623. [obj.objectArray insertObject:obj atIndex:1];
  1624. [obj.objectArray insertObject:obj atIndex:0];
  1625. NSMutableIndexSet *expected = [NSMutableIndexSet new];
  1626. [expected addIndex:0];
  1627. [expected addIndex:2]; // shifted due to inserting at 0 after 1
  1628. AssertIndexChange(NSKeyValueChangeInsertion, expected);
  1629. }
  1630. {
  1631. KVORecorder r(self, obj, @"objectArray");
  1632. [obj.objectArray removeObjectAtIndex:3];
  1633. [obj.objectArray removeObjectAtIndex:3];
  1634. AssertIndexChange(NSKeyValueChangeRemoval, ([NSIndexSet indexSetWithIndexesInRange:{3, 2}]));
  1635. }
  1636. {
  1637. KVORecorder r(self, obj, @"objectArray");
  1638. [obj.objectArray removeObjectAtIndex:0];
  1639. [obj.objectArray removeAllObjects];
  1640. AssertIndexChange(NSKeyValueChangeRemoval, ([NSIndexSet indexSetWithIndexesInRange:{0, 3}]));
  1641. }
  1642. [obj.objectArray addObject:obj];
  1643. {
  1644. KVORecorder r(self, obj, @"objectArray");
  1645. [obj.objectArray addObject:obj];
  1646. [obj.objectArray removeAllObjects];
  1647. AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndex:0]);
  1648. }
  1649. [obj.objectArray addObject:obj];
  1650. {
  1651. KVORecorder r(self, obj, @"objectArray");
  1652. obj.objectArray[0] = obj;
  1653. [obj.objectArray removeAllObjects];
  1654. AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndex:0]);
  1655. }
  1656. }
  1657. - (void)testOrderedErase {
  1658. NSMutableArray *objects = [NSMutableArray arrayWithCapacity:10];
  1659. for (int i = 0; i < 10; ++i) @autoreleasepool {
  1660. [objects addObject:[ObjectWithNoLinksToOrFrom createInRealm:self.realm withValue:@[@(i)]]];
  1661. }
  1662. // deleteObject: always uses move_last_over(), but TableView::clear() uses
  1663. // erase() if there's no links
  1664. auto deleteObject = ^(int value) {
  1665. [self.realm deleteObjects:[ObjectWithNoLinksToOrFrom objectsInRealm:self.realm where:@"value = %d", value]];
  1666. };
  1667. { // delete object before observed, then observed
  1668. KVORecorder r(self, objects[2], @"invalidated");
  1669. deleteObject(1);
  1670. deleteObject(2);
  1671. AssertChanged(r, @NO, @YES);
  1672. }
  1673. { // delete object after observed, then observed
  1674. KVORecorder r(self, objects[3], @"invalidated");
  1675. deleteObject(4);
  1676. deleteObject(3);
  1677. AssertChanged(r, @NO, @YES);
  1678. }
  1679. { // delete observed, then object before observed
  1680. KVORecorder r(self, objects[6], @"invalidated");
  1681. deleteObject(6);
  1682. deleteObject(5);
  1683. AssertChanged(r, @NO, @YES);
  1684. }
  1685. { // delete observed, then object after observed
  1686. KVORecorder r(self, objects[7], @"invalidated");
  1687. deleteObject(7);
  1688. deleteObject(8);
  1689. AssertChanged(r, @NO, @YES);
  1690. }
  1691. }
  1692. @end
  1693. // Test with the table column order not matching the order of the properties
  1694. @interface KVOManagedObjectWithReorderedPropertiesTests : KVOManagedObjectTests
  1695. @end
  1696. @implementation KVOManagedObjectWithReorderedPropertiesTests
  1697. - (RLMRealm *)getRealm {
  1698. // Initialize the file with the properties in reverse order, then re-open
  1699. // with it in the normal order while the reversed one is still open (as
  1700. // otherwise it'll recreate the file due to being in-memory)
  1701. RLMSchema *schema = [RLMSchema new];
  1702. schema.objectSchema = @[[self reverseProperties:KVOObject.sharedSchema],
  1703. [self reverseProperties:KVOLinkObject1.sharedSchema],
  1704. [self reverseProperties:KVOLinkObject2.sharedSchema]];
  1705. RLMRealmConfiguration *configuration = [[RLMRealmConfiguration alloc] init];
  1706. configuration.cache = false;
  1707. configuration.inMemoryIdentifier = @"test";
  1708. configuration.customSchema = schema;
  1709. RLMRealm *reversedRealm = [RLMRealm realmWithConfiguration:configuration error:nil];
  1710. configuration.customSchema = nil;
  1711. RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil];
  1712. XCTAssertNotEqualObjects(realm.schema, reversedRealm.schema);
  1713. return realm;
  1714. }
  1715. - (RLMObjectSchema *)reverseProperties:(RLMObjectSchema *)source {
  1716. RLMObjectSchema *objectSchema = [source copy];
  1717. objectSchema.properties = objectSchema.properties.reverseObjectEnumerator.allObjects;
  1718. return objectSchema;
  1719. }
  1720. @end