RLMRealm.mm 34 KB

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