RLMRealm.mm 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101
  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 "RLMRealm_Private.hpp"
  19. #import "RLMAnalytics.hpp"
  20. #import "RLMArray_Private.hpp"
  21. #import "RLMMigration_Private.h"
  22. #import "RLMObject_Private.h"
  23. #import "RLMObject_Private.hpp"
  24. #import "RLMObjectSchema_Private.hpp"
  25. #import "RLMObjectStore.h"
  26. #import "RLMObservation.hpp"
  27. #import "RLMProperty.h"
  28. #import "RLMProperty_Private.h"
  29. #import "RLMQueryUtil.hpp"
  30. #import "RLMRealmConfiguration+Sync.h"
  31. #import "RLMRealmConfiguration_Private.hpp"
  32. #import "RLMRealmUtil.hpp"
  33. #import "RLMSchema_Private.hpp"
  34. #import "RLMThreadSafeReference_Private.hpp"
  35. #import "RLMUpdateChecker.hpp"
  36. #import "RLMUtil.hpp"
  37. #include "impl/realm_coordinator.hpp"
  38. #include "object_store.hpp"
  39. #include "schema.hpp"
  40. #include "shared_realm.hpp"
  41. #include "thread_safe_reference.hpp"
  42. #include "util/scheduler.hpp"
  43. #include <realm/disable_sync_to_disk.hpp>
  44. #include <realm/util/scope_exit.hpp>
  45. #include <realm/version.hpp>
  46. #if REALM_ENABLE_SYNC
  47. #import "RLMSyncManager_Private.h"
  48. #import "RLMSyncSession_Private.hpp"
  49. #import "RLMSyncUtil_Private.hpp"
  50. #import "sync/async_open_task.hpp"
  51. #import "sync/sync_session.hpp"
  52. #endif
  53. using namespace realm;
  54. using util::File;
  55. @interface RLMRealmNotificationToken : RLMNotificationToken
  56. @property (nonatomic, strong) RLMRealm *realm;
  57. @property (nonatomic, copy) RLMNotificationBlock block;
  58. @end
  59. @interface RLMRealm ()
  60. @property (nonatomic, strong) NSHashTable<RLMRealmNotificationToken *> *notificationHandlers;
  61. - (void)sendNotifications:(RLMNotification)notification;
  62. @end
  63. void RLMDisableSyncToDisk() {
  64. realm::disable_sync_to_disk();
  65. }
  66. static std::atomic<bool> s_set_skip_backup_attribute{true};
  67. void RLMSetSkipBackupAttribute(bool value) {
  68. s_set_skip_backup_attribute = value;
  69. }
  70. static void RLMAddSkipBackupAttributeToItemAtPath(std::string_view path) {
  71. [[NSURL fileURLWithPath:@(path.data())] setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
  72. }
  73. @implementation RLMRealmNotificationToken
  74. - (void)invalidate {
  75. [_realm verifyThread];
  76. [_realm.notificationHandlers removeObject:self];
  77. _realm = nil;
  78. _block = nil;
  79. }
  80. - (void)suppressNextNotification {
  81. // Temporarily replace the block with one which restores the old block
  82. // rather than producing a notification.
  83. // This briefly creates a retain cycle but it's fine because the block will
  84. // be synchronously called shortly after this method is called. Unlike with
  85. // collection notifications, this does not have to go through the object
  86. // store or do fancy things to handle transaction coalescing because it's
  87. // called synchronously by the obj-c code and not by the object store.
  88. auto notificationBlock = _block;
  89. _block = ^(RLMNotification, RLMRealm *) {
  90. _block = notificationBlock;
  91. };
  92. }
  93. - (void)dealloc {
  94. if (_realm || _block) {
  95. NSLog(@"RLMNotificationToken released without unregistering a notification. You must hold "
  96. @"on to the RLMNotificationToken returned from addNotificationBlock and call "
  97. @"-[RLMNotificationToken invalidate] when you no longer wish to receive RLMRealm notifications.");
  98. }
  99. }
  100. @end
  101. #if !REALM_ENABLE_SYNC
  102. @interface RLMAsyncOpenTask : NSObject
  103. @end
  104. @implementation RLMAsyncOpenTask
  105. @end
  106. #endif
  107. static bool shouldForciblyDisableEncryption() {
  108. static bool disableEncryption = getenv("REALM_DISABLE_ENCRYPTION");
  109. return disableEncryption;
  110. }
  111. NSData *RLMRealmValidatedEncryptionKey(NSData *key) {
  112. if (shouldForciblyDisableEncryption()) {
  113. return nil;
  114. }
  115. if (key && key.length != 64) {
  116. @throw RLMException(@"Encryption key must be exactly 64 bytes long");
  117. }
  118. return key;
  119. }
  120. @implementation RLMRealm {
  121. NSHashTable<RLMFastEnumerator *> *_collectionEnumerators;
  122. bool _sendingNotifications;
  123. }
  124. + (BOOL)isCoreDebug {
  125. return realm::Version::has_feature(realm::feature_Debug);
  126. }
  127. + (void)initialize {
  128. static bool initialized;
  129. if (initialized) {
  130. return;
  131. }
  132. initialized = true;
  133. RLMCheckForUpdates();
  134. RLMSendAnalytics();
  135. }
  136. - (instancetype)initPrivate {
  137. self = [super init];
  138. return self;
  139. }
  140. - (BOOL)isEmpty {
  141. return realm::ObjectStore::is_empty(self.group);
  142. }
  143. - (void)verifyThread {
  144. try {
  145. _realm->verify_thread();
  146. }
  147. catch (std::exception const& e) {
  148. @throw RLMException(e);
  149. }
  150. }
  151. - (BOOL)inWriteTransaction {
  152. return _realm->is_in_transaction();
  153. }
  154. - (realm::Group &)group {
  155. return _realm->read_group();
  156. }
  157. - (BOOL)autorefresh {
  158. return _realm->auto_refresh();
  159. }
  160. - (void)setAutorefresh:(BOOL)autorefresh {
  161. try {
  162. _realm->set_auto_refresh(autorefresh);
  163. }
  164. catch (std::exception const& e) {
  165. @throw RLMException(e);
  166. }
  167. }
  168. + (instancetype)defaultRealm {
  169. return [RLMRealm realmWithConfiguration:[RLMRealmConfiguration rawDefaultConfiguration] error:nil];
  170. }
  171. + (instancetype)defaultRealmForQueue:(dispatch_queue_t)queue {
  172. return [RLMRealm realmWithConfiguration:[RLMRealmConfiguration rawDefaultConfiguration] queue:queue error:nil];
  173. }
  174. + (instancetype)realmWithURL:(NSURL *)fileURL {
  175. RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration];
  176. configuration.fileURL = fileURL;
  177. return [RLMRealm realmWithConfiguration:configuration error:nil];
  178. }
  179. static dispatch_queue_t s_async_open_queue = dispatch_queue_create("io.realm.asyncOpenDispatchQueue",
  180. DISPATCH_QUEUE_CONCURRENT);
  181. void RLMSetAsyncOpenQueue(dispatch_queue_t queue) {
  182. s_async_open_queue = queue;
  183. }
  184. // The server doesn't send us the subscriptions for permission types until the
  185. // first subscription is created. This is fine for synchronous opens (if we're
  186. // creating a new Realm we create the permission objects ourselves), but it
  187. // causes issues for asyncOpen because it means that when our download completes
  188. // we don't actually have the full Realm state yet.
  189. static void waitForPartialSyncSubscriptions(dispatch_queue_t queue, Realm::Config config, void (^completion)()) {
  190. #if REALM_ENABLE_SYNC
  191. config.scheduler = realm::util::Scheduler::make_dispatch((__bridge void *)queue);
  192. auto realm = Realm::get_shared_realm(std::move(config));
  193. auto table = ObjectStore::table_for_object_type(realm->read_group(), "__ResultSets");
  194. realm->begin_transaction();
  195. auto row = realm::sync::create_object(static_cast<Transaction&>(realm->read_group()), *table);
  196. // Set expires_at to time 0 so that this object will be cleaned up the first
  197. // time the user creates a subscription
  198. auto expires_at_col = table->get_column_key("expires_at");
  199. if (!expires_at_col) {
  200. expires_at_col = table->add_column(type_Timestamp, "expires_at", true);
  201. }
  202. row.set(expires_at_col, Timestamp(0, 0));
  203. realm->commit_transaction();
  204. struct State {
  205. std::shared_ptr<Realm> realm;
  206. Results results;
  207. NotificationToken token;
  208. };
  209. auto state = std::make_shared<State>(State{realm, Results(realm, table), {}});
  210. state->token = state->results.add_notification_callback([=](CollectionChangeSet const&, std::exception_ptr) {
  211. if (table->size() > 1) {
  212. state->token = {};
  213. completion();
  214. }
  215. });
  216. #else
  217. static_cast<void>(queue);
  218. static_cast<void>(config);
  219. static_cast<void>(completion);
  220. #endif
  221. }
  222. + (RLMAsyncOpenTask *)asyncOpenWithConfiguration:(RLMRealmConfiguration *)configuration
  223. callbackQueue:(dispatch_queue_t)callbackQueue
  224. callback:(RLMAsyncOpenRealmCallback)callback {
  225. auto openCompletion = [=](ThreadSafeReference ref, std::exception_ptr err) {
  226. @autoreleasepool {
  227. if (err) {
  228. try {
  229. std::rethrow_exception(err);
  230. }
  231. catch (...) {
  232. NSError *error;
  233. RLMRealmTranslateException(&error);
  234. dispatch_async(callbackQueue, ^{
  235. callback(nil, error);
  236. });
  237. }
  238. return;
  239. }
  240. auto realm = Realm::get_shared_realm(std::move(ref));
  241. auto complete = [=] {
  242. dispatch_async(callbackQueue, [=]() mutable {
  243. @autoreleasepool {
  244. NSError *error;
  245. RLMRealm *localRealm = [RLMRealm realmWithConfiguration:configuration
  246. queue:callbackQueue
  247. error:&error];
  248. realm.reset();
  249. callback(localRealm, error);
  250. }
  251. });
  252. };
  253. bool needsSubscriptions = realm->is_partial() && ObjectStore::table_for_object_type(realm->read_group(), "__ResultSets")->size() == 0;
  254. if (needsSubscriptions) {
  255. auto queue = dispatch_queue_create("io.realm.asyncOpenWaitForPartialSync", DISPATCH_QUEUE_SERIAL);
  256. dispatch_sync(queue, ^{
  257. @autoreleasepool {
  258. waitForPartialSyncSubscriptions(queue, realm->config(), complete);
  259. }
  260. });
  261. }
  262. else {
  263. complete();
  264. }
  265. }
  266. };
  267. RLMAsyncOpenTask *ret = [RLMAsyncOpenTask new];
  268. dispatch_async(s_async_open_queue, ^{
  269. @autoreleasepool {
  270. Realm::Config& config = configuration.config;
  271. if (config.sync_config) {
  272. #if REALM_ENABLE_SYNC
  273. auto task = realm::Realm::get_synchronized_realm(config);
  274. ret.task = task;
  275. task->start(openCompletion);
  276. #else
  277. @throw RLMException(@"Realm was not built with sync enabled");
  278. #endif
  279. }
  280. else {
  281. try {
  282. openCompletion(realm::_impl::RealmCoordinator::get_coordinator(config)->get_unbound_realm(), nullptr);
  283. }
  284. catch (...) {
  285. openCompletion({}, std::current_exception());
  286. }
  287. }
  288. }
  289. });
  290. return ret;
  291. }
  292. // ARC tries to eliminate calls to autorelease when the value is then immediately
  293. // returned, but this results in significantly different semantics between debug
  294. // and release builds for RLMRealm, so force it to always autorelease.
  295. static id RLMAutorelease(__unsafe_unretained id value) {
  296. // +1 __bridge_retained, -1 CFAutorelease
  297. return value ? (__bridge id)CFAutorelease((__bridge_retained CFTypeRef)value) : nil;
  298. }
  299. + (instancetype)realmWithSharedRealm:(SharedRealm)sharedRealm schema:(RLMSchema *)schema {
  300. RLMRealm *realm = [[RLMRealm alloc] initPrivate];
  301. realm->_realm = sharedRealm;
  302. realm->_dynamic = YES;
  303. realm->_schema = schema;
  304. realm->_info = RLMSchemaInfo(realm);
  305. return RLMAutorelease(realm);
  306. }
  307. REALM_NOINLINE void RLMRealmTranslateException(NSError **error) {
  308. try {
  309. throw;
  310. }
  311. catch (RealmFileException const& ex) {
  312. switch (ex.kind()) {
  313. case RealmFileException::Kind::PermissionDenied:
  314. RLMSetErrorOrThrow(RLMMakeError(RLMErrorFilePermissionDenied, ex), error);
  315. break;
  316. case RealmFileException::Kind::IncompatibleLockFile: {
  317. NSString *err = @"Realm file is currently open in another process "
  318. "which cannot share access with this process. All "
  319. "processes sharing a single file must be the same "
  320. "architecture. For sharing files between the Realm "
  321. "Browser and an iOS simulator, this means that you "
  322. "must use a 64-bit simulator.";
  323. RLMSetErrorOrThrow(RLMMakeError(RLMErrorIncompatibleLockFile,
  324. File::PermissionDenied(err.UTF8String, ex.path())), error);
  325. break;
  326. }
  327. case RealmFileException::Kind::NotFound:
  328. RLMSetErrorOrThrow(RLMMakeError(RLMErrorFileNotFound, ex), error);
  329. break;
  330. case RealmFileException::Kind::Exists:
  331. RLMSetErrorOrThrow(RLMMakeError(RLMErrorFileExists, ex), error);
  332. break;
  333. case RealmFileException::Kind::BadHistoryError: {
  334. NSString *err = @"Realm file's history format is incompatible with the "
  335. "settings in the configuration object being used to open "
  336. "the Realm. Note that Realms configured for sync cannot be "
  337. "opened as non-synced Realms, and vice versa. Otherwise, the "
  338. "file may be corrupt.";
  339. RLMSetErrorOrThrow(RLMMakeError(RLMErrorFileAccess,
  340. File::AccessError(err.UTF8String, ex.path())), error);
  341. break;
  342. }
  343. case RealmFileException::Kind::AccessError:
  344. RLMSetErrorOrThrow(RLMMakeError(RLMErrorFileAccess, ex), error);
  345. break;
  346. case RealmFileException::Kind::FormatUpgradeRequired:
  347. RLMSetErrorOrThrow(RLMMakeError(RLMErrorFileFormatUpgradeRequired, ex), error);
  348. break;
  349. default:
  350. RLMSetErrorOrThrow(RLMMakeError(RLMErrorFail, ex), error);
  351. break;
  352. }
  353. }
  354. catch (AddressSpaceExhausted const &ex) {
  355. RLMSetErrorOrThrow(RLMMakeError(RLMErrorAddressSpaceExhausted, ex), error);
  356. }
  357. catch (SchemaMismatchException const& ex) {
  358. RLMSetErrorOrThrow(RLMMakeError(RLMErrorSchemaMismatch, ex), error);
  359. }
  360. catch (std::system_error const& ex) {
  361. RLMSetErrorOrThrow(RLMMakeError(ex), error);
  362. }
  363. catch (const std::exception &exp) {
  364. RLMSetErrorOrThrow(RLMMakeError(RLMErrorFail, exp), error);
  365. }
  366. }
  367. REALM_NOINLINE static void translateSharedGroupOpenException(NSError **error) {
  368. try {
  369. throw;
  370. }
  371. catch (...) {
  372. RLMRealmTranslateException(error);
  373. }
  374. }
  375. + (instancetype)realmWithConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error {
  376. return [self realmWithConfiguration:configuration queue:nil error:error];
  377. }
  378. + (instancetype)realmWithConfiguration:(RLMRealmConfiguration *)configuration
  379. queue:(dispatch_queue_t)queue
  380. error:(NSError **)error {
  381. bool dynamic = configuration.dynamic;
  382. bool cache = configuration.cache;
  383. bool readOnly = configuration.readOnly;
  384. // The main thread and main queue share a cache key of 1 so that they give
  385. // the same instance. Other Realms are keyed on either the thread or the queue.
  386. // Note that despite being a void* the cache key is not actually a pointer;
  387. // this is just an artifact of NSMapTable's strange API.
  388. void *cacheKey = reinterpret_cast<void *>(1);
  389. if (queue) {
  390. if (queue != dispatch_get_main_queue()) {
  391. cacheKey = (__bridge void *)queue;
  392. }
  393. }
  394. else {
  395. if (!pthread_main_np()) {
  396. cacheKey = pthread_self();
  397. }
  398. }
  399. {
  400. Realm::Config const& config = configuration.config;
  401. // try to reuse existing realm first
  402. if (cache || dynamic) {
  403. if (RLMRealm *realm = RLMGetThreadLocalCachedRealmForPath(config.path, cacheKey)) {
  404. auto const& old_config = realm->_realm->config();
  405. if (old_config.immutable() != config.immutable()
  406. || old_config.read_only_alternative() != config.read_only_alternative()) {
  407. @throw RLMException(@"Realm at path '%s' already opened with different read permissions", config.path.c_str());
  408. }
  409. if (old_config.in_memory != config.in_memory) {
  410. @throw RLMException(@"Realm at path '%s' already opened with different inMemory settings", config.path.c_str());
  411. }
  412. if (realm->_dynamic != dynamic) {
  413. @throw RLMException(@"Realm at path '%s' already opened with different dynamic settings", config.path.c_str());
  414. }
  415. if (old_config.encryption_key != config.encryption_key) {
  416. @throw RLMException(@"Realm at path '%s' already opened with different encryption key", config.path.c_str());
  417. }
  418. return RLMAutorelease(realm);
  419. }
  420. }
  421. }
  422. configuration = [configuration copy];
  423. Realm::Config& config = configuration.config;
  424. RLMRealm *realm = [[self alloc] initPrivate];
  425. realm->_dynamic = dynamic;
  426. // protects the realm cache and accessors cache
  427. static std::mutex& initLock = *new std::mutex();
  428. std::lock_guard<std::mutex> lock(initLock);
  429. try {
  430. if (queue) {
  431. if (queue == dispatch_get_main_queue()) {
  432. config.scheduler = realm::util::Scheduler::make_runloop(CFRunLoopGetMain());
  433. }
  434. else {
  435. config.scheduler = realm::util::Scheduler::make_dispatch((__bridge void *)queue);
  436. }
  437. if (!config.scheduler->is_on_thread()) {
  438. throw RLMException(@"Realm opened from incorrect dispatch queue.");
  439. }
  440. }
  441. else {
  442. // If the source config was read from a Realm it may already have a
  443. // scheduler, and we don't want to reuse it.
  444. config.scheduler = nullptr;
  445. }
  446. realm->_realm = Realm::get_shared_realm(config);
  447. }
  448. catch (...) {
  449. translateSharedGroupOpenException(error);
  450. return nil;
  451. }
  452. // if we have a cached realm on another thread we can skip a few steps and
  453. // just grab its schema
  454. @autoreleasepool {
  455. // ensure that cachedRealm doesn't end up in this thread's autorelease pool
  456. if (auto cachedRealm = RLMGetAnyCachedRealmForPath(config.path)) {
  457. realm->_realm->set_schema_subset(cachedRealm->_realm->schema());
  458. realm->_schema = cachedRealm.schema;
  459. realm->_info = cachedRealm->_info.clone(cachedRealm->_realm->schema(), realm);
  460. }
  461. }
  462. if (realm->_schema) { }
  463. else if (dynamic) {
  464. realm->_schema = [RLMSchema dynamicSchemaFromObjectStoreSchema:realm->_realm->schema()];
  465. realm->_info = RLMSchemaInfo(realm);
  466. }
  467. else {
  468. // set/align schema or perform migration if needed
  469. RLMSchema *schema = configuration.customSchema ?: RLMSchema.sharedSchema;
  470. Realm::MigrationFunction migrationFunction;
  471. auto migrationBlock = configuration.migrationBlock;
  472. if (migrationBlock && configuration.schemaVersion > 0) {
  473. migrationFunction = [=](SharedRealm old_realm, SharedRealm realm, Schema& mutableSchema) {
  474. RLMSchema *oldSchema = [RLMSchema dynamicSchemaFromObjectStoreSchema:old_realm->schema()];
  475. RLMRealm *oldRealm = [RLMRealm realmWithSharedRealm:old_realm schema:oldSchema];
  476. // The destination RLMRealm can't just use the schema from the
  477. // SharedRealm because it doesn't have information about whether or
  478. // not a class was defined in Swift, which effects how new objects
  479. // are created
  480. RLMRealm *newRealm = [RLMRealm realmWithSharedRealm:realm schema:schema.copy];
  481. [[[RLMMigration alloc] initWithRealm:newRealm oldRealm:oldRealm schema:mutableSchema] execute:migrationBlock];
  482. oldRealm->_realm = nullptr;
  483. newRealm->_realm = nullptr;
  484. };
  485. }
  486. try {
  487. realm->_realm->update_schema(schema.objectStoreCopy, config.schema_version,
  488. std::move(migrationFunction));
  489. }
  490. catch (...) {
  491. RLMRealmTranslateException(error);
  492. return nil;
  493. }
  494. realm->_schema = schema;
  495. realm->_info = RLMSchemaInfo(realm);
  496. RLMRealmCreateAccessors(realm.schema);
  497. if (!readOnly) {
  498. REALM_ASSERT(!realm->_realm->is_in_read_transaction());
  499. if (s_set_skip_backup_attribute) {
  500. RLMAddSkipBackupAttributeToItemAtPath(config.path + ".management");
  501. RLMAddSkipBackupAttributeToItemAtPath(config.path + ".lock");
  502. RLMAddSkipBackupAttributeToItemAtPath(config.path + ".note");
  503. }
  504. }
  505. }
  506. if (cache) {
  507. RLMCacheRealm(config.path, cacheKey, realm);
  508. }
  509. if (!readOnly) {
  510. realm->_realm->m_binding_context = RLMCreateBindingContext(realm);
  511. realm->_realm->m_binding_context->realm = realm->_realm;
  512. }
  513. return RLMAutorelease(realm);
  514. }
  515. + (void)resetRealmState {
  516. RLMClearRealmCache();
  517. realm::_impl::RealmCoordinator::clear_cache();
  518. [RLMRealmConfiguration resetRealmConfigurationState];
  519. }
  520. - (void)verifyNotificationsAreSupported:(bool)isCollection {
  521. [self verifyThread];
  522. if (_realm->config().immutable()) {
  523. @throw RLMException(@"Read-only Realms do not change and do not have change notifications.");
  524. }
  525. if (_realm->is_frozen()) {
  526. @throw RLMException(@"Frozen Realms do not change and do not have change notifications.");
  527. }
  528. if (!_realm->can_deliver_notifications()) {
  529. @throw RLMException(@"Can only add notification blocks from within runloops.");
  530. }
  531. if (isCollection && _realm->is_in_transaction()) {
  532. @throw RLMException(@"Cannot register notification blocks from within write transactions.");
  533. }
  534. }
  535. - (RLMNotificationToken *)addNotificationBlock:(RLMNotificationBlock)block {
  536. if (!block) {
  537. @throw RLMException(@"The notification block should not be nil");
  538. }
  539. [self verifyNotificationsAreSupported:false];
  540. _realm->read_group();
  541. if (!_notificationHandlers) {
  542. _notificationHandlers = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory];
  543. }
  544. RLMRealmNotificationToken *token = [[RLMRealmNotificationToken alloc] init];
  545. token.realm = self;
  546. token.block = block;
  547. [_notificationHandlers addObject:token];
  548. return token;
  549. }
  550. - (void)sendNotifications:(RLMNotification)notification {
  551. NSAssert(!_realm->config().immutable(), @"Read-only realms do not have notifications");
  552. if (_sendingNotifications) {
  553. return;
  554. }
  555. NSUInteger count = _notificationHandlers.count;
  556. if (count == 0) {
  557. return;
  558. }
  559. _sendingNotifications = true;
  560. auto cleanup = realm::util::make_scope_exit([&]() noexcept {
  561. _sendingNotifications = false;
  562. });
  563. // call this realm's notification blocks
  564. if (count == 1) {
  565. if (auto block = [_notificationHandlers.anyObject block]) {
  566. block(notification, self);
  567. }
  568. }
  569. else {
  570. for (RLMRealmNotificationToken *token in _notificationHandlers.allObjects) {
  571. if (auto block = token.block) {
  572. block(notification, self);
  573. }
  574. }
  575. }
  576. }
  577. - (RLMRealmConfiguration *)configuration {
  578. RLMRealmConfiguration *configuration = [[RLMRealmConfiguration alloc] init];
  579. configuration.config = _realm->config();
  580. configuration.dynamic = _dynamic;
  581. configuration.customSchema = _schema;
  582. return configuration;
  583. }
  584. - (void)beginWriteTransaction {
  585. [self beginWriteTransactionWithError:nil];
  586. }
  587. - (BOOL)beginWriteTransactionWithError:(NSError **)error {
  588. try {
  589. _realm->begin_transaction();
  590. return YES;
  591. }
  592. catch (...) {
  593. RLMRealmTranslateException(error);
  594. return NO;
  595. }
  596. }
  597. - (void)commitWriteTransaction {
  598. [self commitWriteTransaction:nil];
  599. }
  600. - (BOOL)commitWriteTransaction:(NSError **)error {
  601. return [self commitWriteTransactionWithoutNotifying:@[] error:error];
  602. }
  603. - (BOOL)commitWriteTransactionWithoutNotifying:(NSArray<RLMNotificationToken *> *)tokens error:(NSError **)error {
  604. for (RLMNotificationToken *token in tokens) {
  605. if (token.realm != self) {
  606. @throw RLMException(@"Incorrect Realm: only notifications for the Realm being modified can be skipped.");
  607. }
  608. [token suppressNextNotification];
  609. }
  610. try {
  611. _realm->commit_transaction();
  612. return YES;
  613. }
  614. catch (...) {
  615. RLMRealmTranslateException(error);
  616. return NO;
  617. }
  618. }
  619. - (void)transactionWithBlock:(__attribute__((noescape)) void(^)(void))block {
  620. [self transactionWithBlock:block error:nil];
  621. }
  622. - (BOOL)transactionWithBlock:(__attribute__((noescape)) void(^)(void))block error:(NSError **)outError {
  623. return [self transactionWithoutNotifying:@[] block:block error:outError];
  624. }
  625. - (void)transactionWithoutNotifying:(NSArray<RLMNotificationToken *> *)tokens block:(__attribute__((noescape)) void(^)(void))block {
  626. [self transactionWithoutNotifying:tokens block:block error:nil];
  627. }
  628. - (BOOL)transactionWithoutNotifying:(NSArray<RLMNotificationToken *> *)tokens block:(__attribute__((noescape)) void(^)(void))block error:(NSError **)error {
  629. [self beginWriteTransactionWithError:error];
  630. block();
  631. if (_realm->is_in_transaction()) {
  632. return [self commitWriteTransactionWithoutNotifying:tokens error:error];
  633. }
  634. return YES;
  635. }
  636. - (void)cancelWriteTransaction {
  637. try {
  638. _realm->cancel_transaction();
  639. }
  640. catch (std::exception &ex) {
  641. @throw RLMException(ex);
  642. }
  643. }
  644. - (void)invalidate {
  645. if (_realm->is_in_transaction()) {
  646. NSLog(@"WARNING: An RLMRealm instance was invalidated during a write "
  647. "transaction and all pending changes have been rolled back.");
  648. }
  649. [self detachAllEnumerators];
  650. for (auto& objectInfo : _info) {
  651. for (RLMObservationInfo *info : objectInfo.second.observedObjects) {
  652. info->willChange(RLMInvalidatedKey);
  653. }
  654. }
  655. _realm->invalidate();
  656. for (auto& objectInfo : _info) {
  657. for (RLMObservationInfo *info : objectInfo.second.observedObjects) {
  658. info->didChange(RLMInvalidatedKey);
  659. }
  660. }
  661. if (_realm->is_frozen()) {
  662. _realm->close();
  663. }
  664. }
  665. - (nullable id)resolveThreadSafeReference:(RLMThreadSafeReference *)reference {
  666. return [reference resolveReferenceInRealm:self];
  667. }
  668. /**
  669. Replaces all string columns in this Realm with a string enumeration column and compacts the
  670. database file.
  671. Cannot be called from a write transaction.
  672. Compaction will not occur if other `RLMRealm` instances exist.
  673. While compaction is in progress, attempts by other threads or processes to open the database will
  674. wait.
  675. Be warned that resource requirements for compaction is proportional to the amount of live data in
  676. the database.
  677. Compaction works by writing the database contents to a temporary database file and then replacing
  678. the database with the temporary one. The name of the temporary file is formed by appending
  679. `.tmp_compaction_space` to the name of the database.
  680. @return YES if the compaction succeeded.
  681. */
  682. - (BOOL)compact {
  683. // compact() automatically ends the read transaction, but we need to clean
  684. // up cached state and send invalidated notifications when that happens, so
  685. // explicitly end it first unless we're in a write transaction (in which
  686. // case compact() will throw an exception)
  687. if (!_realm->is_in_transaction()) {
  688. [self invalidate];
  689. }
  690. try {
  691. return _realm->compact();
  692. }
  693. catch (std::exception const& ex) {
  694. @throw RLMException(ex);
  695. }
  696. }
  697. - (void)dealloc {
  698. if (_realm) {
  699. if (_realm->is_in_transaction()) {
  700. [self cancelWriteTransaction];
  701. NSLog(@"WARNING: An RLMRealm instance was deallocated during a write transaction and all "
  702. "pending changes have been rolled back. Make sure to retain a reference to the "
  703. "RLMRealm for the duration of the write transaction.");
  704. }
  705. }
  706. }
  707. - (BOOL)refresh {
  708. try {
  709. return _realm->refresh();
  710. }
  711. catch (std::exception const& e) {
  712. @throw RLMException(e);
  713. }
  714. }
  715. - (void)addObject:(__unsafe_unretained RLMObject *const)object {
  716. RLMAddObjectToRealm(object, self, RLMUpdatePolicyError);
  717. }
  718. - (void)addObjects:(id<NSFastEnumeration>)objects {
  719. for (RLMObject *obj in objects) {
  720. if (![obj isKindOfClass:RLMObjectBase.class]) {
  721. @throw RLMException(@"Cannot insert objects of type %@ with addObjects:. Only RLMObjects are supported.",
  722. NSStringFromClass(obj.class));
  723. }
  724. [self addObject:obj];
  725. }
  726. }
  727. - (void)addOrUpdateObject:(RLMObject *)object {
  728. // verify primary key
  729. if (!object.objectSchema.primaryKeyProperty) {
  730. @throw RLMException(@"'%@' does not have a primary key and can not be updated", object.objectSchema.className);
  731. }
  732. RLMAddObjectToRealm(object, self, RLMUpdatePolicyUpdateAll);
  733. }
  734. - (void)addOrUpdateObjects:(id<NSFastEnumeration>)objects {
  735. for (RLMObject *obj in objects) {
  736. if (![obj isKindOfClass:RLMObjectBase.class]) {
  737. @throw RLMException(@"Cannot add or update objects of type %@ with addOrUpdateObjects:. Only RLMObjects are"
  738. " supported.",
  739. NSStringFromClass(obj.class));
  740. }
  741. [self addOrUpdateObject:obj];
  742. }
  743. }
  744. - (void)deleteObject:(RLMObject *)object {
  745. RLMDeleteObjectFromRealm(object, self);
  746. }
  747. - (void)deleteObjects:(id<NSFastEnumeration>)objects {
  748. id idObjects = objects;
  749. if ([idObjects respondsToSelector:@selector(realm)]
  750. && [idObjects respondsToSelector:@selector(deleteObjectsFromRealm)]) {
  751. if (self != (RLMRealm *)[idObjects realm]) {
  752. @throw RLMException(@"Can only delete objects from the Realm they belong to.");
  753. }
  754. [idObjects deleteObjectsFromRealm];
  755. return;
  756. }
  757. if (auto array = RLMDynamicCast<RLMArray>(objects)) {
  758. if (array.type != RLMPropertyTypeObject) {
  759. @throw RLMException(@"Cannot delete objects from RLMArray<%@>: only RLMObjects can be deleted.",
  760. RLMTypeToString(array.type));
  761. }
  762. }
  763. for (RLMObject *obj in objects) {
  764. if (![obj isKindOfClass:RLMObjectBase.class]) {
  765. @throw RLMException(@"Cannot delete objects of type %@ with deleteObjects:. Only RLMObjects can be deleted.",
  766. NSStringFromClass(obj.class));
  767. }
  768. RLMDeleteObjectFromRealm(obj, self);
  769. }
  770. }
  771. - (void)deleteAllObjects {
  772. RLMDeleteAllObjectsFromRealm(self);
  773. }
  774. - (RLMResults *)allObjects:(NSString *)objectClassName {
  775. return RLMGetObjects(self, objectClassName, nil);
  776. }
  777. - (RLMResults *)objects:(NSString *)objectClassName where:(NSString *)predicateFormat, ... {
  778. va_list args;
  779. va_start(args, predicateFormat);
  780. RLMResults *results = [self objects:objectClassName where:predicateFormat args:args];
  781. va_end(args);
  782. return results;
  783. }
  784. - (RLMResults *)objects:(NSString *)objectClassName where:(NSString *)predicateFormat args:(va_list)args {
  785. return [self objects:objectClassName withPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]];
  786. }
  787. - (RLMResults *)objects:(NSString *)objectClassName withPredicate:(NSPredicate *)predicate {
  788. return RLMGetObjects(self, objectClassName, predicate);
  789. }
  790. - (RLMObject *)objectWithClassName:(NSString *)className forPrimaryKey:(id)primaryKey {
  791. return RLMGetObject(self, className, primaryKey);
  792. }
  793. + (uint64_t)schemaVersionAtURL:(NSURL *)fileURL encryptionKey:(NSData *)key error:(NSError **)error {
  794. RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init];
  795. try {
  796. config.fileURL = fileURL;
  797. config.encryptionKey = RLMRealmValidatedEncryptionKey(key);
  798. uint64_t version = Realm::get_schema_version(config.config);
  799. if (version == realm::ObjectStore::NotVersioned) {
  800. RLMSetErrorOrThrow([NSError errorWithDomain:RLMErrorDomain code:RLMErrorFail userInfo:@{NSLocalizedDescriptionKey:@"Cannot open an uninitialized realm in read-only mode"}], error);
  801. }
  802. return version;
  803. }
  804. catch (...) {
  805. translateSharedGroupOpenException(error);
  806. return RLMNotVersioned;
  807. }
  808. }
  809. + (BOOL)performMigrationForConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error {
  810. if (RLMGetAnyCachedRealmForPath(configuration.config.path)) {
  811. @throw RLMException(@"Cannot migrate Realms that are already open.");
  812. }
  813. NSError *localError; // Prevents autorelease
  814. BOOL success;
  815. @autoreleasepool {
  816. success = [RLMRealm realmWithConfiguration:configuration error:&localError] != nil;
  817. }
  818. if (!success && error) {
  819. *error = localError; // Must set outside pool otherwise will free anyway
  820. }
  821. return success;
  822. }
  823. - (RLMObject *)createObject:(NSString *)className withValue:(id)value {
  824. return (RLMObject *)RLMCreateObjectInRealmWithValue(self, className, value, RLMUpdatePolicyError);
  825. }
  826. - (BOOL)writeCopyToURL:(NSURL *)fileURL encryptionKey:(NSData *)key error:(NSError **)error {
  827. key = RLMRealmValidatedEncryptionKey(key);
  828. NSString *path = fileURL.path;
  829. try {
  830. _realm->write_copy(path.UTF8String, {static_cast<const char *>(key.bytes), key.length});
  831. return YES;
  832. }
  833. catch (...) {
  834. __autoreleasing NSError *dummyError;
  835. if (!error) {
  836. error = &dummyError;
  837. }
  838. RLMRealmTranslateException(error);
  839. return NO;
  840. }
  841. return NO;
  842. }
  843. + (BOOL)fileExistsForConfiguration:(RLMRealmConfiguration *)config {
  844. return [NSFileManager.defaultManager fileExistsAtPath:config.pathOnDisk];
  845. }
  846. + (BOOL)deleteFilesForConfiguration:(RLMRealmConfiguration *)config error:(NSError **)error {
  847. auto& path = config.config.path;
  848. bool anyDeleted = false;
  849. NSError *localError;
  850. bool didCall = DB::call_with_lock(path, [&](auto const& path) {
  851. NSURL *url = [NSURL fileURLWithPath:@(path.c_str())];
  852. NSFileManager *fm = NSFileManager.defaultManager;
  853. anyDeleted = [fm removeItemAtURL:url error:&localError];
  854. if (localError && localError.code != NSFileNoSuchFileError) {
  855. return;
  856. }
  857. [fm removeItemAtURL:[url URLByAppendingPathExtension:@"management"] error:&localError];
  858. if (localError && localError.code != NSFileNoSuchFileError) {
  859. return;
  860. }
  861. [fm removeItemAtURL:[url URLByAppendingPathExtension:@"note"] error:&localError];
  862. });
  863. if (error && localError && localError.code != NSFileNoSuchFileError) {
  864. *error = localError;
  865. }
  866. else if (!didCall) {
  867. if (error) {
  868. NSString *msg = [NSString stringWithFormat:@"Realm file at path %s cannot be deleted because it is currently opened.", path.c_str()];
  869. *error = [NSError errorWithDomain:RLMErrorDomain
  870. code:RLMErrorAlreadyOpen
  871. userInfo:@{NSLocalizedDescriptionKey: msg}];
  872. }
  873. }
  874. return anyDeleted;
  875. }
  876. - (BOOL)isFrozen {
  877. return _realm->is_frozen();
  878. }
  879. - (RLMRealm *)freeze {
  880. [self verifyThread];
  881. return self.isFrozen ? self : RLMGetFrozenRealmForSourceRealm(self);
  882. }
  883. - (RLMRealm *)frozenCopy {
  884. try {
  885. RLMRealm *realm = [[RLMRealm alloc] initPrivate];
  886. realm->_realm = _realm->freeze();
  887. realm->_realm->set_schema_subset(_realm->schema());
  888. realm->_realm->read_group();
  889. realm->_dynamic = _dynamic;
  890. realm->_schema = _schema;
  891. realm->_info = RLMSchemaInfo(realm);
  892. return realm;
  893. }
  894. catch (std::exception const& e) {
  895. @throw RLMException(e);
  896. }
  897. }
  898. #if REALM_ENABLE_SYNC
  899. using Privilege = realm::ComputedPrivileges;
  900. static bool hasPrivilege(realm::ComputedPrivileges actual, realm::ComputedPrivileges expected) {
  901. return (static_cast<int>(actual) & static_cast<int>(expected)) == static_cast<int>(expected);
  902. }
  903. - (RLMRealmPrivileges)privilegesForRealm {
  904. auto p = _realm->get_privileges();
  905. return {
  906. .read = hasPrivilege(p, Privilege::Read),
  907. .update = hasPrivilege(p, Privilege::Update),
  908. .setPermissions = hasPrivilege(p, Privilege::SetPermissions),
  909. .modifySchema = hasPrivilege(p, Privilege::ModifySchema),
  910. };
  911. }
  912. - (RLMObjectPrivileges)privilegesForObject:(RLMObject *)object {
  913. RLMVerifyAttached(object);
  914. auto p = _realm->get_privileges(object->_row);
  915. return {
  916. .read = hasPrivilege(p, Privilege::Read),
  917. .update = hasPrivilege(p, Privilege::Update),
  918. .del = hasPrivilege(p, Privilege::Delete),
  919. .setPermissions = hasPrivilege(p, Privilege::Delete),
  920. };
  921. }
  922. - (RLMClassPrivileges)privilegesForClass:(Class)cls {
  923. if (![cls respondsToSelector:@selector(_realmObjectName)]) {
  924. @throw RLMException(@"Cannot get privileges for non-RLMObject class %@", cls);
  925. }
  926. return [self privilegesForClassNamed:[cls _realmObjectName] ?: [cls className]];
  927. }
  928. - (RLMClassPrivileges)privilegesForClassNamed:(NSString *)className {
  929. auto p = _realm->get_privileges(className.UTF8String);
  930. return {
  931. .read = hasPrivilege(p, Privilege::Read),
  932. .update = hasPrivilege(p, Privilege::Update),
  933. .setPermissions = hasPrivilege(p, Privilege::SetPermissions),
  934. .subscribe = hasPrivilege(p, Privilege::Query),
  935. .create = hasPrivilege(p, Privilege::Create),
  936. };
  937. }
  938. #endif
  939. - (void)registerEnumerator:(RLMFastEnumerator *)enumerator {
  940. if (!_collectionEnumerators) {
  941. _collectionEnumerators = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory];
  942. }
  943. [_collectionEnumerators addObject:enumerator];
  944. }
  945. - (void)unregisterEnumerator:(RLMFastEnumerator *)enumerator {
  946. [_collectionEnumerators removeObject:enumerator];
  947. }
  948. - (void)detachAllEnumerators {
  949. for (RLMFastEnumerator *enumerator in _collectionEnumerators) {
  950. [enumerator detach];
  951. }
  952. _collectionEnumerators = nil;
  953. }
  954. @end