ObjectTests.m 61 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472
  1. ////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright 2014 Realm Inc.
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License");
  6. // you may not use this file except in compliance with the License.
  7. // You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. //
  17. ////////////////////////////////////////////////////////////////////////////
  18. #import "RLMTestCase.h"
  19. #import "RLMObjectSchema_Private.h"
  20. #import "RLMSchema_Private.h"
  21. #import <libkern/OSAtomic.h>
  22. #import <math.h>
  23. #import <objc/runtime.h>
  24. #import <stdalign.h>
  25. #pragma mark - Test Objects
  26. @interface DefaultObject : RLMObject
  27. @property int intCol;
  28. @property float floatCol;
  29. @property double doubleCol;
  30. @property BOOL boolCol;
  31. @property NSDate *dateCol;
  32. @property NSString *stringCol;
  33. @property NSData *binaryCol;
  34. @end
  35. @implementation DefaultObject
  36. + (NSDictionary *)defaultPropertyValues {
  37. NSString *binaryString = @"binary";
  38. NSData *binaryData = [binaryString dataUsingEncoding:NSUTF8StringEncoding];
  39. return @{@"intCol": @12,
  40. @"floatCol": @88.9f,
  41. @"doubleCol": @1002.892,
  42. @"boolCol": @YES,
  43. @"dateCol": [NSDate dateWithTimeIntervalSince1970:999999],
  44. @"stringCol": @"potato",
  45. @"binaryCol": binaryData};
  46. }
  47. @end
  48. @interface DynamicDefaultObject : RLMObject
  49. @property int intCol;
  50. @property float floatCol;
  51. @property double doubleCol;
  52. @property NSDate *dateCol;
  53. @property NSString *stringCol;
  54. @property NSData *binaryCol;
  55. @end
  56. @implementation DynamicDefaultObject
  57. + (BOOL)shouldIncludeInDefaultSchema {
  58. return NO;
  59. }
  60. + (NSDictionary *)defaultPropertyValues {
  61. static NSInteger dynamicDefaultSeed = 0;
  62. dynamicDefaultSeed++;
  63. return @{@"intCol": @(dynamicDefaultSeed),
  64. @"floatCol": @((float)dynamicDefaultSeed),
  65. @"doubleCol": @((double)dynamicDefaultSeed),
  66. @"dateCol": [NSDate dateWithTimeIntervalSince1970:dynamicDefaultSeed],
  67. @"stringCol": [[NSUUID UUID] UUIDString],
  68. @"binaryCol": [[[NSUUID UUID] UUIDString] dataUsingEncoding:NSUTF8StringEncoding]};
  69. }
  70. + (NSString *)primaryKey {
  71. return @"intCol";
  72. }
  73. @end
  74. @class CycleObject;
  75. RLM_ARRAY_TYPE(CycleObject)
  76. @interface CycleObject : RLMObject
  77. @property RLM_GENERIC_ARRAY(CycleObject) *objects;
  78. @end
  79. @implementation CycleObject
  80. @end
  81. @interface PrimaryStringObjectWrapper : RLMObject
  82. @property PrimaryStringObject *primaryStringObject;
  83. @end
  84. @implementation PrimaryStringObjectWrapper
  85. @end
  86. @interface PrimaryNestedObject : RLMObject
  87. @property int primaryCol;
  88. @property PrimaryStringObject *primaryStringObject;
  89. @property PrimaryStringObjectWrapper *primaryStringObjectWrapper;
  90. @property StringObject *stringObject;
  91. @property RLM_GENERIC_ARRAY(PrimaryIntObject) *primaryIntArray;
  92. @property NSString *stringCol;
  93. @end
  94. @implementation PrimaryNestedObject
  95. + (NSString *)primaryKey {
  96. return @"primaryCol";
  97. }
  98. + (NSDictionary *)defaultPropertyValues {
  99. return @{@"stringCol": @"default"};
  100. }
  101. @end
  102. @interface StringSubclassObject : StringObject
  103. @property NSString *stringCol2;
  104. @end
  105. @implementation StringSubclassObject
  106. @end
  107. @interface StringObjectNoThrow : StringObject
  108. @end
  109. @implementation StringObjectNoThrow
  110. - (id)valueForUndefinedKey:(__unused NSString *)key {
  111. return nil;
  112. }
  113. @end
  114. @interface StringSubclassObjectWithDefaults : StringObjectNoThrow
  115. @property NSString *stringCol2;
  116. @end
  117. @implementation StringSubclassObjectWithDefaults
  118. +(NSDictionary *)defaultPropertyValues {
  119. return @{@"stringCol2": @"default"};
  120. }
  121. @end
  122. @interface StringLinkObject : RLMObject
  123. @property StringObject *stringObjectCol;
  124. @property RLM_GENERIC_ARRAY(StringObject) *stringObjectArrayCol;
  125. @end
  126. @implementation StringLinkObject
  127. @end
  128. @interface ReadOnlyPropertyObject ()
  129. @property (readwrite) int readOnlyPropertyMadeReadWriteInClassExtension;
  130. @end
  131. @interface DataObject : RLMObject
  132. @property NSData *data1;
  133. @property NSData *data2;
  134. @end
  135. @implementation DataObject
  136. @end
  137. @interface DateObjectNoThrow : DateObject
  138. @property NSDate *date2;
  139. @end
  140. @implementation DateObjectNoThrow
  141. - (id)valueForUndefinedKey:(__unused NSString *)key {
  142. return nil;
  143. }
  144. @end
  145. @interface DateSubclassObject : DateObjectNoThrow
  146. @property NSDate *date3;
  147. @end
  148. @implementation DateSubclassObject
  149. @end
  150. @interface DateDefaultsObject : DateObjectNoThrow
  151. @property NSDate *date3;
  152. @end
  153. @implementation DateDefaultsObject
  154. + (NSDictionary *)defaultPropertyValues {
  155. return @{@"date3": [NSDate date]};
  156. }
  157. @end
  158. @interface SubclassDateObject : NSObject
  159. @property NSDate *dateCol;
  160. @property (getter=customGetter) NSDate *date2;
  161. @property (setter=customSetter:) NSDate *date3;
  162. @end
  163. @implementation SubclassDateObject
  164. @end
  165. #pragma mark - Tests
  166. @interface ObjectTests : RLMTestCase
  167. @end
  168. @implementation ObjectTests
  169. - (void)testKeyedSubscripting {
  170. EmployeeObject *objs = [[EmployeeObject alloc] initWithValue:@{@"name": @"Test0", @"age": @23, @"hired": @NO}];
  171. XCTAssertEqualObjects(objs[@"name"], @"Test0", @"Name should be Test0");
  172. XCTAssertEqualObjects(objs[@"age"], @23, @"age should be 23");
  173. XCTAssertEqualObjects(objs[@"hired"], @NO, @"hired should be NO");
  174. objs[@"name"] = @"Test1";
  175. XCTAssertEqualObjects(objs.name, @"Test1", @"Name should be Test1");
  176. RLMRealm *realm = [RLMRealm defaultRealm];
  177. [realm beginWriteTransaction];
  178. EmployeeObject *obj0 = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Test1", @"age": @24, @"hired": @NO}];
  179. EmployeeObject *obj1 = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Test2", @"age": @25, @"hired": @YES}];
  180. [realm commitWriteTransaction];
  181. XCTAssertEqualObjects(obj0[@"name"], @"Test1", @"Name should be Test1");
  182. XCTAssertEqualObjects(obj1[@"name"], @"Test2", @"Name should be Test1");
  183. [realm beginWriteTransaction];
  184. obj0[@"name"] = @"newName";
  185. [realm commitWriteTransaction];
  186. XCTAssertEqualObjects(obj0[@"name"], @"newName", @"Name should be newName");
  187. [realm beginWriteTransaction];
  188. obj0[@"name"] = nil;
  189. [realm commitWriteTransaction];
  190. XCTAssertNil(obj0[@"name"]);
  191. }
  192. - (void)testCannotUpdatePrimaryKey {
  193. PrimaryIntObject *intObj = [[PrimaryIntObject alloc] init];
  194. intObj.intCol = 1;
  195. XCTAssertNoThrow(intObj.intCol = 0);
  196. PrimaryStringObject *stringObj = [[PrimaryStringObject alloc] init];
  197. stringObj.stringCol = @"a";
  198. XCTAssertNoThrow(stringObj.stringCol = @"b");
  199. RLMRealm *realm = [RLMRealm defaultRealm];
  200. [realm beginWriteTransaction];
  201. [realm addObject:intObj];
  202. RLMAssertThrowsWithReason(intObj.intCol = 1, @"Primary key can't be changed");
  203. RLMAssertThrowsWithReason(intObj[@"intCol"] = @1, @"Primary key can't be changed");
  204. RLMAssertThrowsWithReason([intObj setValue:@1 forKey:@"intCol"], @"Primary key can't be changed");
  205. [realm addObject:stringObj];
  206. RLMAssertThrowsWithReason(stringObj.stringCol = @"a", @"Primary key can't be changed");
  207. RLMAssertThrowsWithReason(stringObj[@"stringCol"] = @"a", @"Primary key can't be changed");
  208. RLMAssertThrowsWithReason([stringObj setValue:@"a" forKey:@"stringCol"], @"Primary key can't be changed");
  209. [realm cancelWriteTransaction];
  210. }
  211. - (void)testDataTypes {
  212. RLMRealm *realm = [RLMRealm defaultRealm];
  213. [realm beginWriteTransaction];
  214. const char bin[4] = { 0, 1, 2, 3 };
  215. NSData *bin1 = [[NSData alloc] initWithBytes:bin length:sizeof bin / 2];
  216. NSData *bin2 = [[NSData alloc] initWithBytes:bin length:sizeof bin];
  217. NSDate *timeNow = [NSDate dateWithTimeIntervalSince1970:1000000];
  218. NSDate *timeZero = [NSDate dateWithTimeIntervalSince1970:0];
  219. AllTypesObject *c = [[AllTypesObject alloc] init];
  220. c.boolCol = NO;
  221. c.intCol = 54;
  222. c.floatCol = 0.7f;
  223. c.doubleCol = 0.8;
  224. c.stringCol = @"foo";
  225. c.binaryCol = bin1;
  226. c.dateCol = timeZero;
  227. c.cBoolCol = false;
  228. c.longCol = 99;
  229. c.objectCol = [[StringObject alloc] init];
  230. c.objectCol.stringCol = @"c";
  231. [realm addObject:c];
  232. [AllTypesObject createInRealm:realm withValue:@[@YES, @506, @7.7f, @8.8, @"banach", bin2,
  233. timeNow, @YES, @(-20), NSNull.null]];
  234. [realm commitWriteTransaction];
  235. AllTypesObject *row1 = [AllTypesObject allObjects][0];
  236. AllTypesObject *row2 = [AllTypesObject allObjects][1];
  237. XCTAssertEqual(row1.boolCol, NO, @"row1.BoolCol");
  238. XCTAssertEqual(row2.boolCol, YES, @"row2.BoolCol");
  239. XCTAssertEqual(row1.intCol, 54, @"row1.IntCol");
  240. XCTAssertEqual(row2.intCol, 506, @"row2.IntCol");
  241. XCTAssertEqual(row1.floatCol, 0.7f, @"row1.FloatCol");
  242. XCTAssertEqual(row2.floatCol, 7.7f, @"row2.FloatCol");
  243. XCTAssertEqual(row1.doubleCol, 0.8, @"row1.DoubleCol");
  244. XCTAssertEqual(row2.doubleCol, 8.8, @"row2.DoubleCol");
  245. XCTAssertTrue([row1.stringCol isEqual:@"foo"], @"row1.StringCol");
  246. XCTAssertTrue([row2.stringCol isEqual:@"banach"], @"row2.StringCol");
  247. XCTAssertTrue([row1.binaryCol isEqual:bin1], @"row1.BinaryCol");
  248. XCTAssertTrue([row2.binaryCol isEqual:bin2], @"row2.BinaryCol");
  249. XCTAssertTrue(([row1.dateCol isEqual:timeZero]), @"row1.DateCol");
  250. XCTAssertTrue(([row2.dateCol isEqual:timeNow]), @"row2.DateCol");
  251. XCTAssertEqual(row1.cBoolCol, false, @"row1.cBoolCol");
  252. XCTAssertEqual(row2.cBoolCol, true, @"row2.cBoolCol");
  253. XCTAssertEqual(row1.longCol, 99L, @"row1.IntCol");
  254. XCTAssertEqual(row2.longCol, -20L, @"row2.IntCol");
  255. XCTAssertTrue([row1.objectCol.stringCol isEqual:@"c"], @"row1.objectCol");
  256. XCTAssertNil(row2.objectCol, @"row2.objectCol");
  257. [realm transactionWithBlock:^{
  258. row1.boolCol = NO;
  259. row1.cBoolCol = false;
  260. row1.boolCol = (BOOL)6;
  261. row1.cBoolCol = (BOOL)6;
  262. }];
  263. XCTAssertEqual(row1.boolCol, true);
  264. XCTAssertEqual(row1.cBoolCol, true);
  265. AllTypesObject *o = [[AllTypesObject alloc] initWithValue:row1];
  266. o.floatCol = NAN;
  267. o.doubleCol = NAN;
  268. [realm transactionWithBlock:^{
  269. [realm addObject:o];
  270. }];
  271. XCTAssertTrue(isnan(o.floatCol));
  272. XCTAssertTrue(isnan(o.doubleCol));
  273. }
  274. - (void)testObjectSubclass {
  275. // test className methods
  276. XCTAssertEqualObjects(@"StringObject", [StringObject className]);
  277. XCTAssertEqualObjects(@"StringSubclassObject", [StringSubclassObject className]);
  278. RLMRealm *realm = [RLMRealm defaultRealm];
  279. [realm beginWriteTransaction];
  280. [StringObject createInDefaultRealmWithValue:@[@"string"]];
  281. StringSubclassObject *obj = [StringSubclassObject createInDefaultRealmWithValue:@[@"string", @"string2"]];
  282. // ensure property ordering
  283. XCTAssertEqualObjects([obj.objectSchema.properties[0] name], @"stringCol");
  284. XCTAssertEqualObjects([obj.objectSchema.properties[1] name], @"stringCol2");
  285. [realm commitWriteTransaction];
  286. // ensure creation in proper table
  287. RLMResults *results = StringSubclassObject.allObjects;
  288. XCTAssertEqual(1U, results.count);
  289. XCTAssertEqual(1U, StringObject.allObjects.count);
  290. // ensure exceptions on when using polymorphism
  291. [realm beginWriteTransaction];
  292. StringLinkObject *linkObject = [StringLinkObject createInDefaultRealmWithValue:@[NSNull.null, @[]]];
  293. RLMAssertThrowsWithReasonMatching(linkObject.stringObjectCol = obj,
  294. @"Can't .*StringSubclassObject.*StringObject");
  295. RLMAssertThrowsWithReasonMatching([linkObject.stringObjectArrayCol addObject:obj],
  296. @"Object of type .*StringSubclassObject.*does not match.*StringObject.*");
  297. [realm commitWriteTransaction];
  298. }
  299. - (void)testDateDistantFuture {
  300. RLMRealm *realm = [RLMRealm defaultRealm];
  301. [realm beginWriteTransaction];
  302. DateObject *dateObject = [DateObject createInRealm:realm withValue:@[NSDate.distantFuture]];
  303. [realm commitWriteTransaction];
  304. XCTAssertEqualObjects(NSDate.distantFuture, dateObject.dateCol);
  305. }
  306. - (void)testDateDistantPast {
  307. RLMRealm *realm = [RLMRealm defaultRealm];
  308. [realm beginWriteTransaction];
  309. DateObject *dateObject = [DateObject createInRealm:realm withValue:@[NSDate.distantPast]];
  310. [realm commitWriteTransaction];
  311. XCTAssertEqualObjects(NSDate.distantPast, dateObject.dateCol);
  312. }
  313. - (void)testDate50kYears {
  314. NSCalendarUnit units = (NSCalendarUnit)(NSCalendarUnitMonth | NSCalendarUnitYear | NSCalendarUnitDay);
  315. NSDateComponents *components = [[NSCalendar currentCalendar] components:units fromDate:NSDate.date];
  316. components.calendar = [NSCalendar currentCalendar];
  317. components.year += 50000;
  318. RLMRealm *realm = [RLMRealm defaultRealm];
  319. [realm beginWriteTransaction];
  320. DateObject *dateObject = [DateObject createInRealm:realm withValue:@[components.date]];
  321. [realm commitWriteTransaction];
  322. XCTAssertEqualObjects(components.date, dateObject.dateCol);
  323. }
  324. static void testDatesInRange(NSTimeInterval from, NSTimeInterval to, void (^check)(NSDate *, NSDate *)) {
  325. NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:from];
  326. RLMRealm *realm = [RLMRealm defaultRealm];
  327. [realm beginWriteTransaction];
  328. DateObject *dateObject = [DateObject createInRealm:realm withValue:@[date]];
  329. while (from < to) @autoreleasepool {
  330. check(dateObject.dateCol, date);
  331. from = nextafter(from, DBL_MAX);
  332. date = [NSDate dateWithTimeIntervalSinceReferenceDate:from];
  333. dateObject.dateCol = date;
  334. }
  335. [realm commitWriteTransaction];
  336. }
  337. - (void)testExactRepresentationOfDatesAroundNow {
  338. NSDate *date = [NSDate date];
  339. NSTimeInterval time = date.timeIntervalSinceReferenceDate;
  340. testDatesInRange(time - .001, time + .001, ^(NSDate *d1, NSDate *d2) {
  341. XCTAssertEqualObjects(d1, d2);
  342. });
  343. }
  344. - (void)testExactRepresentationOfDatesAroundDistantFuture {
  345. NSDate *date = [NSDate distantFuture];
  346. NSTimeInterval time = date.timeIntervalSinceReferenceDate;
  347. testDatesInRange(time - .001, time + .001, ^(NSDate *d1, NSDate *d2) {
  348. XCTAssertEqualObjects(d1, d2);
  349. });
  350. }
  351. - (void)testExactRepresentationOfDatesAroundEpoch {
  352. NSDate *date = [NSDate dateWithTimeIntervalSince1970:0];
  353. NSTimeInterval time = date.timeIntervalSinceReferenceDate;
  354. testDatesInRange(time - .001, time + .001, ^(NSDate *d1, NSDate *d2) {
  355. XCTAssertEqualObjects(d1, d2);
  356. });
  357. }
  358. - (void)testExactRepresentationOfDatesAroundReferenceDate {
  359. RLMRealm *realm = [RLMRealm defaultRealm];
  360. [realm beginWriteTransaction];
  361. NSDate *zero = [NSDate dateWithTimeIntervalSinceReferenceDate:0];
  362. DateObject *dateObject = [DateObject createInRealm:realm withValue:@[zero]];
  363. XCTAssertEqualObjects(dateObject.dateCol, zero);
  364. // Just shy of 1ns should still be zero
  365. dateObject.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:nextafter(1e-9, -DBL_MAX)];
  366. XCTAssertEqualObjects(dateObject.dateCol, zero);
  367. // Very slightly over 1ns (since 1e-9 can't be exactly represented by a double)
  368. dateObject.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:1e-9];
  369. XCTAssertNotEqualObjects(dateObject.dateCol, zero);
  370. // Round toward zero, so -1ns + epsilon is zero
  371. dateObject.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:nextafter(0, -DBL_MAX)];
  372. XCTAssertEqualObjects(dateObject.dateCol, zero);
  373. dateObject.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:nextafter(-1e-9, DBL_MAX)];
  374. XCTAssertEqualObjects(dateObject.dateCol, zero);
  375. dateObject.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:-1e-9];
  376. XCTAssertNotEqualObjects(dateObject.dateCol, zero);
  377. [realm commitWriteTransaction];
  378. }
  379. - (void)testDatesOutsideOfTimestampRange {
  380. NSDate *date = [NSDate date];
  381. NSDate *maxDate = [NSDate dateWithTimeIntervalSince1970:(double)(1ULL << 63) + .999999999];
  382. NSDate *minDate = [NSDate dateWithTimeIntervalSince1970:-(double)(1ULL << 63) - .999999999];
  383. NSDate *justOverMaxDate = [NSDate dateWithTimeIntervalSince1970:nextafter(maxDate.timeIntervalSince1970, DBL_MAX)];
  384. NSDate *justUnderMaxDate = [NSDate dateWithTimeIntervalSince1970:nextafter(maxDate.timeIntervalSince1970, -DBL_MAX)];
  385. NSDate *justOverMinDate = [NSDate dateWithTimeIntervalSince1970:nextafter(minDate.timeIntervalSince1970, DBL_MAX)];
  386. NSDate *justUnderMinDate = [NSDate dateWithTimeIntervalSince1970:nextafter(minDate.timeIntervalSince1970, -DBL_MAX)];
  387. RLMRealm *realm = [RLMRealm defaultRealm];
  388. [realm beginWriteTransaction];
  389. DateObject *dateObject = [DateObject createInRealm:realm withValue:@[date]];
  390. dateObject.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:0.0/0.0];
  391. XCTAssertEqualObjects(dateObject.dateCol, [NSDate dateWithTimeIntervalSince1970:0]);
  392. dateObject.dateCol = maxDate;
  393. XCTAssertEqualObjects(dateObject.dateCol, maxDate);
  394. dateObject.dateCol = justOverMaxDate;
  395. XCTAssertEqualObjects(dateObject.dateCol, maxDate);
  396. dateObject.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:DBL_MAX];
  397. XCTAssertEqualObjects(dateObject.dateCol, maxDate);
  398. dateObject.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:1.0/0.0];
  399. XCTAssertEqualObjects(dateObject.dateCol, maxDate);
  400. dateObject.dateCol = minDate;
  401. XCTAssertEqualObjects(dateObject.dateCol, minDate);
  402. dateObject.dateCol = justUnderMinDate;
  403. XCTAssertEqualObjects(dateObject.dateCol, minDate);
  404. dateObject.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:-DBL_MAX];
  405. XCTAssertEqualObjects(dateObject.dateCol, minDate);
  406. dateObject.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:-1.0/0.0];
  407. XCTAssertEqualObjects(dateObject.dateCol, minDate);
  408. dateObject.dateCol = justUnderMaxDate;
  409. XCTAssertEqualObjects(dateObject.dateCol, justUnderMaxDate);
  410. dateObject.dateCol = justOverMinDate;
  411. XCTAssertEqualObjects(dateObject.dateCol, justOverMinDate);
  412. [realm commitWriteTransaction];
  413. }
  414. - (void)testDataSizeLimits {
  415. RLMRealm *realm = [RLMRealm defaultRealm];
  416. // Allocation must be < 16 MB, with an 8-byte header and the allocation size
  417. // 8-byte aligned
  418. static const int maxSize = 0xFFFFFF - 15;
  419. // Multiple 16 MB blobs should be fine
  420. void *buffer = malloc(maxSize);
  421. strcpy((char *)buffer + maxSize - sizeof("hello") - 1, "hello");
  422. DataObject *obj = [[DataObject alloc] init];
  423. obj.data1 = obj.data2 = [NSData dataWithBytesNoCopy:buffer length:maxSize freeWhenDone:YES];
  424. [realm beginWriteTransaction];
  425. [realm addObject:obj];
  426. [realm commitWriteTransaction];
  427. XCTAssertEqual(maxSize, obj.data1.length);
  428. XCTAssertEqual(maxSize, obj.data2.length);
  429. XCTAssertTrue(strcmp((const char *)obj.data1.bytes + obj.data1.length - sizeof("hello") - 1, "hello") == 0);
  430. XCTAssertTrue(strcmp((const char *)obj.data2.bytes + obj.data2.length - sizeof("hello") - 1, "hello") == 0);
  431. // A blob over 16 MB should throw (and not crash)
  432. [realm beginWriteTransaction];
  433. RLMAssertThrowsWithReason(obj.data1 = [NSData dataWithBytesNoCopy:malloc(maxSize + 1)
  434. length:maxSize + 1 freeWhenDone:YES],
  435. @"Binary too big");
  436. [realm commitWriteTransaction];
  437. }
  438. - (void)testStringSizeLimits {
  439. RLMRealm *realm = [RLMRealm defaultRealm];
  440. // Allocation must be < 16 MB, with an 8-byte header, trailing NUL, and the
  441. // allocation size 8-byte aligned
  442. static const int maxSize = 0xFFFFFF - 16;
  443. void *buffer = calloc(maxSize, 1);
  444. strcpy((char *)buffer + maxSize - sizeof("hello") - 1, "hello");
  445. NSString *str = [[NSString alloc] initWithBytesNoCopy:buffer length:maxSize
  446. encoding:NSUTF8StringEncoding freeWhenDone:YES];
  447. StringObject *obj = [[StringObject alloc] init];
  448. obj.stringCol = str;
  449. [realm beginWriteTransaction];
  450. [realm addObject:obj];
  451. [realm commitWriteTransaction];
  452. XCTAssertEqualObjects(str, obj.stringCol);
  453. // A blob over 16 MB should throw (and not crash)
  454. [realm beginWriteTransaction];
  455. XCTAssertThrows(obj.stringCol = [[NSString alloc] initWithBytesNoCopy:calloc(maxSize + 1, 1)
  456. length:maxSize + 1
  457. encoding:NSUTF8StringEncoding
  458. freeWhenDone:YES]);
  459. [realm commitWriteTransaction];
  460. }
  461. - (void)testAddingObjectNotInSchemaThrows {
  462. RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration];
  463. configuration.objectClasses = @[StringObject.class];
  464. RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil];
  465. [realm beginWriteTransaction];
  466. RLMAssertThrowsWithReasonMatching([realm addObject:[[IntObject alloc] initWithValue:@[@1]]],
  467. @"Object type 'IntObject' is not managed by the Realm.*custom `objectClasses`");
  468. RLMAssertThrowsWithReasonMatching([IntObject createInRealm:realm withValue:@[@1]],
  469. @"Object type 'IntObject' is not managed by the Realm.*custom `objectClasses`");
  470. XCTAssertNoThrow([realm addObject:[[StringObject alloc] initWithValue:@[@"A"]]]);
  471. XCTAssertNoThrow([StringObject createInRealm:realm withValue:@[@"A"]]);
  472. [realm cancelWriteTransaction];
  473. }
  474. static void addProperty(Class cls, const char *name, const char *type, size_t size, size_t align, id getter) {
  475. objc_property_attribute_t objectColAttrs[] = {
  476. {"T", type},
  477. {"V", name},
  478. };
  479. class_addIvar(cls, name, size, align, type);
  480. class_addProperty(cls, name, objectColAttrs, sizeof(objectColAttrs) / sizeof(objc_property_attribute_t));
  481. char encoding[4] = " @:";
  482. encoding[0] = *type;
  483. class_addMethod(cls, sel_registerName(name), imp_implementationWithBlock(getter), encoding);
  484. }
  485. - (void)testObjectSubclassAddedAtRuntime {
  486. Class objectClass = objc_allocateClassPair(RLMObject.class, "RuntimeGeneratedObject", 0);
  487. addProperty(objectClass, "objectCol", "@\"RuntimeGeneratedObject\"", sizeof(id), alignof(id), ^(__unused id obj) { return nil; });
  488. addProperty(objectClass, "intCol", "i", sizeof(int), alignof(int), ^int(__unused id obj) { return 0; });
  489. objc_registerClassPair(objectClass);
  490. XCTAssertEqualObjects([objectClass className], @"RuntimeGeneratedObject");
  491. RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration];
  492. configuration.objectClasses = @[objectClass];
  493. XCTAssertEqualObjects([objectClass className], @"RuntimeGeneratedObject");
  494. RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil];
  495. [realm beginWriteTransaction];
  496. id object = [objectClass createInRealm:realm withValue:@{@"objectCol": [[objectClass alloc] init], @"intCol": @17}];
  497. RLMObjectSchema *schema = [object objectSchema];
  498. XCTAssertNotNil(schema[@"objectCol"]);
  499. XCTAssertNotNil(schema[@"intCol"]);
  500. XCTAssert([[object objectCol] isKindOfClass:objectClass]);
  501. XCTAssertEqual([object intCol], 17);
  502. [realm commitWriteTransaction];
  503. }
  504. #pragma mark - Default Property Values
  505. - (NSDictionary *)defaultValuesDictionary {
  506. return @{@"intCol" : @98,
  507. @"floatCol" : @231.0f,
  508. @"doubleCol": @123732.9231,
  509. @"boolCol" : @NO,
  510. @"dateCol" : [NSDate dateWithTimeIntervalSince1970:454321],
  511. @"stringCol": @"Westeros",
  512. @"binaryCol": [@"inputData" dataUsingEncoding:NSUTF8StringEncoding]};
  513. }
  514. - (void)testDefaultValuesFromNoValuePresent {
  515. RLMRealm *realm = [RLMRealm defaultRealm];
  516. [realm beginWriteTransaction];
  517. NSDictionary *inputValues = [self defaultValuesDictionary];
  518. NSArray *keys = [inputValues allKeys]; // To ensure iteration order is stable
  519. for (NSString *key in keys) {
  520. NSMutableDictionary *dict = [inputValues mutableCopy];
  521. [dict removeObjectForKey:key];
  522. [DefaultObject createInRealm:realm withValue:dict];
  523. }
  524. [realm commitWriteTransaction];
  525. // Test allObject for DefaultObject
  526. NSDictionary *defaultValues = [DefaultObject defaultPropertyValues];
  527. RLMResults *allObjects = [DefaultObject allObjectsInRealm:realm];
  528. for (NSUInteger i = 0; i < keys.count; ++i) {
  529. DefaultObject *object = allObjects[i];
  530. for (NSUInteger j = 0; j < keys.count; ++j) {
  531. NSString *key = keys[j];
  532. if (i == j) {
  533. XCTAssertEqualObjects(object[key], defaultValues[key]);
  534. }
  535. else {
  536. XCTAssertEqualObjects(object[key], inputValues[key]);
  537. }
  538. }
  539. }
  540. }
  541. - (void)testDefaultValuesFromNSNull {
  542. RLMRealm *realm = [RLMRealm defaultRealm];
  543. NSDictionary *defaultValues = [DefaultObject defaultPropertyValues];
  544. NSDictionary *inputValues = [self defaultValuesDictionary];
  545. NSArray *keys = [inputValues allKeys]; // To ensure iteration order is stable
  546. for (NSString *key in keys) {
  547. NSMutableDictionary *dict = [inputValues mutableCopy];
  548. dict[key] = NSNull.null;
  549. RLMProperty *prop = realm.schema[@"DefaultObject"][key];
  550. if (prop.optional) {
  551. [realm beginWriteTransaction];
  552. [DefaultObject createInRealm:realm withValue:dict];
  553. [realm commitWriteTransaction];
  554. DefaultObject *object = DefaultObject.allObjects.lastObject;
  555. for (NSUInteger j = 0; j < keys.count; ++j) {
  556. NSString *key2 = keys[j];
  557. if ([key isEqualToString:key2]) {
  558. XCTAssertEqualObjects(object[key2], prop.optional ? nil : defaultValues[key2]);
  559. }
  560. else {
  561. XCTAssertEqualObjects(object[key2], inputValues[key2]);
  562. }
  563. }
  564. }
  565. else {
  566. [realm beginWriteTransaction];
  567. RLMAssertThrowsWithReason([DefaultObject createInRealm:realm withValue:dict],
  568. @"Invalid value '<null>' of type 'NSNull' for ");
  569. [realm commitWriteTransaction];
  570. }
  571. }
  572. }
  573. - (void)testDefaultNSNumberPropertyValues {
  574. void (^assertDefaults)(NumberObject *) = ^(NumberObject *no) {
  575. XCTAssertEqualObjects(no.intObj, @1);
  576. XCTAssertEqualObjects(no.floatObj, @2.2f);
  577. XCTAssertEqualObjects(no.doubleObj, @3.3);
  578. XCTAssertEqualObjects(no.boolObj, @NO);
  579. };
  580. assertDefaults([[NumberDefaultsObject alloc] init]);
  581. RLMRealm *realm = [RLMRealm defaultRealm];
  582. [realm beginWriteTransaction];
  583. assertDefaults([NumberDefaultsObject createInRealm:realm withValue:@{}]);
  584. [realm cancelWriteTransaction];
  585. }
  586. - (void)testDynamicDefaultPropertyValues {
  587. void (^assertDifferentPropertyValues)(DynamicDefaultObject *, DynamicDefaultObject *) = ^(DynamicDefaultObject *obj1, DynamicDefaultObject *obj2) {
  588. XCTAssertNotEqual(obj1.intCol, obj2.intCol);
  589. XCTAssertNotEqual(obj1.floatCol, obj2.floatCol);
  590. XCTAssertNotEqual(obj1.doubleCol, obj2.doubleCol);
  591. XCTAssertNotEqualWithAccuracy(obj1.dateCol.timeIntervalSinceReferenceDate, obj2.dateCol.timeIntervalSinceReferenceDate, 0.01f);
  592. XCTAssertNotEqualObjects(obj1.stringCol, obj2.stringCol);
  593. XCTAssertNotEqualObjects(obj1.binaryCol, obj2.binaryCol);
  594. };
  595. assertDifferentPropertyValues([[DynamicDefaultObject alloc] init], [[DynamicDefaultObject alloc] init]);
  596. RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration];
  597. configuration.objectClasses = @[[DynamicDefaultObject class]];
  598. RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil];
  599. [realm beginWriteTransaction];
  600. assertDifferentPropertyValues([DynamicDefaultObject createInRealm:realm withValue:@{}], [DynamicDefaultObject createInRealm:realm withValue:@{}]);
  601. [realm cancelWriteTransaction];
  602. }
  603. #pragma mark - Ignored Properties
  604. - (void)testCanUseIgnoredProperty {
  605. NSURL *url = [NSURL URLWithString:@"http://realm.io"];
  606. RLMRealm *realm = [RLMRealm defaultRealm];
  607. [realm beginWriteTransaction];
  608. IgnoredURLObject *obj = [IgnoredURLObject new];
  609. obj.name = @"Realm";
  610. obj.url = url;
  611. [realm addObject:obj];
  612. XCTAssertEqual(obj.url, url, @"ignored properties should still be assignable and gettable inside a write block");
  613. [realm commitWriteTransaction];
  614. XCTAssertEqual(obj.url, url, @"ignored properties should still be assignable and gettable outside a write block");
  615. IgnoredURLObject *obj2 = [[IgnoredURLObject objectsWithPredicate:nil] firstObject];
  616. XCTAssertNotNil(obj2, @"object with ignored property should still be stored and accessible through the realm");
  617. XCTAssertEqualObjects(obj2.name, obj.name, @"managed property should be the same");
  618. XCTAssertNil(obj2.url, @"ignored property should be nil when getting from realm");
  619. }
  620. #pragma mark - Create
  621. - (void)testCreateInRealmValidationForDictionary {
  622. RLMRealm *realm = [RLMRealm defaultRealm];
  623. const char bin[4] = { 0, 1, 2, 3 };
  624. NSData *bin1 = [[NSData alloc] initWithBytes:bin length:sizeof bin / 2];
  625. NSDate *timeNow = [NSDate dateWithTimeIntervalSince1970:1000000];
  626. NSDictionary * const dictValidAllTypes = @{@"boolCol" : @NO,
  627. @"intCol" : @54,
  628. @"floatCol" : @0.7f,
  629. @"doubleCol": @0.8,
  630. @"stringCol": @"foo",
  631. @"binaryCol": bin1,
  632. @"dateCol" : timeNow,
  633. @"cBoolCol" : @NO,
  634. @"longCol" : @(99),
  635. @"objectCol": NSNull.null};
  636. [realm beginWriteTransaction];
  637. // Test NSDictonary
  638. XCTAssertNoThrow(([AllTypesObject createInRealm:realm withValue:dictValidAllTypes]),
  639. @"Creating object with valid value types should not throw exception");
  640. for (NSString *keyToInvalidate in dictValidAllTypes.allKeys) {
  641. NSMutableDictionary *invalidInput = [dictValidAllTypes mutableCopy];
  642. id obj = @"invalid";
  643. if ([keyToInvalidate isEqualToString:@"stringCol"]) {
  644. obj = @1;
  645. }
  646. invalidInput[keyToInvalidate] = obj;
  647. RLMAssertThrowsWithReasonMatching([AllTypesObject createInRealm:realm withValue:invalidInput],
  648. @"Invalid value '.*'");
  649. }
  650. [realm commitWriteTransaction];
  651. }
  652. - (void)testCreateInRealmValidationForArray {
  653. RLMRealm *realm = [RLMRealm defaultRealm];
  654. // add test/link object to realm
  655. [realm beginWriteTransaction];
  656. StringObject *to = [StringObject createInRealm:realm withValue:@[@"c"]];
  657. [realm commitWriteTransaction];
  658. const char bin[4] = { 0, 1, 2, 3 };
  659. NSData *bin1 = [[NSData alloc] initWithBytes:bin length:sizeof bin / 2];
  660. NSDate *timeNow = [NSDate dateWithTimeIntervalSince1970:1000000];
  661. NSArray *const arrayValidAllTypes = @[@NO, @54, @0.7f, @0.8, @"foo", bin1, timeNow, @NO, @(99), to];
  662. [realm beginWriteTransaction];
  663. // Test NSArray
  664. XCTAssertNoThrow(([AllTypesObject createInRealm:realm withValue:arrayValidAllTypes]),
  665. @"Creating object with valid value types should not throw exception");
  666. const NSInteger stringColIndex = 4;
  667. for (NSUInteger i = 0; i < arrayValidAllTypes.count; i++) {
  668. NSMutableArray *invalidInput = [arrayValidAllTypes mutableCopy];
  669. id obj = @"invalid";
  670. if (i == stringColIndex) {
  671. obj = @1;
  672. }
  673. invalidInput[i] = obj;
  674. RLMAssertThrowsWithReasonMatching([AllTypesObject createInRealm:realm withValue:invalidInput],
  675. @"Invalid value '.*'");
  676. }
  677. [realm commitWriteTransaction];
  678. }
  679. - (void)testCreateInRealmReusesExistingObjects {
  680. RLMRealm *realm = [RLMRealm defaultRealm];
  681. [realm beginWriteTransaction];
  682. DogObject *dog = [DogObject createInDefaultRealmWithValue:@[@"Fido", @5]];
  683. OwnerObject *owner = [OwnerObject createInDefaultRealmWithValue:@[@"name", dog]];
  684. XCTAssertTrue([owner.dog isEqualToObject:dog]);
  685. XCTAssertEqual(1U, DogObject.allObjects.count);
  686. DogArrayObject *dogArray = [DogArrayObject createInDefaultRealmWithValue:@[@[dog]]];
  687. XCTAssertTrue([dogArray.dogs[0] isEqualToObject:dog]);
  688. XCTAssertEqual(1U, DogObject.allObjects.count);
  689. [realm commitWriteTransaction];
  690. }
  691. - (void)testCreateInRealmReusesExistingNestedObjectsByPrimaryKey {
  692. RLMRealm *realm = [RLMRealm defaultRealm];
  693. [realm beginWriteTransaction];
  694. PrimaryEmployeeObject *eo = [PrimaryEmployeeObject createInRealm:realm withValue:@[@"Samuel", @19, @NO]];
  695. PrimaryCompanyObject *co = [PrimaryCompanyObject createInRealm:realm withValue:@[@"Realm", @[eo], eo, @[eo]]];
  696. [realm commitWriteTransaction];
  697. [realm beginWriteTransaction];
  698. [PrimaryCompanyObject createOrUpdateInRealm:realm withValue:@{
  699. @"name": @"Realm",
  700. @"intern": @{@"name":@"Samuel", @"hired":@YES},
  701. }];
  702. [realm commitWriteTransaction];
  703. XCTAssertEqual(1U, co.employees.count);
  704. XCTAssertEqual(1U, [PrimaryEmployeeObject allObjectsInRealm:realm].count);
  705. XCTAssertEqualObjects(@"Samuel", eo.name);
  706. XCTAssertEqual(YES, eo.hired);
  707. XCTAssertEqual(19, eo.age);
  708. [realm beginWriteTransaction];
  709. [PrimaryCompanyObject createOrUpdateInRealm:realm withValue:@{
  710. @"name": @"Realm",
  711. @"employees": @[@{@"name":@"Samuel", @"hired":@NO}],
  712. @"intern": @{@"name":@"Samuel", @"age":@20},
  713. }];
  714. [realm commitWriteTransaction];
  715. XCTAssertEqual(1U, co.employees.count);
  716. XCTAssertEqual(1U, [PrimaryEmployeeObject allObjectsInRealm:realm].count);
  717. XCTAssertEqualObjects(@"Samuel", eo.name);
  718. XCTAssertEqual(NO, eo.hired);
  719. XCTAssertEqual(20, eo.age);
  720. [realm beginWriteTransaction];
  721. [PrimaryCompanyObject createOrUpdateInRealm:realm withValue:@{@"name": @"Realm",
  722. @"wrappedIntern": @[eo]}];
  723. [realm commitWriteTransaction];
  724. XCTAssertEqual(1U, [[PrimaryEmployeeObject allObjectsInRealm:realm] count]);
  725. }
  726. - (void)testCreateInRealmCopiesFromOtherRealm {
  727. RLMRealm *realm1 = [RLMRealm defaultRealm];
  728. RLMRealm *realm2 = [self realmWithTestPath];
  729. [realm1 beginWriteTransaction];
  730. [realm2 beginWriteTransaction];
  731. DogObject *dog = [DogObject createInDefaultRealmWithValue:@[@"Fido", @5]];
  732. OwnerObject *owner = [OwnerObject createInRealm:realm2 withValue:@[@"name", dog]];
  733. XCTAssertFalse([owner.dog isEqualToObject:dog]);
  734. XCTAssertEqual(1U, DogObject.allObjects.count);
  735. XCTAssertEqual(1U, [DogObject allObjectsInRealm:realm2].count);
  736. DogArrayObject *dogArray = [DogArrayObject createInRealm:realm2 withValue:@[@[dog]]];
  737. XCTAssertFalse([dogArray.dogs[0] isEqualToObject:dog]);
  738. XCTAssertEqual(1U, DogObject.allObjects.count);
  739. XCTAssertEqual(2U, [DogObject allObjectsInRealm:realm2].count);
  740. [realm1 commitWriteTransaction];
  741. [realm2 commitWriteTransaction];
  742. }
  743. - (void)testCreateInRealmWithOtherObjects {
  744. RLMRealm *realm = [RLMRealm defaultRealm];
  745. [realm beginWriteTransaction];
  746. DateObjectNoThrow *object = [DateObjectNoThrow createInDefaultRealmWithValue:@[NSDate.date, NSDate.date]];
  747. // create subclass with instance of base class with/without default objects
  748. XCTAssertNoThrow([DateSubclassObject createInDefaultRealmWithValue:object]);
  749. XCTAssertNoThrow([DateObjectNoThrow createInDefaultRealmWithValue:object]);
  750. // create using non-realm object with custom getter
  751. SubclassDateObject *obj = [SubclassDateObject new];
  752. obj.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:1000];
  753. obj.date2 = [NSDate dateWithTimeIntervalSinceReferenceDate:2000];
  754. obj.date3 = [NSDate dateWithTimeIntervalSinceReferenceDate:3000];
  755. [DateDefaultsObject createInDefaultRealmWithValue:obj];
  756. XCTAssertEqual(2U, DateObjectNoThrow.allObjects.count);
  757. [realm commitWriteTransaction];
  758. }
  759. #pragma mark - Description
  760. - (void)testObjectDescription {
  761. RLMRealm *realm = [RLMRealm defaultRealm];
  762. [realm beginWriteTransaction];
  763. // Init object before adding to realm
  764. EmployeeObject *soInit = [[EmployeeObject alloc] init];
  765. soInit.name = @"Peter";
  766. soInit.age = 30;
  767. soInit.hired = YES;
  768. [realm addObject:soInit];
  769. // description asserts block
  770. void (^descriptionAsserts)(NSString *) = ^(NSString *description) {
  771. XCTAssertTrue([description rangeOfString:@"name"].location != NSNotFound,
  772. @"column names should be displayed when calling \"description\" on RLMObject subclasses");
  773. XCTAssertTrue([description rangeOfString:@"Peter"].location != NSNotFound,
  774. @"column values should be displayed when calling \"description\" on RLMObject subclasses");
  775. XCTAssertTrue([description rangeOfString:@"age"].location != NSNotFound,
  776. @"column names should be displayed when calling \"description\" on RLMObject subclasses");
  777. XCTAssertTrue([description rangeOfString:[@30 description]].location != NSNotFound,
  778. @"column values should be displayed when calling \"description\" on RLMObject subclasses");
  779. XCTAssertTrue([description rangeOfString:@"hired"].location != NSNotFound,
  780. @"column names should be displayed when calling \"description\" on RLMObject subclasses");
  781. XCTAssertTrue([description rangeOfString:[@YES description]].location != NSNotFound,
  782. @"column values should be displayed when calling \"description\" on RLMObject subclasses");
  783. };
  784. // Test description in write block
  785. descriptionAsserts(soInit.description);
  786. [realm commitWriteTransaction];
  787. // Test description in read block
  788. NSString *objDescription = [[[EmployeeObject objectsWithPredicate:nil] firstObject] description];
  789. descriptionAsserts(objDescription);
  790. soInit = [[EmployeeObject alloc] init];
  791. soInit.age = 20;
  792. XCTAssert([soInit.description rangeOfString:@"(null)"].location != NSNotFound);
  793. }
  794. - (void)testObjectCycleDescription {
  795. CycleObject *obj = [[CycleObject alloc] init];
  796. [RLMRealm.defaultRealm transactionWithBlock:^{
  797. [RLMRealm.defaultRealm addObject:obj];
  798. [obj.objects addObject:obj];
  799. }];
  800. XCTAssertNoThrow(obj.description);
  801. }
  802. - (void)testDataObjectDescription {
  803. RLMRealm *realm = [RLMRealm defaultRealm];
  804. [realm beginWriteTransaction];
  805. char longData[200];
  806. [DataObject createInRealm:realm withValue:@[[NSData dataWithBytes:&longData length:200], [NSData dataWithBytes:&longData length:2]]];
  807. [realm commitWriteTransaction];
  808. DataObject *obj = [DataObject allObjectsInRealm:realm].firstObject;
  809. XCTAssertTrue([obj.description rangeOfString:@"200 total bytes"].location != NSNotFound);
  810. XCTAssertTrue([obj.description rangeOfString:@"2 total bytes"].location != NSNotFound);
  811. }
  812. - (void)testDeletedObjectDescription {
  813. RLMRealm *realm = [RLMRealm defaultRealm];
  814. [realm beginWriteTransaction];
  815. EmployeeObject *obj = [EmployeeObject createInRealm:realm withValue:@[@"Peter", @30, @YES]];
  816. [realm deleteObject:obj];
  817. [realm commitWriteTransaction];
  818. XCTAssertNoThrow(obj.description);
  819. }
  820. - (void)testManagedObjectUnknownKey {
  821. IntObject *obj = [[IntObject alloc] init];
  822. RLMRealm *realm = [RLMRealm defaultRealm];
  823. [realm beginWriteTransaction];
  824. [realm addObject:obj];
  825. [realm commitWriteTransaction];
  826. RLMAssertThrowsWithReason([obj objectForKeyedSubscript:@""],
  827. @"Invalid property name '' for class 'IntObject'");
  828. RLMAssertThrowsWithReason([obj setObject:@0 forKeyedSubscript:@""],
  829. @"Invalid property name '' for class 'IntObject'");
  830. }
  831. - (void)testUnmanagedRealmObjectUnknownKey {
  832. IntObject *obj = [[IntObject alloc] init];
  833. XCTAssertThrows([obj objectForKeyedSubscript:@""]);
  834. XCTAssertThrows([obj setObject:@0 forKeyedSubscript:@""]);
  835. }
  836. - (void)testEquality {
  837. IntObject *obj = [[IntObject alloc] init];
  838. IntObject *otherObj = [[IntObject alloc] init];
  839. RLMRealm *realm = [RLMRealm defaultRealm];
  840. RLMRealm *otherRealm = [self realmWithTestPath];
  841. XCTAssertFalse([obj isEqual:[NSObject new]], @"Comparing an RLMObject to a non-RLMObject should be false.");
  842. XCTAssertFalse([obj isEqualToObject:(RLMObject *)[NSObject new]], @"Comparing an RLMObject to a non-RLMObject should be false.");
  843. XCTAssertTrue([obj isEqual:obj], @"Same instance.");
  844. XCTAssertTrue([obj isEqualToObject:obj], @"Same instance.");
  845. XCTAssertFalse([obj isEqualToObject:otherObj], @"Comparison outside of realm.");
  846. [realm beginWriteTransaction];
  847. [realm addObject:obj];
  848. [realm commitWriteTransaction];
  849. XCTAssertFalse([obj isEqualToObject:otherObj], @"One in realm, the other is not.");
  850. XCTAssertTrue([obj isEqualToObject:[IntObject allObjects][0]], @"Same table and index.");
  851. [otherRealm beginWriteTransaction];
  852. [otherRealm addObject:otherObj];
  853. [otherRealm commitWriteTransaction];
  854. XCTAssertFalse([obj isEqualToObject:otherObj], @"Different realms.");
  855. [realm beginWriteTransaction];
  856. [realm addObject:[[IntObject alloc] init]];
  857. [realm addObject:[[BoolObject alloc] init]];
  858. [realm commitWriteTransaction];
  859. XCTAssertFalse([obj isEqualToObject:[IntObject allObjects][1]], @"Same table, different index.");
  860. XCTAssertFalse([obj isEqualToObject:[BoolObject allObjects][0]], @"Different tables.");
  861. }
  862. - (void)testCrossThreadAccess {
  863. IntObject *obj = [[IntObject alloc] init];
  864. // Unmanaged object can be accessed from other threads
  865. [self dispatchAsyncAndWait:^{ XCTAssertNoThrow(obj.intCol = 5); }];
  866. [RLMRealm.defaultRealm beginWriteTransaction];
  867. [RLMRealm.defaultRealm addObject:obj];
  868. [RLMRealm.defaultRealm commitWriteTransaction];
  869. [self dispatchAsyncAndWait:^{ RLMAssertThrowsWithReason(obj.intCol, @"incorrect thread"); }];
  870. }
  871. - (void)testIsDeleted {
  872. StringObject *obj1 = [[StringObject alloc] initWithValue:@[@"a"]];
  873. XCTAssertEqual(obj1.invalidated, NO);
  874. RLMRealm *realm = [self realmWithTestPath];
  875. [realm beginWriteTransaction];
  876. [realm addObject:obj1];
  877. StringObject *obj2 = [StringObject createInRealm:realm withValue:@[@"b"]];
  878. XCTAssertEqual([obj1 isInvalidated], NO);
  879. XCTAssertEqual(obj2.invalidated, NO);
  880. [realm commitWriteTransaction];
  881. // delete
  882. [realm beginWriteTransaction];
  883. // Delete directly
  884. [realm deleteObject:obj1];
  885. // Delete as result of query since then obj2's realm could point to a different instance
  886. [realm deleteObject:[[StringObject allObjectsInRealm:realm] firstObject]];
  887. XCTAssertEqual(obj1.invalidated, YES);
  888. XCTAssertEqual(obj2.invalidated, YES);
  889. RLMAssertThrowsWithReason([realm addObject:obj1], @"deleted or invalidated");
  890. NSArray *propObject = @[@"", @[obj2], @[]];
  891. RLMAssertThrowsWithReason([ArrayPropertyObject createInRealm:realm withValue:propObject],
  892. @"deleted or invalidated");
  893. [realm commitWriteTransaction];
  894. XCTAssertEqual(obj1.invalidated, YES);
  895. XCTAssertNil(obj1.realm, @"Realm should be nil after deletion");
  896. }
  897. #pragma mark - Primary Keys
  898. - (void)testPrimaryKey {
  899. [[RLMRealm defaultRealm] beginWriteTransaction];
  900. [PrimaryStringObject createInDefaultRealmWithValue:(@[@"string", @1])];
  901. [PrimaryStringObject createInDefaultRealmWithValue:(@[@"string2", @1])];
  902. RLMAssertThrowsWithReason([PrimaryStringObject createInDefaultRealmWithValue:(@[@"string", @1])],
  903. @"existing primary key value");
  904. [PrimaryIntObject createInDefaultRealmWithValue:(@[@1])];
  905. [PrimaryIntObject createInDefaultRealmWithValue:(@{@"intCol": @2})];
  906. RLMAssertThrowsWithReason([PrimaryIntObject createInDefaultRealmWithValue:(@[@1])],
  907. @"existing primary key value");
  908. [PrimaryInt64Object createInDefaultRealmWithValue:(@[@(1LL << 40)])];
  909. [PrimaryInt64Object createInDefaultRealmWithValue:(@[@(1LL << 41)])];
  910. RLMAssertThrowsWithReason([PrimaryInt64Object createInDefaultRealmWithValue:(@[@(1LL << 40)])],
  911. @"existing primary key value");
  912. [PrimaryNullableIntObject createInDefaultRealmWithValue:@[@1]];
  913. [PrimaryNullableIntObject createInDefaultRealmWithValue:(@{@"optIntCol": @2, @"value": @0})];
  914. [PrimaryNullableIntObject createInDefaultRealmWithValue:@[NSNull.null]];
  915. RLMAssertThrowsWithReason([PrimaryNullableIntObject createInDefaultRealmWithValue:(@[@1, @0])],
  916. @"existing primary key value");
  917. RLMAssertThrowsWithReason([PrimaryNullableIntObject createInDefaultRealmWithValue:(@[NSNull.null, @0])],
  918. @"existing primary key value");
  919. [[RLMRealm defaultRealm] commitWriteTransaction];
  920. }
  921. - (void)testCreateOrUpdate {
  922. RLMRealm *realm = [RLMRealm defaultRealm];
  923. [realm beginWriteTransaction];
  924. PrimaryNullableStringObject *obj1 = [PrimaryNullableStringObject
  925. createOrUpdateInDefaultRealmWithValue:@[@"string", @1]];
  926. RLMResults *objects = [PrimaryNullableStringObject allObjects];
  927. XCTAssertEqual([objects count], 1U, @"Should have 1 object");
  928. XCTAssertEqual(obj1.intCol, 1, @"Value should be 1");
  929. [PrimaryNullableStringObject createOrUpdateInRealm:realm withValue:@{@"stringCol": @"string2", @"intCol": @2}];
  930. XCTAssertEqual([objects count], 2U, @"Should have 2 objects");
  931. [PrimaryNullableStringObject createOrUpdateInRealm:realm withValue:@{@"intCol": @5}];
  932. [PrimaryNullableStringObject createOrUpdateInRealm:realm withValue:@{@"intCol": @7}];
  933. XCTAssertEqual([PrimaryNullableStringObject objectInRealm:realm forPrimaryKey:NSNull.null].intCol, 7);
  934. [PrimaryNullableStringObject createOrUpdateInRealm:realm withValue:@{@"stringCol": NSNull.null, @"intCol": @11}];
  935. XCTAssertEqual([PrimaryNullableStringObject objectInRealm:realm forPrimaryKey:nil].intCol, 11);
  936. // upsert with new secondary property
  937. [PrimaryNullableStringObject createOrUpdateInDefaultRealmWithValue:@[@"string", @3]];
  938. XCTAssertEqual([objects count], 3U, @"Should have 3 objects");
  939. XCTAssertEqual(obj1.intCol, 3, @"Value should be 3");
  940. [realm commitWriteTransaction];
  941. }
  942. - (void)testCreateOrUpdateNestedObjects {
  943. RLMRealm *realm = [RLMRealm defaultRealm];
  944. [realm beginWriteTransaction];
  945. [PrimaryNestedObject createOrUpdateInDefaultRealmWithValue:@[@0, @[@"string", @1], @[@[@"string", @1]], @[@"string"], @[@[@1]], @""]];
  946. XCTAssertEqual([[PrimaryNestedObject allObjects] count], 1U, @"Should have 1 object");
  947. XCTAssertEqual([[PrimaryStringObject allObjects] count], 1U, @"Should have 1 object");
  948. XCTAssertEqual([[PrimaryIntObject allObjects] count], 1U, @"Should have 1 object");
  949. // update parent and nested object
  950. [PrimaryNestedObject createOrUpdateInDefaultRealmWithValue:@{@"primaryCol": @0,
  951. @"primaryStringObject": @[@"string", @2],
  952. @"primaryStringObjectWrapper": @[@[@"string", @2]],
  953. @"stringObject": @[@"string2"]}];
  954. XCTAssertEqual([[PrimaryNestedObject allObjects] count], 1U, @"Should have 1 object");
  955. XCTAssertEqual([[PrimaryStringObject allObjects] count], 1U, @"Should have 1 object");
  956. XCTAssertEqual([PrimaryStringObject.allObjects.lastObject intCol], 2, @"intCol should be 2");
  957. XCTAssertEqualObjects([PrimaryNestedObject.allObjects.lastObject stringCol], @"", @"stringCol should not have been updated");
  958. XCTAssertEqual(1U, [PrimaryNestedObject.allObjects.lastObject primaryIntArray].count, @"intArray should not have been overwritten");
  959. XCTAssertEqual([[StringObject allObjects] count], 2U, @"Should have 2 objects");
  960. // test partial update nulling out object/array properties
  961. [PrimaryNestedObject createOrUpdateInDefaultRealmWithValue:@{@"primaryCol": @0,
  962. @"stringCol": @"updated",
  963. @"stringObject": NSNull.null,
  964. @"primaryIntArray": NSNull.null}];
  965. PrimaryNestedObject *obj = PrimaryNestedObject.allObjects.lastObject;
  966. XCTAssertEqual(2, obj.primaryStringObject.intCol, @"primaryStringObject should not have changed");
  967. XCTAssertEqualObjects(obj.stringCol, @"updated", @"stringCol should have been updated");
  968. XCTAssertEqual(0U, obj.primaryIntArray.count, @"intArray should not have been emptied");
  969. XCTAssertNil(obj.stringObject, @"stringObject should be nil");
  970. // inserting new object should update nested
  971. obj = [PrimaryNestedObject createOrUpdateInDefaultRealmWithValue:@[@1, @[@"string", @3], @[@[@"string", @3]], @[@"string"], @[], @""]];
  972. XCTAssertEqual([[PrimaryNestedObject allObjects] count], 2U, @"Should have 2 objects");
  973. XCTAssertEqual([[PrimaryStringObject allObjects] count], 1U, @"Should have 1 object");
  974. XCTAssertEqual([(PrimaryStringObject *)[[PrimaryStringObject allObjects] lastObject] intCol], 3, @"intCol should be 3");
  975. // test addOrUpdateObject
  976. obj.primaryStringObject = [PrimaryStringObject createInDefaultRealmWithValue:@[@"string2", @1]];
  977. PrimaryNestedObject *obj1 = [[PrimaryNestedObject alloc] initWithValue:@[@1, @[@"string2", @4], @[@[@"string2", @4]], @[@"string"], @[@[@1], @[@2]], @""]];
  978. [realm addOrUpdateObject:obj1];
  979. XCTAssertEqual([[PrimaryNestedObject allObjects] count], 2U, @"Should have 2 objects");
  980. XCTAssertEqual([[PrimaryStringObject allObjects] count], 2U, @"Should have 2 objects");
  981. XCTAssertEqual([[PrimaryIntObject allObjects] count], 2U, @"Should have 2 objects");
  982. XCTAssertEqual([(PrimaryStringObject *)[[PrimaryStringObject allObjects] lastObject] intCol], 4, @"intCol should be 4");
  983. [realm commitWriteTransaction];
  984. }
  985. - (void)testCreateOrUpdateWithReorderedColumns {
  986. @autoreleasepool {
  987. // Create a Realm file with the properties in reverse order
  988. RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:PrimaryStringObject.class];
  989. objectSchema.properties = @[objectSchema.properties[1], objectSchema.properties[0]];
  990. RLMSchema *schema = [RLMSchema new];
  991. schema.objectSchema = @[objectSchema];
  992. RLMRealm *realm = [self realmWithTestPathAndSchema:schema];
  993. [realm beginWriteTransaction];
  994. [PrimaryStringObject createOrUpdateInRealm:realm withValue:@[@5, @"a"]];
  995. [realm commitWriteTransaction];
  996. }
  997. RLMRealm *realm = [self realmWithTestPath];
  998. [realm beginWriteTransaction];
  999. XCTAssertEqual([PrimaryStringObject objectInRealm:realm forPrimaryKey:@"a"].intCol, 5);
  1000. // Values in array are used in property declaration order, not table column order
  1001. [PrimaryStringObject createOrUpdateInRealm:realm withValue:@[@"a", @6]];
  1002. XCTAssertEqual([PrimaryStringObject objectInRealm:realm forPrimaryKey:@"a"].intCol, 6);
  1003. [PrimaryStringObject createOrUpdateInRealm:realm withValue:@{@"stringCol": @"a", @"intCol": @7}];
  1004. XCTAssertEqual([PrimaryStringObject objectInRealm:realm forPrimaryKey:@"a"].intCol, 7);
  1005. [realm commitWriteTransaction];
  1006. }
  1007. - (void)testObjectInSet {
  1008. [[RLMRealm defaultRealm] beginWriteTransaction];
  1009. // set object with primary and non primary keys as they both override isEqual and hash
  1010. PrimaryStringObject *obj = [PrimaryStringObject createInDefaultRealmWithValue:(@[@"string2", @1])];
  1011. StringObject *strObj = [StringObject createInDefaultRealmWithValue:@[@"string"]];
  1012. NSMutableSet *dict = [NSMutableSet set];
  1013. [dict addObject:obj];
  1014. [dict addObject:strObj];
  1015. // primary key objects should match even with duplicate instances of the same object
  1016. XCTAssertTrue([dict containsObject:obj]);
  1017. XCTAssertTrue([dict containsObject:[[PrimaryStringObject allObjects] firstObject]]);
  1018. // non-primary key objects should only match when comparing identical instances
  1019. XCTAssertTrue([dict containsObject:strObj]);
  1020. XCTAssertFalse([dict containsObject:[[StringObject allObjects] firstObject]]);
  1021. [[RLMRealm defaultRealm] commitWriteTransaction];
  1022. }
  1023. - (void)testObjectForKey {
  1024. [RLMRealm.defaultRealm beginWriteTransaction];
  1025. PrimaryStringObject *strObj = [PrimaryStringObject createInDefaultRealmWithValue:@[@"key", @0]];
  1026. PrimaryNullableStringObject *nullStrObj = [PrimaryNullableStringObject createInDefaultRealmWithValue:@[NSNull.null, @0]];
  1027. PrimaryIntObject *intObj = [PrimaryIntObject createInDefaultRealmWithValue:@[@0]];
  1028. PrimaryNullableIntObject *nonNullIntObj = [PrimaryNullableIntObject createInDefaultRealmWithValue:@[@0]];
  1029. PrimaryNullableIntObject *nullIntObj = [PrimaryNullableIntObject createInDefaultRealmWithValue:@[NSNull.null]];
  1030. [RLMRealm.defaultRealm commitWriteTransaction];
  1031. // no PK
  1032. RLMAssertThrowsWithReason([StringObject objectForPrimaryKey:@""],
  1033. @"does not have a primary key");
  1034. RLMAssertThrowsWithReason([IntObject objectForPrimaryKey:@0],
  1035. @"does not have a primary key");
  1036. RLMAssertThrowsWithReason([StringObject objectForPrimaryKey:NSNull.null],
  1037. @"does not have a primary key");
  1038. RLMAssertThrowsWithReason([StringObject objectForPrimaryKey:nil],
  1039. @"does not have a primary key");
  1040. RLMAssertThrowsWithReason([IntObject objectForPrimaryKey:nil],
  1041. @"does not have a primary key");
  1042. // wrong PK type
  1043. RLMAssertThrowsWithReasonMatching([PrimaryStringObject objectForPrimaryKey:@0],
  1044. @"Invalid value '0' of type '.*Number.*' for 'string' property 'PrimaryStringObject.stringCol'.");
  1045. RLMAssertThrowsWithReasonMatching([PrimaryStringObject objectForPrimaryKey:@[]],
  1046. @"of type '.*Array.*' for 'string' property 'PrimaryStringObject.stringCol'.");
  1047. RLMAssertThrowsWithReasonMatching([PrimaryIntObject objectForPrimaryKey:@""],
  1048. @"Invalid value '' of type '.*String.*' for 'int' property 'PrimaryIntObject.intCol'.");
  1049. RLMAssertThrowsWithReason([PrimaryIntObject objectForPrimaryKey:NSNull.null],
  1050. @"Invalid value '<null>' of type 'NSNull' for 'int' property 'PrimaryIntObject.intCol'.");
  1051. RLMAssertThrowsWithReason([PrimaryIntObject objectForPrimaryKey:nil],
  1052. @"Invalid value '(null)' of type '(null)' for 'int' property 'PrimaryIntObject.intCol'.");
  1053. RLMAssertThrowsWithReason([PrimaryStringObject objectForPrimaryKey:NSNull.null],
  1054. @"Invalid value '<null>' of type 'NSNull' for 'string' property 'PrimaryStringObject.stringCol'.");
  1055. RLMAssertThrowsWithReason([PrimaryStringObject objectForPrimaryKey:nil],
  1056. @"Invalid value '(null)' of type '(null)' for 'string' property 'PrimaryStringObject.stringCol'.");
  1057. // no object with key
  1058. XCTAssertNil([PrimaryStringObject objectForPrimaryKey:@"bad key"]);
  1059. XCTAssertNil([PrimaryIntObject objectForPrimaryKey:@1]);
  1060. // object with key exists
  1061. XCTAssertEqualObjects(strObj, [PrimaryStringObject objectForPrimaryKey:@"key"]);
  1062. XCTAssertEqualObjects(nullStrObj, [PrimaryNullableStringObject objectForPrimaryKey:NSNull.null]);
  1063. XCTAssertEqualObjects(nullStrObj, [PrimaryNullableStringObject objectForPrimaryKey:nil]);
  1064. XCTAssertEqualObjects(intObj, [PrimaryIntObject objectForPrimaryKey:@0]);
  1065. XCTAssertEqualObjects(nonNullIntObj, [PrimaryNullableIntObject objectForPrimaryKey:@0]);
  1066. XCTAssertEqualObjects(nullIntObj, [PrimaryNullableIntObject objectForPrimaryKey:NSNull.null]);
  1067. XCTAssertEqualObjects(nullIntObj, [PrimaryNullableIntObject objectForPrimaryKey:nil]);
  1068. // nil realm throws
  1069. RLMAssertThrowsWithReason([PrimaryIntObject objectInRealm:self.nonLiteralNil forPrimaryKey:@0],
  1070. @"Realm must not be nil");
  1071. }
  1072. - (void)testClassExtension {
  1073. RLMRealm *realm = [RLMRealm defaultRealm];
  1074. [realm beginWriteTransaction];
  1075. BaseClassStringObject *bObject = [[BaseClassStringObject alloc ] init];
  1076. bObject.intCol = 1;
  1077. bObject.stringCol = @"stringVal";
  1078. [realm addObject:bObject];
  1079. [realm commitWriteTransaction];
  1080. BaseClassStringObject *objectFromRealm = [BaseClassStringObject allObjects][0];
  1081. XCTAssertEqual(1, objectFromRealm.intCol);
  1082. XCTAssertEqualObjects(@"stringVal", objectFromRealm.stringCol);
  1083. }
  1084. #pragma mark - Frozen Objects
  1085. static IntObject *managedObject() {
  1086. IntObject *obj = [[IntObject alloc] init];
  1087. RLMRealm *realm = RLMRealm.defaultRealm;
  1088. [realm transactionWithBlock:^{
  1089. [realm addObject:obj];
  1090. }];
  1091. return obj;
  1092. }
  1093. - (void)testIsFrozen {
  1094. IntObject *standalone = [[IntObject alloc] init];
  1095. IntObject *managed = managedObject();
  1096. IntObject *frozen = [managed freeze];
  1097. XCTAssertFalse(standalone.isFrozen);
  1098. XCTAssertFalse(managed.isFrozen);
  1099. XCTAssertTrue(frozen.isFrozen);
  1100. }
  1101. - (void)testFreezeUnmanagedObject {
  1102. RLMAssertThrowsWithReason([[[IntObject alloc] init] freeze],
  1103. @"Unmanaged objects cannot be frozen.");
  1104. }
  1105. - (void)testFreezingFrozenObjectReturnsSelf {
  1106. IntObject *obj = managedObject();
  1107. IntObject *frozen = obj.freeze;
  1108. XCTAssertNotEqual(obj, frozen);
  1109. XCTAssertNotEqual(obj.freeze, frozen);
  1110. XCTAssertEqual(frozen, frozen.freeze);
  1111. }
  1112. - (void)testFreezingDeletedObject {
  1113. IntObject *obj = managedObject();
  1114. [obj.realm transactionWithBlock:^{
  1115. [obj.realm deleteObject:obj];
  1116. }];
  1117. RLMAssertThrowsWithReason([obj freeze],
  1118. @"Object has been deleted or invalidated.");
  1119. }
  1120. - (void)testFreezeFromWrongThread {
  1121. IntObject *obj = managedObject();
  1122. [self dispatchAsyncAndWait:^{
  1123. RLMAssertThrowsWithReason([obj freeze],
  1124. @"Realm accessed from incorrect thread");
  1125. }];
  1126. }
  1127. - (void)testAccessFrozenObjectFromDifferentThread {
  1128. IntObject *obj = managedObject();
  1129. IntObject *frozen = [obj freeze];
  1130. [self dispatchAsyncAndWait:^{
  1131. XCTAssertEqual(frozen.intCol, 0);
  1132. }];
  1133. }
  1134. - (void)testMutateFrozenObject {
  1135. IntObject *obj = managedObject();
  1136. IntObject *frozen = obj.freeze;
  1137. XCTAssertThrows(frozen.intCol = 1);
  1138. }
  1139. - (void)testObserveFrozenObject {
  1140. IntObject *frozen = [managedObject() freeze];
  1141. id block = ^(__unused BOOL deleted, __unused NSArray *changes, __unused NSError *error) {};
  1142. RLMAssertThrowsWithReason([frozen addNotificationBlock:block],
  1143. @"Frozen Realms do not change and do not have change notifications.");
  1144. }
  1145. - (void)testFrozenObjectEquality {
  1146. IntObject *liveObj = [[IntObject alloc] init];
  1147. RLMRealm *realm = RLMRealm.defaultRealm;
  1148. [realm transactionWithBlock:^{
  1149. [realm addObject:liveObj];
  1150. }];
  1151. IntObject *frozen1 = [liveObj freeze];
  1152. IntObject *frozen2 = [liveObj freeze];
  1153. XCTAssertNotEqual(frozen1, frozen2);
  1154. XCTAssertEqualObjects(frozen1, frozen2);
  1155. [realm transactionWithBlock:^{
  1156. [StringObject createInRealm:realm withValue:@[@"a"]];
  1157. }];
  1158. IntObject *frozen3 = [liveObj freeze];
  1159. XCTAssertEqualObjects(frozen1, frozen2);
  1160. XCTAssertNotEqualObjects(frozen1, frozen3);
  1161. XCTAssertNotEqualObjects(frozen2, frozen3);
  1162. }
  1163. - (void)testFrozenObjectHashing {
  1164. RLMRealm *realm = RLMRealm.defaultRealm;
  1165. [realm transactionWithBlock:^{
  1166. // NSSet does a linear search on an array for very small sets, so make
  1167. // enough objects to ensure it actually does hash lookups
  1168. for (int i = 0; i < 200; ++i) {
  1169. [IntObject createInRealm:realm withValue:@[@(i)]];
  1170. }
  1171. }];
  1172. NSMutableSet *frozenSet = [NSMutableSet new];
  1173. NSMutableSet *thawedSet = [NSMutableSet new];
  1174. RLMResults<IntObject *> *allObjects = [IntObject allObjectsInRealm:realm];
  1175. for (int i = 0; i < 100; ++i) {
  1176. [thawedSet addObject:allObjects[i]];
  1177. [frozenSet addObject:allObjects[i].freeze];
  1178. }
  1179. for (IntObject *obj in allObjects) {
  1180. XCTAssertFalse([thawedSet containsObject:obj]);
  1181. XCTAssertFalse([frozenSet containsObject:obj]);
  1182. XCTAssertEqual([frozenSet containsObject:obj.freeze], obj.intCol < 100);
  1183. }
  1184. }
  1185. - (void)testFreezeInsideWriteTransaction {
  1186. RLMRealm *realm = RLMRealm.defaultRealm;
  1187. [realm beginWriteTransaction];
  1188. IntObject *obj = [IntObject createInRealm:realm withValue:@[@1]];
  1189. RLMAssertThrowsWithReason([obj freeze], @"Cannot freeze an object in the same write transaction as it was created in.");
  1190. [realm commitWriteTransaction];
  1191. [realm beginWriteTransaction];
  1192. obj.intCol = 2;
  1193. // Frozen objects have the value of the object at the start of the transaction
  1194. XCTAssertEqual(obj.freeze.intCol, 1);
  1195. [realm cancelWriteTransaction];
  1196. }
  1197. @end