123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007 |
- ////////////////////////////////////////////////////////////////////////////
- //
- // Copyright 2015 Realm Inc.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- //
- ////////////////////////////////////////////////////////////////////////////
- #import "RLMTestCase.h"
- #import "RLMObjectSchema_Private.hpp"
- #import "RLMObjectStore.h"
- #import "RLMObject_Private.hpp"
- #import "RLMRealmConfiguration_Private.hpp"
- #import "RLMRealm_Private.hpp"
- #import "RLMSchema_Private.h"
- #import "shared_realm.hpp"
- #import <realm/group.hpp>
- #import <atomic>
- #import <memory>
- #import <objc/runtime.h>
- #import <vector>
- RLM_ARRAY_TYPE(KVOObject)
- RLM_ARRAY_TYPE(KVOLinkObject1)
- @interface KVOObject : RLMObject
- @property int pk; // Primary key for isEqual:
- @property int ignored;
- @property BOOL boolCol;
- @property int16_t int16Col;
- @property int32_t int32Col;
- @property int64_t int64Col;
- @property float floatCol;
- @property double doubleCol;
- @property bool cBoolCol;
- @property NSString *stringCol;
- @property NSData *binaryCol;
- @property NSDate *dateCol;
- @property KVOObject *objectCol;
- @property RLMArray<RLMBool> *boolArray;
- @property RLMArray<RLMInt> *intArray;
- @property RLMArray<RLMFloat> *floatArray;
- @property RLMArray<RLMDouble> *doubleArray;
- @property RLMArray<RLMString> *stringArray;
- @property RLMArray<RLMData> *dataArray;
- @property RLMArray<RLMDate> *dateArray;
- @property RLMArray<KVOObject> *objectArray;
- @property NSNumber<RLMInt> *optIntCol;
- @property NSNumber<RLMFloat> *optFloatCol;
- @property NSNumber<RLMDouble> *optDoubleCol;
- @property NSNumber<RLMBool> *optBoolCol;
- @end
- @implementation KVOObject
- + (NSString *)primaryKey {
- return @"pk";
- }
- + (NSArray *)ignoredProperties {
- return @[@"ignored"];
- }
- @end
- @interface KVOLinkObject1 : RLMObject
- @property int pk; // Primary key for isEqual:
- @property KVOObject *obj;
- @property RLMArray<KVOObject> *array;
- @end
- @implementation KVOLinkObject1
- + (NSString *)primaryKey {
- return @"pk";
- }
- @end
- @interface KVOLinkObject2 : RLMObject
- @property int pk; // Primary key for isEqual:
- @property KVOLinkObject1 *obj;
- @property RLMArray<KVOLinkObject1> *array;
- @end
- @implementation KVOLinkObject2
- + (NSString *)primaryKey {
- return @"pk";
- }
- @end
- @interface PlainKVOObject : NSObject
- @property int ignored;
- @property BOOL boolCol;
- @property int16_t int16Col;
- @property int32_t int32Col;
- @property int64_t int64Col;
- @property float floatCol;
- @property double doubleCol;
- @property bool cBoolCol;
- @property NSString *stringCol;
- @property NSData *binaryCol;
- @property NSDate *dateCol;
- @property PlainKVOObject *objectCol;
- @property NSMutableArray *boolArray;
- @property NSMutableArray *intArray;
- @property NSMutableArray *floatArray;
- @property NSMutableArray *doubleArray;
- @property NSMutableArray *stringArray;
- @property NSMutableArray *dataArray;
- @property NSMutableArray *dateArray;
- @property NSMutableArray *objectArray;
- @property NSNumber<RLMInt> *optIntCol;
- @property NSNumber<RLMFloat> *optFloatCol;
- @property NSNumber<RLMDouble> *optDoubleCol;
- @property NSNumber<RLMBool> *optBoolCol;
- @end
- @implementation PlainKVOObject
- @end
- @interface PlainLinkObject1 : NSObject
- @property PlainKVOObject *obj;
- @property NSMutableArray *array;
- @end
- @implementation PlainLinkObject1
- @end
- @interface PlainLinkObject2 : NSObject
- @property PlainLinkObject1 *obj;
- @property NSMutableArray *array;
- @end
- @implementation PlainLinkObject2
- @end
- // Tables with no links (or backlinks) preserve the order of rows on
- // insertion/deletion, while tables with links do not, so we need an object
- // class known to have no links to test the ordered case
- @interface ObjectWithNoLinksToOrFrom : RLMObject
- @property int value;
- @end
- @implementation ObjectWithNoLinksToOrFrom
- @end
- // An object which removes a KVO registration when it's deallocated, for use
- // as an associated object
- @interface KVOUnregisterHelper : NSObject
- @end
- @implementation KVOUnregisterHelper {
- __unsafe_unretained id _obj;
- __unsafe_unretained id _observer;
- NSString *_keyPath;
- }
- + (void)automaticallyUnregister:(id)observer object:(id)obj keyPath:(NSString *)keyPath {
- KVOUnregisterHelper *helper = [self new];
- helper->_observer = observer;
- helper->_obj = obj;
- helper->_keyPath = keyPath;
- objc_setAssociatedObject(obj, (__bridge void *)helper, helper, OBJC_ASSOCIATION_RETAIN);
- }
- - (void)dealloc {
- [_obj removeObserver:_observer forKeyPath:_keyPath];
- }
- @end
- // A KVO observer which retains the given object until it observes a change
- @interface ReleaseOnObservation : NSObject
- @property (strong) id object;
- @end
- @implementation ReleaseOnObservation
- - (void)observeValueForKeyPath:(NSString *)keyPath
- ofObject:(id)object
- change:(__unused NSDictionary *)change
- context:(void *)context
- {
- [object removeObserver:self forKeyPath:keyPath context:context];
- _object = nil;
- }
- @end
- @interface KVOTests : RLMTestCase
- // get an object that should be observed for the given object being mutated
- // used by some of the subclasses to observe a different accessor for the same row
- - (id)observableForObject:(id)obj;
- @end
- // subscribes to kvo notifications on the passed object on creation, records
- // all change notifications sent and makes them available in `notifications`,
- // and automatically unsubscribes on destruction
- class KVORecorder {
- id _observer;
- id _obj;
- NSString *_keyPath;
- RLMRealm *_mutationRealm;
- RLMRealm *_observationRealm;
- NSMutableArray *_notifications;
- public:
- // construct a new recorder for the given `keyPath` on `obj`, using `observer`
- // as the NSObject helper to actually add as an observer
- KVORecorder(id observer, id obj, NSString *keyPath,
- int options = NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew)
- : _observer(observer)
- , _obj([observer observableForObject:obj])
- , _keyPath(keyPath)
- , _mutationRealm([obj respondsToSelector:@selector(realm)] ? (RLMRealm *)[obj realm] : nil)
- , _observationRealm([_obj respondsToSelector:@selector(realm)] ? (RLMRealm *)[_obj realm] : nil)
- , _notifications([NSMutableArray new])
- {
- [_obj addObserver:observer forKeyPath:keyPath options:options context:this];
- }
- ~KVORecorder() {
- id self = _observer;
- @try {
- [_obj removeObserver:_observer forKeyPath:_keyPath context:this];
- }
- @catch (NSException *e) {
- XCTFail(@"%@", e.description);
- }
- XCTAssertEqual(0U, _notifications.count);
- static_cast<void>(self); // Pre-12 versions of Xcode require the self variable but 12 doesn't use it
- }
- // record a single notification
- void operator()(NSString *key, id obj, NSDictionary *changeDictionary) {
- id self = _observer;
- XCTAssertEqual(obj, _obj);
- XCTAssertEqualObjects(key, _keyPath);
- [_notifications addObject:changeDictionary.copy];
- static_cast<void>(self); // Pre-12 versions of Xcode require the self variable but 12 doesn't use it
- }
- // ensure that the observed object is updated for any changes made to the
- // object being mutated if they are different
- void refresh() {
- if (_mutationRealm != _observationRealm) {
- [_mutationRealm commitWriteTransaction];
- [_observationRealm refresh];
- [_mutationRealm beginWriteTransaction];
- }
- }
- NSDictionary *pop_front() {
- NSDictionary *value = [_notifications firstObject];
- if (value) {
- [_notifications removeObjectAtIndex:0U];
- }
- return value;
- }
- NSUInteger size() const {
- return _notifications.count;
- }
- bool empty() const {
- return _notifications.count == 0;
- }
- };
- // Assert that `recorder` has a notification at `index` and return it if so
- #define AssertNotification(recorder) ([&]{ \
- (recorder).refresh(); \
- NSDictionary *value = recorder.pop_front(); \
- XCTAssertNotNil(value, @"Did not get a notification when expected"); \
- return value; \
- })()
- // Validate that `recorder` has at least one notification, and that the first
- // notification is the expected one
- #define AssertChanged(recorder, from, to) do { \
- if (NSDictionary *note = AssertNotification((recorder))) { \
- XCTAssertEqualObjects(@(NSKeyValueChangeSetting), note[NSKeyValueChangeKindKey]); \
- XCTAssertEqualObjects((from), note[NSKeyValueChangeOldKey]); \
- XCTAssertEqualObjects((to), note[NSKeyValueChangeNewKey]); \
- } \
- else { \
- return; \
- } \
- } while (false)
- // Validate that `r` has a notification with the given kind and changed indexes,
- // remove it, and verify that there are no more notifications
- #define AssertIndexChange(kind, indexes) do { \
- if (NSDictionary *note = AssertNotification(r)) { \
- XCTAssertEqual([note[NSKeyValueChangeKindKey] intValue], static_cast<int>(kind)); \
- XCTAssertEqualObjects(note[NSKeyValueChangeIndexesKey], indexes); \
- } \
- XCTAssertTrue(r.empty()); \
- } while (0)
- // Tests for plain Foundation key-value observing to verify that we correctly
- // match the standard semantics. Each of the subclasses of KVOTests runs the
- // same set of tests on RLMObjects in difference scenarios
- @implementation KVOTests
- // forward a KVO notification to the KVORecorder stored in the context
- - (void)observeValueForKeyPath:(NSString *)keyPath
- ofObject:(id)object
- change:(NSDictionary *)change
- context:(void *)context
- {
- (*static_cast<KVORecorder *>(context))(keyPath, object, change);
- }
- // overridden in the multiple accessors, one realm and multiple realms cases
- - (id)observableForObject:(id)obj {
- return obj;
- }
- // overridden in the multiple realms case because `-refresh` does not send
- // notifications for intermediate states
- - (bool)collapsesNotifications {
- return false;
- }
- // overridden in all subclases to return the appropriate object
- // base class runs the tests on a plain NSObject using stock KVO to ensure that
- // the tests are actually covering the correct behavior, since there's a great
- // deal that the documentation doesn't specify
- - (id)createObject {
- PlainKVOObject *obj = [PlainKVOObject new];
- obj.int16Col = 1;
- obj.int32Col = 2;
- obj.int64Col = 3;
- obj.binaryCol = NSData.data;
- obj.stringCol = @"";
- obj.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:0];
- obj.boolArray = [NSMutableArray array];
- obj.intArray = [NSMutableArray array];
- obj.floatArray = [NSMutableArray array];
- obj.doubleArray = [NSMutableArray array];
- obj.stringArray = [NSMutableArray array];
- obj.dataArray = [NSMutableArray array];
- obj.dateArray = [NSMutableArray array];
- obj.objectArray = [NSMutableArray array];
- return obj;
- }
- - (id)createLinkObject {
- PlainLinkObject1 *obj1 = [PlainLinkObject1 new];
- obj1.obj = [self createObject];
- obj1.array = [NSMutableArray new];
- PlainLinkObject2 *obj2 = [PlainLinkObject2 new];
- obj2.obj = obj1;
- obj2.array = [NSMutableArray new];
- return obj2;
- }
- // actual tests follow
- - (void)testRegisterForUnknownProperty {
- KVOObject *obj = [self createObject];
- XCTAssertNoThrow([obj addObserver:self forKeyPath:@"non-existent" options:0 context:nullptr]);
- XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"non-existent"]);
- XCTAssertNoThrow([obj addObserver:self forKeyPath:@"non-existent" options:NSKeyValueObservingOptionOld context:nullptr]);
- XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"non-existent"]);
- XCTAssertNoThrow([obj addObserver:self forKeyPath:@"non-existent" options:NSKeyValueObservingOptionPrior context:nullptr]);
- XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"non-existent"]);
- }
- - (void)testRemoveObserver {
- KVOObject *obj = [self createObject];
- XCTAssertThrowsSpecificNamed([obj removeObserver:self forKeyPath:@"int32Col"], NSException, NSRangeException);
- XCTAssertThrowsSpecificNamed([obj removeObserver:self forKeyPath:@"int32Col" context:nullptr], NSException, NSRangeException);
- XCTAssertNoThrow([obj addObserver:self forKeyPath:@"int32Col" options:0 context:nullptr]);
- XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"int32Col"]);
- XCTAssertThrowsSpecificNamed([obj removeObserver:self forKeyPath:@"int32Col"], NSException, NSRangeException);
- // `context` parameter must match if it's passed, but the overload that doesn't
- // take one will unregister any context
- void *context1 = (void *)1;
- void *context2 = (void *)2;
- XCTAssertNoThrow([obj addObserver:self forKeyPath:@"int32Col" options:0 context:context1]);
- XCTAssertThrows([obj removeObserver:self forKeyPath:@"int32Col" context:context2]);
- XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"int32Col" context:context1]);
- XCTAssertNoThrow([obj addObserver:self forKeyPath:@"int32Col" options:0 context:context2]);
- XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"int32Col" context:context2]);
- XCTAssertNoThrow([obj addObserver:self forKeyPath:@"int32Col" options:0 context:context2]);
- XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"int32Col"]);
- XCTAssertThrows([obj removeObserver:self forKeyPath:@"int32Col"]);
- XCTAssertNoThrow([obj addObserver:self forKeyPath:@"int32Col" options:0 context:context1]);
- XCTAssertNoThrow([obj addObserver:self forKeyPath:@"int32Col" options:0 context:context2]);
- XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"int32Col" context:context1]);
- XCTAssertThrows([obj removeObserver:self forKeyPath:@"int32Col" context:context1]);
- XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"int32Col" context:context2]);
- XCTAssertThrows([obj removeObserver:self forKeyPath:@"int32Col" context:context2]);
- XCTAssertNoThrow([obj addObserver:self forKeyPath:@"int32Col" options:0 context:context1]);
- XCTAssertNoThrow([obj addObserver:self forKeyPath:@"int32Col" options:0 context:context2]);
- XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"int32Col" context:context2]);
- XCTAssertThrows([obj removeObserver:self forKeyPath:@"int32Col" context:context2]);
- XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"int32Col" context:context1]);
- XCTAssertThrows([obj removeObserver:self forKeyPath:@"int32Col" context:context1]);
- // no context version should only unregister one (unspecified) observer
- XCTAssertNoThrow([obj addObserver:self forKeyPath:@"int32Col" options:0 context:context1]);
- XCTAssertNoThrow([obj addObserver:self forKeyPath:@"int32Col" options:0 context:context2]);
- XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"int32Col"]);
- XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"int32Col"]);
- XCTAssertThrows([obj removeObserver:self forKeyPath:@"int32Col"]);
- }
- - (void)testRemoveObserverInObservation {
- auto helper = [ReleaseOnObservation new];
- __unsafe_unretained id obj;
- __weak id weakObj;
- @autoreleasepool {
- obj = weakObj = helper.object = [self createObject];
- [obj addObserver:helper forKeyPath:@"int32Col" options:NSKeyValueObservingOptionOld context:nullptr];
- }
- [obj setInt32Col:0];
- XCTAssertNil(helper.object);
- XCTAssertNil(weakObj);
- }
- - (void)testSimple {
- KVOObject *obj = [self createObject];
- {
- KVORecorder r(self, obj, @"int32Col");
- obj.int32Col = 10;
- AssertChanged(r, @2, @10);
- }
- {
- KVORecorder r(self, obj, @"int32Col");
- obj.int32Col = 1;
- AssertChanged(r, @10, @1);
- }
- }
- - (void)testSelfAssignmentNotifies {
- KVOObject *obj = [self createObject];
- {
- KVORecorder r(self, obj, @"int32Col");
- obj.int32Col = obj.int32Col;
- AssertChanged(r, @2, @2);
- }
- }
- - (void)testMultipleObserversAreNotified {
- KVOObject *obj = [self createObject];
- {
- KVORecorder r1(self, obj, @"int32Col");
- KVORecorder r2(self, obj, @"int32Col");
- KVORecorder r3(self, obj, @"int32Col");
- obj.int32Col = 10;
- AssertChanged(r1, @2, @10);
- AssertChanged(r2, @2, @10);
- AssertChanged(r3, @2, @10);
- }
- }
- - (void)testOnlyObserversForTheCorrectPropertyAreNotified {
- KVOObject *obj = [self createObject];
- {
- KVORecorder r16(self, obj, @"int16Col");
- KVORecorder r32(self, obj, @"int32Col");
- KVORecorder r64(self, obj, @"int64Col");
- obj.int16Col = 2;
- AssertChanged(r16, @1, @2);
- XCTAssertTrue(r16.empty());
- XCTAssertTrue(r32.empty());
- XCTAssertTrue(r64.empty());
- obj.int32Col = 2;
- AssertChanged(r32, @2, @2);
- XCTAssertTrue(r16.empty());
- XCTAssertTrue(r32.empty());
- XCTAssertTrue(r64.empty());
- obj.int64Col = 2;
- AssertChanged(r64, @3, @2);
- XCTAssertTrue(r16.empty());
- XCTAssertTrue(r32.empty());
- XCTAssertTrue(r64.empty());
- }
- }
- - (void)testMultipleChangesWithSingleObserver {
- KVOObject *obj = [self createObject];
- KVORecorder r(self, obj, @"int32Col");
- obj.int32Col = 1;
- obj.int32Col = 2;
- obj.int32Col = 3;
- obj.int32Col = 3;
- if (self.collapsesNotifications) {
- AssertChanged(r, @2, @3);
- }
- else {
- AssertChanged(r, @2, @1);
- AssertChanged(r, @1, @2);
- AssertChanged(r, @2, @3);
- AssertChanged(r, @3, @3);
- }
- }
- - (void)testOnlyObserversForTheCorrectObjectAreNotified {
- KVOObject *obj1 = [self createObject];
- KVOObject *obj2 = [self createObject];
- KVORecorder r1(self, obj1, @"int32Col");
- KVORecorder r2(self, obj2, @"int32Col");
- obj1.int32Col = 10;
- AssertChanged(r1, @2, @10);
- XCTAssertEqual(0U, r2.size());
- obj2.int32Col = 5;
- AssertChanged(r2, @2, @5);
- }
- - (void)testOptionsInitial {
- KVOObject *obj = [self createObject];
- {
- KVORecorder r(self, obj, @"int32Col", 0);
- XCTAssertEqual(0U, r.size());
- }
- {
- KVORecorder r(self, obj, @"int32Col", NSKeyValueObservingOptionInitial);
- r.pop_front();
- }
- }
- - (void)testOptionsOld {
- KVOObject *obj = [self createObject];
- {
- KVORecorder r(self, obj, @"int32Col", 0);
- obj.int32Col = 0;
- if (NSDictionary *note = AssertNotification(r)) {
- XCTAssertNil(note[NSKeyValueChangeOldKey]);
- }
- }
- {
- KVORecorder r(self, obj, @"int32Col", NSKeyValueObservingOptionOld);
- obj.int32Col = 0;
- if (NSDictionary *note = AssertNotification(r)) {
- XCTAssertNotNil(note[NSKeyValueChangeOldKey]);
- }
- }
- }
- - (void)testOptionsNew {
- KVOObject *obj = [self createObject];
- {
- KVORecorder r(self, obj, @"int32Col", 0);
- obj.int32Col = 0;
- if (NSDictionary *note = AssertNotification(r)) {
- XCTAssertNil(note[NSKeyValueChangeNewKey]);
- }
- }
- {
- KVORecorder r(self, obj, @"int32Col", NSKeyValueObservingOptionNew);
- obj.int32Col = 0;
- if (NSDictionary *note = AssertNotification(r)) {
- XCTAssertNotNil(note[NSKeyValueChangeNewKey]);
- }
- }
- }
- - (void)testOptionsPrior {
- KVOObject *obj = [self createObject];
- KVORecorder r(self, obj, @"int32Col", NSKeyValueObservingOptionNew|NSKeyValueObservingOptionPrior);
- obj.int32Col = 0;
- r.refresh();
- XCTAssertEqual(2U, r.size());
- if (NSDictionary *note = AssertNotification(r)) {
- XCTAssertNil(note[NSKeyValueChangeNewKey]);
- XCTAssertEqualObjects(@YES, note[NSKeyValueChangeNotificationIsPriorKey]);
- }
- if (NSDictionary *note = AssertNotification(r)) {
- XCTAssertNotNil(note[NSKeyValueChangeNewKey]);
- XCTAssertNil(note[NSKeyValueChangeNotificationIsPriorKey]);
- }
- }
- - (void)testAllPropertyTypes {
- KVOObject *obj = [self createObject];
- {
- KVORecorder r(self, obj, @"boolCol");
- obj.boolCol = YES;
- AssertChanged(r, @NO, @YES);
- }
- {
- KVORecorder r(self, obj, @"int16Col");
- obj.int16Col = 0;
- AssertChanged(r, @1, @0);
- }
- {
- KVORecorder r(self, obj, @"int32Col");
- obj.int32Col = 0;
- AssertChanged(r, @2, @0);
- }
- {
- KVORecorder r(self, obj, @"int64Col");
- obj.int64Col = 0;
- AssertChanged(r, @3, @0);
- }
- {
- KVORecorder r(self, obj, @"floatCol");
- obj.floatCol = 1.0f;
- AssertChanged(r, @0, @1);
- }
- {
- KVORecorder r(self, obj, @"doubleCol");
- obj.doubleCol = 1.0;
- AssertChanged(r, @0, @1);
- }
- {
- KVORecorder r(self, obj, @"cBoolCol");
- obj.cBoolCol = YES;
- AssertChanged(r, @NO, @YES);
- }
- {
- KVORecorder r(self, obj, @"stringCol");
- obj.stringCol = @"abc";
- AssertChanged(r, @"", @"abc");
- obj.stringCol = nil;
- AssertChanged(r, @"abc", NSNull.null);
- }
- {
- KVORecorder r(self, obj, @"binaryCol");
- NSData *data = [@"abc" dataUsingEncoding:NSUTF8StringEncoding];
- obj.binaryCol = data;
- AssertChanged(r, NSData.data, data);
- obj.binaryCol = nil;
- AssertChanged(r, data, NSNull.null);
- }
- {
- KVORecorder r(self, obj, @"dateCol");
- NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:1];
- obj.dateCol = date;
- AssertChanged(r, [NSDate dateWithTimeIntervalSinceReferenceDate:0], date);
- obj.dateCol = nil;
- AssertChanged(r, date, NSNull.null);
- }
- {
- KVORecorder r(self, obj, @"objectCol");
- obj.objectCol = obj;
- AssertChanged(r, NSNull.null, [self observableForObject:obj]);
- obj.objectCol = nil;
- AssertChanged(r, [self observableForObject:obj], NSNull.null);
- }
- {
- KVORecorder r(self, obj, @"intArray");
- obj.intArray = obj.intArray;
- r.refresh();
- r.pop_front(); // asserts that there's something to pop
- }
- {
- KVORecorder r(self, obj, @"boolArray");
- obj.boolArray = obj.boolArray;
- r.refresh();
- r.pop_front(); // asserts that there's something to pop
- }
- {
- KVORecorder r(self, obj, @"floatArray");
- obj.floatArray = obj.floatArray;
- r.refresh();
- r.pop_front(); // asserts that there's something to pop
- }
- {
- KVORecorder r(self, obj, @"doubleArray");
- obj.doubleArray = obj.doubleArray;
- r.refresh();
- r.pop_front(); // asserts that there's something to pop
- }
- {
- KVORecorder r(self, obj, @"stringArray");
- obj.stringArray = obj.stringArray;
- r.refresh();
- r.pop_front(); // asserts that there's something to pop
- }
- {
- KVORecorder r(self, obj, @"dataArray");
- obj.dataArray = obj.dataArray;
- r.refresh();
- r.pop_front(); // asserts that there's something to pop
- }
- {
- KVORecorder r(self, obj, @"dateArray");
- obj.dateArray = obj.dateArray;
- r.refresh();
- r.pop_front(); // asserts that there's something to pop
- }
- {
- KVORecorder r(self, obj, @"objectArray");
- obj.objectArray = obj.objectArray;
- r.refresh();
- r.pop_front(); // asserts that there's something to pop
- }
- {
- KVORecorder r(self, obj, @"optIntCol");
- obj.optIntCol = @1;
- AssertChanged(r, NSNull.null, @1);
- obj.optIntCol = nil;
- AssertChanged(r, @1, NSNull.null);
- }
- {
- KVORecorder r(self, obj, @"optFloatCol");
- obj.optFloatCol = @1.1f;
- AssertChanged(r, NSNull.null, @1.1f);
- obj.optFloatCol = nil;
- AssertChanged(r, @1.1f, NSNull.null);
- }
- {
- KVORecorder r(self, obj, @"optDoubleCol");
- obj.optDoubleCol = @1.1;
- AssertChanged(r, NSNull.null, @1.1);
- obj.optDoubleCol = nil;
- AssertChanged(r, @1.1, NSNull.null);
- }
- {
- KVORecorder r(self, obj, @"optBoolCol");
- obj.optBoolCol = @YES;
- AssertChanged(r, NSNull.null, @YES);
- obj.optBoolCol = nil;
- AssertChanged(r, @YES, NSNull.null);
- }
- }
- - (void)testAllPropertyTypesKVC {
- KVOObject *obj = [self createObject];
- {
- KVORecorder r(self, obj, @"boolCol");
- [obj setValue:@YES forKey:@"boolCol"];
- AssertChanged(r, @NO, @YES);
- }
- {
- KVORecorder r(self, obj, @"int16Col");
- [obj setValue:@0 forKey:@"int16Col"];
- AssertChanged(r, @1, @0);
- }
- {
- KVORecorder r(self, obj, @"int32Col");
- [obj setValue:@0 forKey:@"int32Col"];
- AssertChanged(r, @2, @0);
- }
- {
- KVORecorder r(self, obj, @"int64Col");
- [obj setValue:@0 forKey:@"int64Col"];
- AssertChanged(r, @3, @0);
- }
- {
- KVORecorder r(self, obj, @"floatCol");
- [obj setValue:@1.0f forKey:@"floatCol"];
- AssertChanged(r, @0, @1);
- }
- {
- KVORecorder r(self, obj, @"doubleCol");
- [obj setValue:@1.0 forKey:@"doubleCol"];
- AssertChanged(r, @0, @1);
- }
- {
- KVORecorder r(self, obj, @"cBoolCol");
- [obj setValue:@YES forKey:@"cBoolCol"];
- AssertChanged(r, @NO, @YES);
- }
- {
- KVORecorder r(self, obj, @"stringCol");
- [obj setValue:@"abc" forKey:@"stringCol"];
- AssertChanged(r, @"", @"abc");
- [obj setValue:nil forKey:@"stringCol"];
- AssertChanged(r, @"abc", NSNull.null);
- }
- {
- KVORecorder r(self, obj, @"binaryCol");
- NSData *data = [@"abc" dataUsingEncoding:NSUTF8StringEncoding];
- [obj setValue:data forKey:@"binaryCol"];
- AssertChanged(r, NSData.data, data);
- [obj setValue:nil forKey:@"binaryCol"];
- AssertChanged(r, data, NSNull.null);
- }
- {
- KVORecorder r(self, obj, @"dateCol");
- NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:1];
- [obj setValue:date forKey:@"dateCol"];
- AssertChanged(r, [NSDate dateWithTimeIntervalSinceReferenceDate:0], date);
- [obj setValue:nil forKey:@"dateCol"];
- AssertChanged(r, date, NSNull.null);
- }
- {
- KVORecorder r(self, obj, @"objectCol");
- [obj setValue:obj forKey:@"objectCol"];
- AssertChanged(r, NSNull.null, [self observableForObject:obj]);
- [obj setValue:nil forKey:@"objectCol"];
- AssertChanged(r, [self observableForObject:obj], NSNull.null);
- }
- {
- KVORecorder r(self, obj, @"objectArray");
- [obj setValue:obj.objectArray forKey:@"objectArray"];
- r.refresh();
- r.pop_front(); // asserts that there's something to pop
- }
- {
- KVORecorder r(self, obj, @"optIntCol");
- [obj setValue:@1 forKey:@"optIntCol"];
- AssertChanged(r, NSNull.null, @1);
- [obj setValue:nil forKey:@"optIntCol"];
- AssertChanged(r, @1, NSNull.null);
- }
- {
- KVORecorder r(self, obj, @"optFloatCol");
- [obj setValue:@1.1f forKey:@"optFloatCol"];
- AssertChanged(r, NSNull.null, @1.1f);
- [obj setValue:nil forKey:@"optFloatCol"];
- AssertChanged(r, @1.1f, NSNull.null);
- }
- {
- KVORecorder r(self, obj, @"optDoubleCol");
- [obj setValue:@1.1 forKey:@"optDoubleCol"];
- AssertChanged(r, NSNull.null, @1.1);
- [obj setValue:nil forKey:@"optDoubleCol"];
- AssertChanged(r, @1.1, NSNull.null);
- }
- {
- KVORecorder r(self, obj, @"optBoolCol");
- [obj setValue:@YES forKey:@"optBoolCol"];
- AssertChanged(r, NSNull.null, @YES);
- [obj setValue:nil forKey:@"optBoolCol"];
- AssertChanged(r, @YES, NSNull.null);
- }
- }
- - (void)testAllPropertyTypesDynamic {
- KVOObject *obj = [self createObject];
- if (![obj respondsToSelector:@selector(setObject:forKeyedSubscript:)]) {
- return;
- }
- {
- KVORecorder r(self, obj, @"boolCol");
- obj[@"boolCol"] = @YES;
- AssertChanged(r, @NO, @YES);
- }
- {
- KVORecorder r(self, obj, @"int16Col");
- obj[@"int16Col"] = @0;
- AssertChanged(r, @1, @0);
- }
- {
- KVORecorder r(self, obj, @"int32Col");
- obj[@"int32Col"] = @0;
- AssertChanged(r, @2, @0);
- }
- {
- KVORecorder r(self, obj, @"int64Col");
- obj[@"int64Col"] = @0;
- AssertChanged(r, @3, @0);
- }
- {
- KVORecorder r(self, obj, @"floatCol");
- obj[@"floatCol"] = @1.0f;
- AssertChanged(r, @0, @1);
- }
- {
- KVORecorder r(self, obj, @"doubleCol");
- obj[@"doubleCol"] = @1.0;
- AssertChanged(r, @0, @1);
- }
- {
- KVORecorder r(self, obj, @"cBoolCol");
- obj[@"cBoolCol"] = @YES;
- AssertChanged(r, @NO, @YES);
- }
- {
- KVORecorder r(self, obj, @"stringCol");
- obj[@"stringCol"] = @"abc";
- AssertChanged(r, @"", @"abc");
- obj[@"stringCol"] = nil;
- AssertChanged(r, @"abc", NSNull.null);
- }
- {
- KVORecorder r(self, obj, @"binaryCol");
- NSData *data = [@"abc" dataUsingEncoding:NSUTF8StringEncoding];
- obj[@"binaryCol"] = data;
- AssertChanged(r, NSData.data, data);
- obj[@"binaryCol"] = nil;
- AssertChanged(r, data, NSNull.null);
- }
- {
- KVORecorder r(self, obj, @"dateCol");
- NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:1];
- obj[@"dateCol"] = date;
- AssertChanged(r, [NSDate dateWithTimeIntervalSinceReferenceDate:0], date);
- obj[@"dateCol"] = nil;
- AssertChanged(r, date, NSNull.null);
- }
- {
- KVORecorder r(self, obj, @"objectCol");
- obj[@"objectCol"] = obj;
- AssertChanged(r, NSNull.null, [self observableForObject:obj]);
- obj[@"objectCol"] = nil;
- AssertChanged(r, [self observableForObject:obj], NSNull.null);
- }
- {
- KVORecorder r(self, obj, @"objectArray");
- obj[@"objectArray"] = obj.objectArray;
- r.refresh();
- r.pop_front(); // asserts that there's something to pop
- }
- {
- KVORecorder r(self, obj, @"optIntCol");
- obj[@"optIntCol"] = @1;
- AssertChanged(r, NSNull.null, @1);
- obj[@"optIntCol"] = nil;
- AssertChanged(r, @1, NSNull.null);
- }
- {
- KVORecorder r(self, obj, @"optFloatCol");
- obj[@"optFloatCol"] = @1.1f;
- AssertChanged(r, NSNull.null, @1.1f);
- obj[@"optFloatCol"] = nil;
- AssertChanged(r, @1.1f, NSNull.null);
- }
- {
- KVORecorder r(self, obj, @"optDoubleCol");
- obj[@"optDoubleCol"] = @1.1;
- AssertChanged(r, NSNull.null, @1.1);
- obj[@"optDoubleCol"] = nil;
- AssertChanged(r, @1.1, NSNull.null);
- }
- {
- KVORecorder r(self, obj, @"optBoolCol");
- obj[@"optBoolCol"] = @YES;
- AssertChanged(r, NSNull.null, @YES);
- obj[@"optBoolCol"] = nil;
- AssertChanged(r, @YES, NSNull.null);
- }
- }
- - (void)testArrayDiffs {
- KVOLinkObject2 *obj = [self createLinkObject];
- KVORecorder r(self, obj, @"array");
- id mutator = [obj mutableArrayValueForKey:@"array"];
- [mutator addObject:obj.obj];
- AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndex:0]);
- [mutator addObject:obj.obj];
- AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndex:1]);
- [mutator removeObjectAtIndex:0];
- AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndex:0]);
- [mutator replaceObjectAtIndex:0 withObject:obj.obj];
- AssertIndexChange(NSKeyValueChangeReplacement, [NSIndexSet indexSetWithIndex:0]);
- NSMutableIndexSet *indexes = [NSMutableIndexSet new];
- [indexes addIndex:0];
- [indexes addIndex:2];
- [mutator insertObjects:@[obj.obj, obj.obj] atIndexes:indexes];
- AssertIndexChange(NSKeyValueChangeInsertion, indexes);
- [mutator removeObjectsAtIndexes:indexes];
- AssertIndexChange(NSKeyValueChangeRemoval, indexes);
- if (![obj.array isKindOfClass:[NSArray class]]) {
- // We deliberately diverge from NSMutableArray for `removeAllObjects` and
- // `addObjectsFromArray:`, because generating a separate notification for
- // each object added or removed is needlessly pessimal.
- [mutator addObjectsFromArray:@[obj.obj, obj.obj]];
- AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, 2)]);
- // NSArray sends multiple notifications for exchange, which we can't do
- // on refresh
- [mutator exchangeObjectAtIndex:0 withObjectAtIndex:1];
- AssertIndexChange(NSKeyValueChangeReplacement, [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)]);
- // NSArray doesn't have move
- [mutator moveObjectAtIndex:1 toIndex:0];
- AssertIndexChange(NSKeyValueChangeReplacement, [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)]);
- [mutator removeLastObject];
- AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndex:2]);
- [mutator removeAllObjects];
- AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)]);
- }
- }
- - (void)testPrimitiveArrayDiffs {
- KVOObject *obj = [self createObject];
- KVORecorder r(self, obj, @"intArray");
- id mutator = [obj mutableArrayValueForKey:@"intArray"];
- [mutator addObject:@1];
- AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndex:0]);
- [mutator addObject:@2];
- AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndex:1]);
- [mutator removeObjectAtIndex:0];
- AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndex:0]);
- [mutator replaceObjectAtIndex:0 withObject:@3];
- AssertIndexChange(NSKeyValueChangeReplacement, [NSIndexSet indexSetWithIndex:0]);
- NSMutableIndexSet *indexes = [NSMutableIndexSet new];
- [indexes addIndex:0];
- [indexes addIndex:2];
- [mutator insertObjects:@[@4, @5] atIndexes:indexes];
- AssertIndexChange(NSKeyValueChangeInsertion, indexes);
- [mutator removeObjectsAtIndexes:indexes];
- AssertIndexChange(NSKeyValueChangeRemoval, indexes);
- if (![obj.intArray isKindOfClass:[NSArray class]]) {
- // We deliberately diverge from NSMutableArray for `removeAllObjects` and
- // `addObjectsFromArray:`, because generating a separate notification for
- // each object added or removed is needlessly pessimal.
- [mutator addObjectsFromArray:@[@6, @7]];
- AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, 2)]);
- // NSArray sends multiple notifications for exchange, which we can't do
- // on refresh
- [mutator exchangeObjectAtIndex:0 withObjectAtIndex:1];
- AssertIndexChange(NSKeyValueChangeReplacement, [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)]);
- // NSArray doesn't have move
- [mutator moveObjectAtIndex:1 toIndex:0];
- AssertIndexChange(NSKeyValueChangeReplacement, [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)]);
- [mutator removeLastObject];
- AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndex:2]);
- [mutator removeAllObjects];
- AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)]);
- }
- }
- - (void)testIgnoredProperty {
- KVOObject *obj = [self createObject];
- KVORecorder r(self, obj, @"ignored");
- obj.ignored = 10;
- AssertChanged(r, @0, @10);
- }
- - (void)testChangeEndOfKeyPath {
- KVOLinkObject2 *obj = [self createLinkObject];
- std::unique_ptr<KVORecorder> r;
- @autoreleasepool {
- r = std::make_unique<KVORecorder>(self, obj, @"obj.obj.boolCol");
- }
- obj.obj.obj.boolCol = YES;
- AssertChanged(*r, @NO, @YES);
- }
- - (void)testChangeMiddleOfKeyPath {
- KVOLinkObject2 *obj = [self createLinkObject];
- KVOObject *oldObj = obj.obj.obj;
- KVOObject *newObj = [self createObject];
- newObj.boolCol = YES;
- KVORecorder r(self, obj, @"obj.obj.boolCol");
- obj.obj.obj = newObj;
- AssertChanged(r, @NO, @YES);
- newObj.boolCol = NO;
- AssertChanged(r, @YES, @NO);
- oldObj.boolCol = YES;
- }
- - (void)testNullifyMiddleOfKeyPath {
- KVOLinkObject2 *obj = [self createLinkObject];
- KVORecorder r(self, obj, @"obj.obj.boolCol");
- obj.obj = nil;
- AssertChanged(r, @NO, NSNull.null);
- }
- - (void)testChangeMiddleOfKeyPathToNonNil {
- KVOLinkObject2 *obj = [self createLinkObject];
- KVOLinkObject1 *obj2 = obj.obj;
- obj.obj = nil;
- obj2.obj.boolCol = YES;
- KVORecorder r(self, obj, @"obj.obj.boolCol");
- obj.obj = obj2;
- AssertChanged(r, NSNull.null, @YES);
- }
- - (void)testArrayKVC {
- KVOObject *obj = [self createObject];
- [obj.objectArray addObject:obj];
- KVORecorder r(self, obj, @"boolCol");
- [obj.objectArray setValue:@YES forKey:@"boolCol"];
- AssertChanged(r, @NO, @YES);
- }
- - (void)testSharedSchemaOnObservedObjectGivesOriginalSchema {
- KVOObject *obj = [self createObject];
- if (![obj isKindOfClass:RLMObjectBase.class]) {
- return;
- }
- RLMObjectSchema *original = [obj.class sharedSchema];
- KVORecorder r(self, obj, @"boolCol");
- XCTAssertEqual(original, [obj.class sharedSchema]); // note: intentionally not EqualObjects
- }
- // RLMArray doesn't support @count at all
- //- (void)testObserveArrayCount {
- // KVOObject *obj = [self createObject];
- // KVORecorder r(self, obj, @"objectArray.@count");
- // id mutator = [obj mutableArrayValueForKey:@"objectArray"];
- // [mutator addObject:obj];
- // AssertChanged(r, @0, @1);
- //}
- @end
- // Run tests on an unmanaged RLMObject instance
- @interface KVOUnmanagedObjectTests : KVOTests
- @end
- @implementation KVOUnmanagedObjectTests
- - (id)createObject {
- static int pk = 0;
- KVOObject *obj = [KVOObject new];
- obj.pk = pk++;
- obj.int16Col = 1;
- obj.int32Col = 2;
- obj.int64Col = 3;
- obj.binaryCol = NSData.data;
- obj.stringCol = @"";
- obj.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:0];
- return obj;
- }
- - (id)createLinkObject {
- static int pk = 0;
- KVOLinkObject1 *obj1 = [KVOLinkObject1 new];
- obj1.pk = pk++;
- obj1.obj = [self createObject];
- KVOLinkObject2 *obj2 = [KVOLinkObject2 new];
- obj2.pk = pk++;
- obj2.obj = obj1;
- return obj2;
- }
- - (void)testAddToRealmAfterAddingObservers {
- RLMRealm *realm = RLMRealm.defaultRealm;
- [realm beginWriteTransaction];
- KVOObject *obj = [self createObject];
- {
- KVORecorder r(self, obj, @"int32Col");
- XCTAssertThrows([realm addObject:obj]);
- }
- XCTAssertNoThrow([realm addObject:obj]);
- [realm cancelWriteTransaction];
- }
- - (void)testObserveInvalidArrayProperty {
- KVOObject *obj = [self createObject];
- XCTAssertThrows([obj.objectArray addObserver:self forKeyPath:@"self" options:0 context:0]);
- XCTAssertNoThrow([obj.objectArray addObserver:self forKeyPath:RLMInvalidatedKey options:0 context:0]);
- XCTAssertNoThrow([obj.objectArray removeObserver:self forKeyPath:RLMInvalidatedKey context:0]);
- }
- - (void)testUnregisteringViaAnAssociatedObject {
- @autoreleasepool {
- __attribute__((objc_precise_lifetime)) KVOObject *obj = [self createObject];
- [obj addObserver:self forKeyPath:@"boolCol" options:0 context:0];
- [KVOUnregisterHelper automaticallyUnregister:self object:obj keyPath:@"boolCol"];
- }
- // Throws if the unregistration doesn't succeed
- }
- @end
- // Run tests on a managed object, modifying the actual object instance being
- // observed
- @interface KVOManagedObjectTests : KVOTests
- @property (nonatomic, strong) RLMRealm *realm;
- @end
- @implementation KVOManagedObjectTests
- - (void)setUp {
- [super setUp];
- _realm = [self getRealm];
- [_realm beginWriteTransaction];
- }
- - (void)tearDown {
- [self.realm cancelWriteTransaction];
- self.realm = nil;
- [super tearDown];
- }
- - (RLMRealm *)getRealm {
- RLMRealmConfiguration *configuration = [[RLMRealmConfiguration alloc] init];
- configuration.inMemoryIdentifier = @"test";
- configuration.schemaMode = realm::SchemaMode::Additive;
- return [RLMRealm realmWithConfiguration:configuration error:nil];
- }
- - (id)createObject {
- static std::atomic<int> pk{0};
- return [KVOObject createInRealm:_realm withValue:@[@(++pk),
- @NO, @1, @2, @3, @0, @0, @NO, @"",
- NSData.data, [NSDate dateWithTimeIntervalSinceReferenceDate:0]]];
- }
- - (id)createLinkObject {
- static std::atomic<int> pk{0};
- return [KVOLinkObject2 createInRealm:_realm withValue:@[@(++pk), @[@(++pk), [self createObject], @[]], @[]]];
- }
- - (void)testDeleteObservedObject {
- KVOObject *obj = [self createObject];
- KVORecorder r1(self, obj, @"boolCol");
- KVORecorder r2(self, obj, RLMInvalidatedKey);
- [self.realm deleteObject:obj];
- AssertChanged(r2, @NO, @YES);
- // should not crash
- }
- - (void)testDeleteMultipleObservedObjects {
- KVOObject *obj1 = [self createObject];
- KVOObject *obj2 = [self createObject];
- KVOObject *obj3 = [self createObject];
- KVORecorder r1(self, obj1, RLMInvalidatedKey);
- KVORecorder r2(self, obj2, RLMInvalidatedKey);
- KVORecorder r3(self, obj3, RLMInvalidatedKey);
- [self.realm deleteObject:obj2];
- AssertChanged(r2, @NO, @YES);
- XCTAssertTrue(r1.empty());
- XCTAssertTrue(r3.empty());
- [self.realm deleteObject:obj3];
- AssertChanged(r3, @NO, @YES);
- XCTAssertTrue(r1.empty());
- XCTAssertTrue(r2.empty());
- [self.realm deleteObject:obj1];
- AssertChanged(r1, @NO, @YES);
- XCTAssertTrue(r2.empty());
- XCTAssertTrue(r3.empty());
- }
- - (void)testDeleteMiddleOfKeyPath {
- KVOLinkObject2 *obj = [self createLinkObject];
- KVORecorder r(self, obj, @"obj.obj.boolCol");
- [self.realm deleteObject:obj.obj];
- AssertChanged(r, @NO, NSNull.null);
- }
- - (void)testDeleteParentOfObservedRLMArray {
- KVOObject *obj = [self createObject];
- KVORecorder r1(self, obj, @"objectArray");
- KVORecorder r2(self, obj, @"objectArray.invalidated");
- KVORecorder r3(self, obj.objectArray, RLMInvalidatedKey);
- [self.realm deleteObject:obj];
- AssertChanged(r2, @NO, @YES);
- AssertChanged(r3, @NO, @YES);
- }
- - (void)testDeleteAllObjects {
- KVOObject *obj = [self createObject];
- KVORecorder r1(self, obj, @"boolCol");
- KVORecorder r2(self, obj, RLMInvalidatedKey);
- [self.realm deleteAllObjects];
- AssertChanged(r2, @NO, @YES);
- // should not crash
- }
- - (void)testClearTable {
- KVOObject *obj = [self createObject];
- KVORecorder r1(self, obj, @"boolCol");
- KVORecorder r2(self, obj, RLMInvalidatedKey);
- [self.realm deleteObjects:[KVOObject allObjectsInRealm:self.realm]];
- AssertChanged(r2, @NO, @YES);
- // should not crash
- }
- - (void)testClearQuery {
- KVOObject *obj = [self createObject];
- KVORecorder r1(self, obj, @"boolCol");
- KVORecorder r2(self, obj, RLMInvalidatedKey);
- [self.realm deleteObjects:[KVOObject objectsInRealm:self.realm where:@"TRUEPREDICATE"]];
- AssertChanged(r2, @NO, @YES);
- // should not crash
- }
- - (void)testClearLinkView {
- KVOObject *obj = [self createObject];
- KVOObject *obj2 = [self createObject];
- [obj2.objectArray addObject:obj];
- KVORecorder r1(self, obj, @"boolCol");
- KVORecorder r2(self, obj, RLMInvalidatedKey);
- [self.realm deleteObjects:obj2.objectArray];
- AssertChanged(r2, @NO, @YES);
- // should not crash
- }
- - (void)testCreateObserverAfterDealloc {
- @autoreleasepool {
- KVOObject *obj = [self createObject];
- KVORecorder r(self, obj, @"boolCol");
- obj.boolCol = YES;
- AssertChanged(r, @NO, @YES);
- }
- @autoreleasepool {
- KVOObject *obj = [self createObject];
- KVORecorder r(self, obj, @"boolCol");
- obj.boolCol = YES;
- AssertChanged(r, @NO, @YES);
- }
- }
- - (void)testDirectlyDeleteLinkedToObject {
- KVOLinkObject2 *obj = [self createLinkObject];
- KVOLinkObject1 *linked = obj.obj;
- KVORecorder r(self, obj, @"obj");
- KVORecorder r2(self, obj, @"obj.invalidated");
- [self.realm deleteObject:linked];
- if (NSDictionary *note = AssertNotification(r)) {
- XCTAssertTrue([note[NSKeyValueChangeOldKey] isKindOfClass:[RLMObjectBase class]]);
- XCTAssertEqualObjects(note[NSKeyValueChangeNewKey], NSNull.null);
- }
- AssertChanged(r2, @NO, NSNull.null);
- }
- - (void)testDeleteLinkedToObjectViaTableClear {
- KVOLinkObject2 *obj = [self createLinkObject];
- KVORecorder r(self, obj, @"obj");
- KVORecorder r2(self, obj, @"obj.invalidated");
- [self.realm deleteObjects:[KVOLinkObject1 allObjectsInRealm:self.realm]];
- if (NSDictionary *note = AssertNotification(r)) {
- XCTAssertTrue([note[NSKeyValueChangeOldKey] isKindOfClass:[RLMObjectBase class]]);
- XCTAssertEqualObjects(note[NSKeyValueChangeNewKey], NSNull.null);
- }
- AssertChanged(r2, @NO, NSNull.null);
- }
- - (void)testDeleteLinkedToObjectViaQueryClear {
- KVOLinkObject2 *obj = [self createLinkObject];
- KVORecorder r(self, obj, @"obj");
- KVORecorder r2(self, obj, @"obj.invalidated");
- [self.realm deleteObjects:[KVOLinkObject1 objectsInRealm:self.realm where:@"TRUEPREDICATE"]];
- if (NSDictionary *note = AssertNotification(r)) {
- XCTAssertTrue([note[NSKeyValueChangeOldKey] isKindOfClass:[RLMObjectBase class]]);
- XCTAssertEqualObjects(note[NSKeyValueChangeNewKey], NSNull.null);
- }
- AssertChanged(r2, @NO, NSNull.null);
- }
- - (void)testDeleteObjectInArray {
- KVOLinkObject2 *obj = [self createLinkObject];
- KVOLinkObject1 *linked = obj.obj;
- [obj.array addObject:linked];
- KVORecorder r(self, obj, @"array");
- [self.realm deleteObject:linked];
- AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndex:0]);
- }
- - (void)testDeleteObjectsInArrayViaTableClear {
- KVOLinkObject2 *obj = [self createLinkObject];
- KVOLinkObject2 *obj2 = [self createLinkObject];
- [obj.array addObject:obj.obj];
- [obj.array addObject:obj.obj];
- [obj.array addObject:obj2.obj];
- KVORecorder r(self, obj, @"array");
- [self.realm deleteObjects:[KVOLinkObject1 allObjectsInRealm:self.realm]];
- AssertIndexChange(NSKeyValueChangeRemoval, ([NSIndexSet indexSetWithIndexesInRange:{0, 3}]));
- }
- - (void)testDeleteObjectsInArrayViaTableViewClear {
- KVOLinkObject2 *obj = [self createLinkObject];
- KVOLinkObject2 *obj2 = [self createLinkObject];
- [obj.array addObject:obj2.obj];
- [obj.array addObject:obj.obj];
- [obj.array addObject:obj.obj];
- KVORecorder r(self, obj, @"array");
- RLMResults *results = [KVOLinkObject1 objectsInRealm:self.realm where:@"TRUEPREDICATE"];
- [results lastObject];
- [self.realm deleteObjects:results];
- AssertIndexChange(NSKeyValueChangeRemoval, ([NSIndexSet indexSetWithIndexesInRange:{0, 3}]));
- }
- - (void)testDeleteObjectsInArrayViaQueryClear {
- KVOLinkObject2 *obj = [self createLinkObject];
- KVOLinkObject2 *obj2 = [self createLinkObject];
- [obj.array addObject:obj.obj];
- [obj.array addObject:obj.obj];
- [obj.array addObject:obj2.obj];
- KVORecorder r(self, obj, @"array");
- [self.realm deleteObjects:[KVOLinkObject1 objectsInRealm:self.realm where:@"TRUEPREDICATE"]];
- AssertIndexChange(NSKeyValueChangeRemoval, ([NSIndexSet indexSetWithIndexesInRange:{0, 3}]));
- }
- - (void)testObserveInvalidArrayProperty {
- KVOObject *obj = [self createObject];
- RLMArray *array = obj.objectArray;
- XCTAssertThrows([array addObserver:self forKeyPath:@"self" options:0 context:0]);
- XCTAssertNoThrow([array addObserver:self forKeyPath:RLMInvalidatedKey options:0 context:0]);
- XCTAssertNoThrow([array removeObserver:self forKeyPath:RLMInvalidatedKey context:0]);
- }
- - (void)testInvalidOperationOnObservedArray {
- KVOLinkObject2 *obj = [self createLinkObject];
- KVOLinkObject1 *linked = obj.obj;
- [obj.array addObject:linked];
- KVORecorder r(self, obj, @"array");
- XCTAssertThrows([obj.array exchangeObjectAtIndex:2 withObjectAtIndex:3]);
- // A KVO notification is still sent to observers on the same thread since we
- // can't cancel willChange, but the data is not very meaningful so don't check it
- if (!self.collapsesNotifications) {
- AssertNotification(r);
- }
- }
- @end
- // Mutate a different accessor backed by the same row as the accessor being observed
- @interface KVOMultipleAccessorsTests : KVOManagedObjectTests
- @end
- @implementation KVOMultipleAccessorsTests
- - (id)observableForObject:(id)value {
- if (RLMObject *obj = RLMDynamicCast<RLMObject>(value)) {
- RLMObject *copy = RLMCreateManagedAccessor(obj.objectSchema.accessorClass, obj->_info);
- copy->_row = obj->_row;
- return copy;
- }
- else if (RLMArray *array = RLMDynamicCast<RLMArray>(value)) {
- return array;
- }
- else {
- XCTFail(@"unsupported type");
- return nil;
- }
- }
- - (void)testIgnoredProperty {
- // ignored properties do not notify other accessors for the same row
- }
- - (void)testAddOrUpdate {
- KVOObject *obj = [self createObject];
- KVOObject *obj2 = [[KVOObject alloc] initWithValue:obj];
- KVORecorder r(self, obj, @"boolCol");
- obj2.boolCol = true;
- XCTAssertTrue(r.empty());
- [self.realm addOrUpdateObject:obj2];
- AssertChanged(r, @NO, @YES);
- }
- - (void)testCreateOrUpdate {
- KVOObject *obj = [self createObject];
- KVOObject *obj2 = [[KVOObject alloc] initWithValue:obj];
- KVORecorder r(self, obj, @"boolCol");
- obj2.boolCol = true;
- XCTAssertTrue(r.empty());
- [KVOObject createOrUpdateInRealm:self.realm withValue:obj2];
- AssertChanged(r, @NO, @YES);
- }
- // The following tests aren't really multiple-accessor-specific, but they're
- // conceptually similar and don't make sense in the multiple realm instances case
- - (void)testCancelWriteTransactionWhileObservingNewObject {
- KVOObject *obj = [self createObject];
- KVORecorder r(self, obj, RLMInvalidatedKey);
- KVORecorder r2(self, obj, @"boolCol");
- [self.realm cancelWriteTransaction];
- AssertChanged(r, @NO, @YES);
- r2.pop_front();
- [self.realm beginWriteTransaction];
- }
- - (void)testCancelWriteTransactionWhileObservingChangedProperty {
- KVOObject *obj = [self createObject];
- [self.realm commitWriteTransaction];
- [self.realm beginWriteTransaction];
- obj.boolCol = YES;
- KVORecorder r(self, obj, @"boolCol");
- [self.realm cancelWriteTransaction];
- AssertChanged(r, @YES, @NO);
- [self.realm beginWriteTransaction];
- }
- - (void)testCancelWriteTransactionWhileObservingLinkToExistingObject {
- KVOObject *obj = [self createObject];
- KVOObject *obj2 = [self createObject];
- [self.realm commitWriteTransaction];
- [self.realm beginWriteTransaction];
- obj.objectCol = obj2;
- KVORecorder r(self, obj, @"objectCol");
- [self.realm cancelWriteTransaction];
- AssertChanged(r, obj2, NSNull.null);
- [self.realm beginWriteTransaction];
- }
- - (void)testCancelWriteTransactionWhileObservingLinkToNewObject {
- KVOObject *obj = [self createObject];
- [self.realm commitWriteTransaction];
- [self.realm beginWriteTransaction];
- obj.objectCol = [self createObject];
- KVORecorder r(self, obj, @"objectCol");
- [self.realm cancelWriteTransaction];
- if (NSDictionary *note = AssertNotification(r)) {
- XCTAssertTrue([note[NSKeyValueChangeOldKey] isKindOfClass:[RLMObjectBase class]]);
- XCTAssertEqualObjects(note[NSKeyValueChangeNewKey], NSNull.null);
- }
- [self.realm beginWriteTransaction];
- }
- - (void)testCancelWriteTransactionWhileObservingNewObjectLinkingToNewObject {
- KVOObject *obj = [self createObject];
- obj.objectCol = [self createObject];
- KVORecorder r(self, obj, RLMInvalidatedKey);
- KVORecorder r2(self, obj, @"objectCol");
- KVORecorder r3(self, obj, @"objectCol.boolCol");
- [self.realm cancelWriteTransaction];
- AssertChanged(r, @NO, @YES);
- [self.realm beginWriteTransaction];
- }
- - (void)testCancelWriteWithArrayChanges {
- KVOObject *obj = [self createObject];
- [obj.objectArray addObject:obj];
- [self.realm commitWriteTransaction];
- [self.realm beginWriteTransaction];
- {
- [obj.objectArray addObject:obj];
- KVORecorder r(self, obj, @"objectArray");
- [self.realm cancelWriteTransaction];
- [self.realm beginWriteTransaction];
- AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndex:1]);
- }
- {
- [obj.objectArray removeLastObject];
- KVORecorder r(self, obj, @"objectArray");
- [self.realm cancelWriteTransaction];
- [self.realm beginWriteTransaction];
- AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndex:0]);
- }
- {
- obj.objectArray[0] = obj;
- KVORecorder r(self, obj, @"objectArray");
- [self.realm cancelWriteTransaction];
- [self.realm beginWriteTransaction];
- AssertIndexChange(NSKeyValueChangeReplacement, [NSIndexSet indexSetWithIndex:0]);
- }
- // test batching with multiple items changed
- [obj.objectArray addObject:obj];
- [self.realm commitWriteTransaction];
- [self.realm beginWriteTransaction];
- {
- [obj.objectArray removeAllObjects];
- KVORecorder r(self, obj, @"objectArray");
- [self.realm cancelWriteTransaction];
- [self.realm beginWriteTransaction];
- AssertIndexChange(NSKeyValueChangeInsertion, ([NSIndexSet indexSetWithIndexesInRange:{0, 2}]));
- }
- {
- [obj.objectArray removeLastObject];
- [obj.objectArray removeLastObject];
- KVORecorder r(self, obj, @"objectArray");
- [self.realm cancelWriteTransaction];
- [self.realm beginWriteTransaction];
- AssertIndexChange(NSKeyValueChangeInsertion, ([NSIndexSet indexSetWithIndexesInRange:{0, 2}]));
- }
- {
- [obj.objectArray insertObject:obj atIndex:1];
- [obj.objectArray insertObject:obj atIndex:0];
- KVORecorder r(self, obj, @"objectArray");
- [self.realm cancelWriteTransaction];
- [self.realm beginWriteTransaction];
- NSMutableIndexSet *expected = [NSMutableIndexSet new];
- [expected addIndex:0];
- [expected addIndex:2]; // shifted due to inserting at 0 after 1
- AssertIndexChange(NSKeyValueChangeRemoval, expected);
- }
- {
- [obj.objectArray insertObject:obj atIndex:0];
- [obj.objectArray removeLastObject];
- KVORecorder r(self, obj, @"objectArray");
- [self.realm cancelWriteTransaction];
- [self.realm beginWriteTransaction];
- AssertChanged(r, obj.objectArray, obj.objectArray);
- }
- }
- - (void)testCancelWriteWithPrimitiveArrayChanges {
- KVOObject *obj = [self createObject];
- [obj.intArray addObject:@1];
- [self.realm commitWriteTransaction];
- [self.realm beginWriteTransaction];
- {
- [obj.intArray addObject:@2];
- KVORecorder r(self, obj, @"intArray");
- [self.realm cancelWriteTransaction];
- [self.realm beginWriteTransaction];
- AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndex:1]);
- }
- {
- [obj.intArray removeLastObject];
- KVORecorder r(self, obj, @"intArray");
- [self.realm cancelWriteTransaction];
- [self.realm beginWriteTransaction];
- AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndex:0]);
- }
- {
- obj.intArray[0] = @3;
- KVORecorder r(self, obj, @"intArray");
- [self.realm cancelWriteTransaction];
- [self.realm beginWriteTransaction];
- AssertIndexChange(NSKeyValueChangeReplacement, [NSIndexSet indexSetWithIndex:0]);
- }
- // test batching with multiple items changed
- [obj.intArray addObject:@4];
- [self.realm commitWriteTransaction];
- [self.realm beginWriteTransaction];
- {
- [obj.intArray removeAllObjects];
- KVORecorder r(self, obj, @"intArray");
- [self.realm cancelWriteTransaction];
- [self.realm beginWriteTransaction];
- AssertIndexChange(NSKeyValueChangeInsertion, ([NSIndexSet indexSetWithIndexesInRange:{0, 2}]));
- }
- {
- [obj.intArray removeLastObject];
- [obj.intArray removeLastObject];
- KVORecorder r(self, obj, @"intArray");
- [self.realm cancelWriteTransaction];
- [self.realm beginWriteTransaction];
- AssertIndexChange(NSKeyValueChangeInsertion, ([NSIndexSet indexSetWithIndexesInRange:{0, 2}]));
- }
- {
- [obj.intArray insertObject:@5 atIndex:1];
- [obj.intArray insertObject:@6 atIndex:0];
- KVORecorder r(self, obj, @"intArray");
- [self.realm cancelWriteTransaction];
- [self.realm beginWriteTransaction];
- NSMutableIndexSet *expected = [NSMutableIndexSet new];
- [expected addIndex:0];
- [expected addIndex:2]; // shifted due to inserting at 0 after 1
- AssertIndexChange(NSKeyValueChangeRemoval, expected);
- }
- {
- [obj.intArray insertObject:@7 atIndex:0];
- [obj.intArray removeLastObject];
- KVORecorder r(self, obj, @"intArray");
- [self.realm cancelWriteTransaction];
- [self.realm beginWriteTransaction];
- AssertChanged(r, obj.intArray, obj.intArray);
- }
- }
- - (void)testCancelWriteWithLinkedObjectedRemoved {
- KVOLinkObject2 *obj = [self createLinkObject];
- [obj.array addObject:obj.obj];
- [self.realm commitWriteTransaction];
- [self.realm beginWriteTransaction];
- {
- [self.realm deleteObject:obj.obj];
- KVORecorder r(self, obj, @"array");
- KVORecorder r2(self, obj, @"obj");
- [self.realm cancelWriteTransaction];
- [self.realm beginWriteTransaction];
- AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndex:0]);
- AssertChanged(r2, NSNull.null, [KVOLinkObject1 allObjectsInRealm:self.realm].firstObject);
- }
- {
- [self.realm deleteObjects:[KVOLinkObject1 allObjectsInRealm:self.realm]];
- KVORecorder r(self, obj, @"array");
- KVORecorder r2(self, obj, @"obj");
- [self.realm cancelWriteTransaction];
- [self.realm beginWriteTransaction];
- AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndex:0]);
- AssertChanged(r2, NSNull.null, [KVOLinkObject1 allObjectsInRealm:self.realm].firstObject);
- }
- {
- [self.realm deleteObjects:obj.array];
- KVORecorder r(self, obj, @"array");
- KVORecorder r2(self, obj, @"obj");
- [self.realm cancelWriteTransaction];
- [self.realm beginWriteTransaction];
- AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndex:0]);
- AssertChanged(r2, NSNull.null, [KVOLinkObject1 allObjectsInRealm:self.realm].firstObject);
- }
- }
- - (void)testInvalidateRealm {
- KVOObject *obj = [self createObject];
- [self.realm commitWriteTransaction];
- KVORecorder r1(self, obj, RLMInvalidatedKey);
- KVORecorder r2(self, obj, @"objectArray.invalidated");
- [self.realm invalidate];
- [self.realm beginWriteTransaction];
- AssertChanged(r1, @NO, @YES);
- AssertChanged(r2, @NO, @YES);
- }
- - (void)testRenamedProperties {
- auto obj = [RenamedProperties1 createInRealm:self.realm withValue:@[@1, @"a"]];
- [self.realm commitWriteTransaction];
- [self.realm beginWriteTransaction];
- KVORecorder r(self, obj, @"propA");
- obj.propA = 2;
- AssertChanged(r, @1, @2);
- obj[@"propA"] = @3;
- AssertChanged(r, @2, @3);
- [obj setValue:@4 forKey:@"propA"];
- AssertChanged(r, @3, @4);
- // Only rollback will notify objects of different types with the same table,
- // not direct modification. Probably not worth fixing this.
- RenamedProperties2 *obj2 = [RenamedProperties2 allObjectsInRealm:self.realm].firstObject;
- KVORecorder r2(self, obj2, @"propC");
- [self.realm cancelWriteTransaction];
- [self.realm beginWriteTransaction];
- AssertChanged(r, @4, @1);
- AssertChanged(r2, @4, @1);
- }
- @end
- // Observing an object from a different RLMRealm instance backed by the same
- // row as the managed object being mutated
- @interface KVOMultipleRealmsTests : KVOManagedObjectTests
- @property RLMRealm *secondaryRealm;
- @end
- @implementation KVOMultipleRealmsTests
- - (void)setUp {
- [super setUp];
- RLMRealmConfiguration *config = self.realm.configuration;
- config.cache = false;
- self.secondaryRealm = [RLMRealm realmWithConfiguration:config error:nil];
- }
- - (void)tearDown {
- self.secondaryRealm = nil;
- [super tearDown];
- }
- - (id)observableForObject:(id)value {
- [self.realm commitWriteTransaction];
- [self.realm beginWriteTransaction];
- [self.secondaryRealm refresh];
- if (RLMObject *obj = RLMDynamicCast<RLMObject>(value)) {
- RLMObject *copy = RLMCreateManagedAccessor(obj.objectSchema.accessorClass,
- &self.secondaryRealm->_info[obj.objectSchema.className]);
- copy->_row = (*copy->_info->table()).get_object(obj->_row.get_key());
- return copy;
- }
- else if (RLMArray *array = RLMDynamicCast<RLMArray>(value)) {
- return array;
- }
- else {
- XCTFail(@"unsupported type");
- return nil;
- }
- }
- - (bool)collapsesNotifications {
- return true;
- }
- - (void)testIgnoredProperty {
- // ignored properties do not notify other accessors for the same row
- }
- - (void)testBatchArrayChanges {
- KVOObject *obj = [self createObject];
- [obj.objectArray addObject:obj];
- [obj.objectArray addObject:obj];
- [obj.objectArray addObject:obj];
- {
- KVORecorder r(self, obj, @"objectArray");
- [obj.objectArray insertObject:obj atIndex:1];
- [obj.objectArray insertObject:obj atIndex:0];
- NSMutableIndexSet *expected = [NSMutableIndexSet new];
- [expected addIndex:0];
- [expected addIndex:2]; // shifted due to inserting at 0 after 1
- AssertIndexChange(NSKeyValueChangeInsertion, expected);
- }
- {
- KVORecorder r(self, obj, @"objectArray");
- [obj.objectArray removeObjectAtIndex:3];
- [obj.objectArray removeObjectAtIndex:3];
- AssertIndexChange(NSKeyValueChangeRemoval, ([NSIndexSet indexSetWithIndexesInRange:{3, 2}]));
- }
- {
- KVORecorder r(self, obj, @"objectArray");
- [obj.objectArray removeObjectAtIndex:0];
- [obj.objectArray removeAllObjects];
- AssertIndexChange(NSKeyValueChangeRemoval, ([NSIndexSet indexSetWithIndexesInRange:{0, 3}]));
- }
- [obj.objectArray addObject:obj];
- {
- KVORecorder r(self, obj, @"objectArray");
- [obj.objectArray addObject:obj];
- [obj.objectArray removeAllObjects];
- AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndex:0]);
- }
- [obj.objectArray addObject:obj];
- {
- KVORecorder r(self, obj, @"objectArray");
- obj.objectArray[0] = obj;
- [obj.objectArray removeAllObjects];
- AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndex:0]);
- }
- }
- - (void)testOrderedErase {
- NSMutableArray *objects = [NSMutableArray arrayWithCapacity:10];
- for (int i = 0; i < 10; ++i) @autoreleasepool {
- [objects addObject:[ObjectWithNoLinksToOrFrom createInRealm:self.realm withValue:@[@(i)]]];
- }
- // deleteObject: always uses move_last_over(), but TableView::clear() uses
- // erase() if there's no links
- auto deleteObject = ^(int value) {
- [self.realm deleteObjects:[ObjectWithNoLinksToOrFrom objectsInRealm:self.realm where:@"value = %d", value]];
- };
- { // delete object before observed, then observed
- KVORecorder r(self, objects[2], @"invalidated");
- deleteObject(1);
- deleteObject(2);
- AssertChanged(r, @NO, @YES);
- }
- { // delete object after observed, then observed
- KVORecorder r(self, objects[3], @"invalidated");
- deleteObject(4);
- deleteObject(3);
- AssertChanged(r, @NO, @YES);
- }
- { // delete observed, then object before observed
- KVORecorder r(self, objects[6], @"invalidated");
- deleteObject(6);
- deleteObject(5);
- AssertChanged(r, @NO, @YES);
- }
- { // delete observed, then object after observed
- KVORecorder r(self, objects[7], @"invalidated");
- deleteObject(7);
- deleteObject(8);
- AssertChanged(r, @NO, @YES);
- }
- }
- @end
- // Test with the table column order not matching the order of the properties
- @interface KVOManagedObjectWithReorderedPropertiesTests : KVOManagedObjectTests
- @end
- @implementation KVOManagedObjectWithReorderedPropertiesTests
- - (RLMRealm *)getRealm {
- // Initialize the file with the properties in reverse order, then re-open
- // with it in the normal order while the reversed one is still open (as
- // otherwise it'll recreate the file due to being in-memory)
- RLMSchema *schema = [RLMSchema new];
- schema.objectSchema = @[[self reverseProperties:KVOObject.sharedSchema],
- [self reverseProperties:KVOLinkObject1.sharedSchema],
- [self reverseProperties:KVOLinkObject2.sharedSchema]];
- RLMRealmConfiguration *configuration = [[RLMRealmConfiguration alloc] init];
- configuration.cache = false;
- configuration.inMemoryIdentifier = @"test";
- configuration.customSchema = schema;
- RLMRealm *reversedRealm = [RLMRealm realmWithConfiguration:configuration error:nil];
- configuration.customSchema = nil;
- RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil];
- XCTAssertNotEqualObjects(realm.schema, reversedRealm.schema);
- return realm;
- }
- - (RLMObjectSchema *)reverseProperties:(RLMObjectSchema *)source {
- RLMObjectSchema *objectSchema = [source copy];
- objectSchema.properties = objectSchema.properties.reverseObjectEnumerator.allObjects;
- return objectSchema;
- }
- @end
|