1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077 |
- ////////////////////////////////////////////////////////////////////////////
- //
- // 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/descriptor.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);
- }
- // 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];
- }
- // 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);
- }
- // 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)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:obj.obj];
- [obj.array addObject:obj.obj];
- [obj.array addObject:obj2.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.realm, 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,
- &self.secondaryRealm->_info[obj.objectSchema.className]);
- copy->_row = (*copy->_info->table())[obj->_row.get_index()];
- 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);
- }
- }
- - (void)testInsertNewTables {
- KVOObject *obj = [self createObject];
- KVORecorder r1(self, obj, @"boolCol");
- KVORecorder r2(self, obj, @"int32Col");
- obj.boolCol = YES;
- // Add tables before the observed one so that the observed one's index changes
- realm::Group &group = self.realm->_realm->read_group();
- realm::TableRef table1 = group.insert_table(5, "new table");
- realm::TableRef table2 = group.insert_table(0, "new table 2");
- table1->add_column(realm::type_Int, "col");
- table2->add_column(realm::type_Int, "col");
- obj.int32Col = 3;
- AssertChanged(r1, @NO, @YES);
- AssertChanged(r2, @2, @3);
- }
- - (void)testInsertNewColumns {
- KVOObject *obj = [self createObject];
- KVORecorder r1(self, obj, @"boolCol");
- KVORecorder r2(self, obj, @"int32Col");
- auto ndx = obj->_info->tableColumn(@"int32Col");
- // Add a column before the observed one so that the observed one's index changes
- obj.boolCol = YES;
- auto& table = *obj->_info->table();
- table.insert_column(0, realm::type_Binary, "new col");
- table.insert_column(ndx, realm::type_Binary, "new col 2");
- obj->_row.set_int(ndx + 2, 3); // can't use the accessor after a local schema change
- AssertChanged(r1, @NO, @YES);
- AssertChanged(r2, @2, @3);
- }
- - (void)testShiftObservedColumnBeforeChange {
- KVOObject *obj = [self createObject];
- auto ndx = obj->_info->tableColumn(@"boolCol");
- KVORecorder r(self, obj, @"boolCol");
- obj->_info->table()->insert_column(0, realm::type_Binary, "new col");
- obj->_row.set_bool(ndx + 1, true); // can't use the accessor after a local schema change
- AssertChanged(r, @NO, @YES);
- }
- - (void)testShiftObservedColumnAfterChange {
- KVOObject *obj = [self createObject];
- KVORecorder r(self, obj, @"boolCol");
- obj.boolCol = YES;
- obj->_info->table()->insert_column(0, realm::type_Binary, "new col");
- AssertChanged(r, @NO, @YES);
- }
- - (void)testSwapRowsIsNotAChange {
- KVOObject *obj = [self createObject];
- [self createObject];
- KVORecorder r(self, obj, @"boolCol");
- obj->_info->table()->swap_rows(0, 1);
- r.refresh();
- XCTAssertTrue(r.empty());
- }
- - (void)testSwapRowsBeforeChange {
- KVOObject *obj = [self createObject];
- [self createObject];
- KVORecorder r(self, obj, @"boolCol");
- obj->_info->table()->swap_rows(0, 1);
- obj.boolCol = YES;
- AssertChanged(r, @NO, @YES);
- }
- - (void)testSwapRowsAfterChange {
- KVOObject *obj = [self createObject];
- [self createObject];
- KVORecorder r(self, obj, @"boolCol");
- obj.boolCol = YES;
- obj->_info->table()->swap_rows(0, 1);
- AssertChanged(r, @NO, @YES);
- }
- - (void)testSwapRowsBeforeArrayChange {
- KVOObject *obj = [self createObject];
- [self createObject];
- KVORecorder r(self, obj, @"objectArray");
- obj->_info->table()->swap_rows(0, 1);
- [obj.objectArray addObject:obj];
- AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndex:0]);
- }
- - (void)testSwapRowsAfterArrayChange {
- KVOObject *obj = [self createObject];
- [self createObject];
- KVORecorder r(self, obj, @"objectArray");
- [obj.objectArray addObject:obj];
- obj->_info->table()->swap_rows(0, 1);
- AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndex:0]);
- }
- @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
|