RLMRealm.mm 36 KB

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