1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033 |
- ////////////////////////////////////////////////////////////////////////////
- //
- // Copyright 2014 Realm Inc.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- //
- ////////////////////////////////////////////////////////////////////////////
- #import "RLMRealm_Private.hpp"
- #import "RLMAnalytics.hpp"
- #import "RLMArray_Private.hpp"
- #import "RLMMigration_Private.h"
- #import "RLMObject_Private.h"
- #import "RLMObject_Private.hpp"
- #import "RLMObjectSchema_Private.hpp"
- #import "RLMObjectStore.h"
- #import "RLMObservation.hpp"
- #import "RLMProperty.h"
- #import "RLMProperty_Private.h"
- #import "RLMQueryUtil.hpp"
- #import "RLMRealmConfiguration_Private.hpp"
- #import "RLMRealmUtil.hpp"
- #import "RLMSchema_Private.hpp"
- #import "RLMThreadSafeReference_Private.hpp"
- #import "RLMUpdateChecker.hpp"
- #import "RLMUtil.hpp"
- #include "impl/realm_coordinator.hpp"
- #include "object_store.hpp"
- #include "schema.hpp"
- #include "shared_realm.hpp"
- #include "thread_safe_reference.hpp"
- #include <realm/disable_sync_to_disk.hpp>
- #include <realm/util/scope_exit.hpp>
- #include <realm/version.hpp>
- #if REALM_ENABLE_SYNC
- #import "RLMSyncManager_Private.h"
- #import "RLMSyncSession_Private.hpp"
- #import "RLMSyncUtil_Private.hpp"
- #import "sync/async_open_task.hpp"
- #import "sync/sync_session.hpp"
- #endif
- using namespace realm;
- using util::File;
- @interface RLMRealmNotificationToken : RLMNotificationToken
- @property (nonatomic, strong) RLMRealm *realm;
- @property (nonatomic, copy) RLMNotificationBlock block;
- @end
- @interface RLMRealm ()
- @property (nonatomic, strong) NSHashTable<RLMRealmNotificationToken *> *notificationHandlers;
- - (void)sendNotifications:(RLMNotification)notification;
- @end
- void RLMDisableSyncToDisk() {
- realm::disable_sync_to_disk();
- }
- static void RLMAddSkipBackupAttributeToItemAtPath(std::string const& path) {
- [[NSURL fileURLWithPath:@(path.c_str())] setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
- }
- @implementation RLMRealmNotificationToken
- - (void)invalidate {
- [_realm verifyThread];
- [_realm.notificationHandlers removeObject:self];
- _realm = nil;
- _block = nil;
- }
- - (void)suppressNextNotification {
- // Temporarily replace the block with one which restores the old block
- // rather than producing a notification.
- // This briefly creates a retain cycle but it's fine because the block will
- // be synchronously called shortly after this method is called. Unlike with
- // collection notifications, this does not have to go through the object
- // store or do fancy things to handle transaction coalescing because it's
- // called synchronously by the obj-c code and not by the object store.
- auto notificationBlock = _block;
- _block = ^(RLMNotification, RLMRealm *) {
- _block = notificationBlock;
- };
- }
- - (void)dealloc {
- if (_realm || _block) {
- NSLog(@"RLMNotificationToken released without unregistering a notification. You must hold "
- @"on to the RLMNotificationToken returned from addNotificationBlock and call "
- @"-[RLMNotificationToken invalidate] when you no longer wish to receive RLMRealm notifications.");
- }
- }
- @end
- #if !REALM_ENABLE_SYNC
- @interface RLMAsyncOpenTask : NSObject
- @end
- @implementation RLMAsyncOpenTask
- @end
- #endif
- static bool shouldForciblyDisableEncryption() {
- static bool disableEncryption = getenv("REALM_DISABLE_ENCRYPTION");
- return disableEncryption;
- }
- NSData *RLMRealmValidatedEncryptionKey(NSData *key) {
- if (shouldForciblyDisableEncryption()) {
- return nil;
- }
- if (key && key.length != 64) {
- @throw RLMException(@"Encryption key must be exactly 64 bytes long");
- }
- return key;
- }
- @implementation RLMRealm {
- NSHashTable<RLMFastEnumerator *> *_collectionEnumerators;
- bool _sendingNotifications;
- }
- + (BOOL)isCoreDebug {
- return realm::Version::has_feature(realm::feature_Debug);
- }
- + (void)initialize {
- static bool initialized;
- if (initialized) {
- return;
- }
- initialized = true;
- RLMCheckForUpdates();
- RLMSendAnalytics();
- }
- - (instancetype)initPrivate {
- self = [super init];
- return self;
- }
- - (BOOL)isEmpty {
- return realm::ObjectStore::is_empty(self.group);
- }
- - (void)verifyThread {
- try {
- _realm->verify_thread();
- }
- catch (std::exception const& e) {
- @throw RLMException(e);
- }
- }
- - (BOOL)inWriteTransaction {
- return _realm->is_in_transaction();
- }
- - (realm::Group &)group {
- return _realm->read_group();
- }
- - (BOOL)autorefresh {
- return _realm->auto_refresh();
- }
- - (void)setAutorefresh:(BOOL)autorefresh {
- _realm->set_auto_refresh(autorefresh);
- }
- + (instancetype)defaultRealm {
- return [RLMRealm realmWithConfiguration:[RLMRealmConfiguration rawDefaultConfiguration] error:nil];
- }
- + (instancetype)realmWithURL:(NSURL *)fileURL {
- RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration];
- configuration.fileURL = fileURL;
- return [RLMRealm realmWithConfiguration:configuration error:nil];
- }
- // The server doesn't send us the subscriptions for permission types until the
- // first subscription is created. This is fine for synchronous opens (if we're
- // creating a new Realm we create the permission objects ourselves), but it
- // causes issues for asyncOpen because it means that when our download completes
- // we don't actually have the full Realm state yet.
- static void waitForPartialSyncSubscriptions(Realm::Config const& config) {
- #if REALM_ENABLE_SYNC
- auto realm = Realm::get_shared_realm(config);
- auto table = ObjectStore::table_for_object_type(realm->read_group(), "__ResultSets");
- realm->begin_transaction();
- size_t row = realm::sync::create_object(realm->read_group(), *table);
- // Set expires_at to time 0 so that this object will be cleaned up the first
- // time the user creates a subscription
- size_t expires_at_col = table->get_column_index("expires_at");
- if (expires_at_col == npos) {
- expires_at_col = table->add_column(type_Timestamp, "expires_at", true);
- }
- table->set_timestamp(expires_at_col, row, Timestamp(0, 0));
- realm->commit_transaction();
- NotificationToken token;
- Results results(realm, *table);
- CFRunLoopRef runLoop = CFRunLoopGetCurrent();
- CFRunLoopPerformBlock(runLoop, kCFRunLoopDefaultMode, [&]() mutable {
- token = results.add_notification_callback([&](CollectionChangeSet const&, std::exception_ptr) mutable {
- if (table->size() > 1) {
- token = {};
- CFRunLoopStop(runLoop);
- }
- });
- });
- CFRunLoopRun();
- #else
- static_cast<void>(config);
- #endif
- }
- static dispatch_queue_t s_async_open_queue = dispatch_queue_create("io.realm.asyncOpenDispatchQueue",
- DISPATCH_QUEUE_CONCURRENT);
- void RLMSetAsyncOpenQueue(dispatch_queue_t queue) {
- s_async_open_queue = queue;
- }
- + (RLMAsyncOpenTask *)asyncOpenWithConfiguration:(RLMRealmConfiguration *)configuration
- callbackQueue:(dispatch_queue_t)callbackQueue
- callback:(RLMAsyncOpenRealmCallback)callback {
- auto openCompletion = [=](ThreadSafeReference<Realm> ref, std::exception_ptr err) {
- @autoreleasepool {
- if (err) {
- try {
- std::rethrow_exception(err);
- }
- catch (...) {
- NSError *error;
- RLMRealmTranslateException(&error);
- dispatch_async(callbackQueue, ^{
- callback(nil, error);
- });
- }
- return;
- }
- auto complete = ^{
- dispatch_async(callbackQueue, ^{
- @autoreleasepool {
- NSError *error;
- RLMRealm *localRealm = [RLMRealm realmWithConfiguration:configuration error:&error];
- callback(localRealm, error);
- }
- });
- };
- auto realm = Realm::get_shared_realm(std::move(ref));
- bool needsSubscriptions = realm->is_partial() && ObjectStore::table_for_object_type(realm->read_group(), "__ResultSets")->size() == 0;
- if (needsSubscriptions) {
- // We need to dispatch back to the work queue to wait for the
- // subscriptions as we're currently running on the sync worker
- // thread and blocking it to wait for subscriptions means no syncing
- dispatch_async(s_async_open_queue, ^{
- @autoreleasepool {
- waitForPartialSyncSubscriptions(realm->config());
- complete();
- }
- });
- }
- else {
- complete();
- }
- }
- };
- RLMAsyncOpenTask *ret = [RLMAsyncOpenTask new];
- dispatch_async(s_async_open_queue, ^{
- @autoreleasepool {
- Realm::Config& config = configuration.config;
- if (config.sync_config) {
- #if REALM_ENABLE_SYNC
- auto task = realm::Realm::get_synchronized_realm(config);
- ret.task = task;
- task->start(openCompletion);
- #else
- @throw RLMException(@"Realm was not built with sync enabled");
- #endif
- }
- else {
- try {
- openCompletion(realm::_impl::RealmCoordinator::get_coordinator(config)->get_unbound_realm(), nullptr);
- }
- catch (...) {
- openCompletion({}, std::current_exception());
- }
- }
- }
- });
- return ret;
- }
- // ARC tries to eliminate calls to autorelease when the value is then immediately
- // returned, but this results in significantly different semantics between debug
- // and release builds for RLMRealm, so force it to always autorelease.
- static id RLMAutorelease(__unsafe_unretained id value) {
- // +1 __bridge_retained, -1 CFAutorelease
- return value ? (__bridge id)CFAutorelease((__bridge_retained CFTypeRef)value) : nil;
- }
- + (instancetype)realmWithSharedRealm:(SharedRealm)sharedRealm schema:(RLMSchema *)schema {
- RLMRealm *realm = [[RLMRealm alloc] initPrivate];
- realm->_realm = sharedRealm;
- realm->_dynamic = YES;
- realm->_schema = schema;
- realm->_info = RLMSchemaInfo(realm);
- return RLMAutorelease(realm);
- }
- REALM_NOINLINE void RLMRealmTranslateException(NSError **error) {
- try {
- throw;
- }
- catch (RealmFileException const& ex) {
- switch (ex.kind()) {
- case RealmFileException::Kind::PermissionDenied:
- RLMSetErrorOrThrow(RLMMakeError(RLMErrorFilePermissionDenied, ex), error);
- break;
- case RealmFileException::Kind::IncompatibleLockFile: {
- NSString *err = @"Realm file is currently open in another process "
- "which cannot share access with this process. All "
- "processes sharing a single file must be the same "
- "architecture. For sharing files between the Realm "
- "Browser and an iOS simulator, this means that you "
- "must use a 64-bit simulator.";
- RLMSetErrorOrThrow(RLMMakeError(RLMErrorIncompatibleLockFile,
- File::PermissionDenied(err.UTF8String, ex.path())), error);
- break;
- }
- case RealmFileException::Kind::NotFound:
- RLMSetErrorOrThrow(RLMMakeError(RLMErrorFileNotFound, ex), error);
- break;
- case RealmFileException::Kind::Exists:
- RLMSetErrorOrThrow(RLMMakeError(RLMErrorFileExists, ex), error);
- break;
- case RealmFileException::Kind::BadHistoryError: {
- NSString *err = @"Realm file's history format is incompatible with the "
- "settings in the configuration object being used to open "
- "the Realm. Note that Realms configured for sync cannot be "
- "opened as non-synced Realms, and vice versa. Otherwise, the "
- "file may be corrupt.";
- RLMSetErrorOrThrow(RLMMakeError(RLMErrorFileAccess,
- File::AccessError(err.UTF8String, ex.path())), error);
- break;
- }
- case RealmFileException::Kind::AccessError:
- RLMSetErrorOrThrow(RLMMakeError(RLMErrorFileAccess, ex), error);
- break;
- case RealmFileException::Kind::FormatUpgradeRequired:
- RLMSetErrorOrThrow(RLMMakeError(RLMErrorFileFormatUpgradeRequired, ex), error);
- break;
- default:
- RLMSetErrorOrThrow(RLMMakeError(RLMErrorFail, ex), error);
- break;
- }
- }
- catch (AddressSpaceExhausted const &ex) {
- RLMSetErrorOrThrow(RLMMakeError(RLMErrorAddressSpaceExhausted, ex), error);
- }
- catch (SchemaMismatchException const& ex) {
- RLMSetErrorOrThrow(RLMMakeError(RLMErrorSchemaMismatch, ex), error);
- }
- catch (std::system_error const& ex) {
- RLMSetErrorOrThrow(RLMMakeError(ex), error);
- }
- catch (const std::exception &exp) {
- RLMSetErrorOrThrow(RLMMakeError(RLMErrorFail, exp), error);
- }
- }
- REALM_NOINLINE static void translateSharedGroupOpenException(RLMRealmConfiguration *originalConfiguration, NSError **error) {
- try {
- throw;
- }
- catch (RealmFileException const& ex) {
- switch (ex.kind()) {
- case RealmFileException::Kind::IncompatibleSyncedRealm: {
- RLMRealmConfiguration *configuration = [originalConfiguration copy];
- configuration.fileURL = [NSURL fileURLWithPath:@(ex.path().data())];
- configuration.readOnly = YES;
- NSError *intermediateError = RLMMakeError(RLMErrorIncompatibleSyncedFile, ex);
- NSMutableDictionary *userInfo = [intermediateError.userInfo mutableCopy];
- userInfo[RLMBackupRealmConfigurationErrorKey] = configuration;
- NSError *finalError = [NSError errorWithDomain:intermediateError.domain code:intermediateError.code
- userInfo:userInfo];
- RLMSetErrorOrThrow(finalError, error);
- break;
- }
- default:
- RLMRealmTranslateException(error);
- break;
- }
- }
- catch (...) {
- RLMRealmTranslateException(error);
- }
- }
- + (instancetype)realmWithConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error {
- bool dynamic = configuration.dynamic;
- bool cache = configuration.cache;
- bool readOnly = configuration.readOnly;
- {
- Realm::Config& config = configuration.config;
- // try to reuse existing realm first
- if (cache || dynamic) {
- if (RLMRealm *realm = RLMGetThreadLocalCachedRealmForPath(config.path)) {
- auto const& old_config = realm->_realm->config();
- if (old_config.immutable() != config.immutable()
- || old_config.read_only_alternative() != config.read_only_alternative()) {
- @throw RLMException(@"Realm at path '%s' already opened with different read permissions", config.path.c_str());
- }
- if (old_config.in_memory != config.in_memory) {
- @throw RLMException(@"Realm at path '%s' already opened with different inMemory settings", config.path.c_str());
- }
- if (realm->_dynamic != dynamic) {
- @throw RLMException(@"Realm at path '%s' already opened with different dynamic settings", config.path.c_str());
- }
- if (old_config.encryption_key != config.encryption_key) {
- @throw RLMException(@"Realm at path '%s' already opened with different encryption key", config.path.c_str());
- }
- return RLMAutorelease(realm);
- }
- }
- }
- configuration = [configuration copy];
- Realm::Config& config = configuration.config;
- RLMRealm *realm = [[self alloc] initPrivate];
- realm->_dynamic = dynamic;
- // protects the realm cache and accessors cache
- static std::mutex& initLock = *new std::mutex();
- std::lock_guard<std::mutex> lock(initLock);
- try {
- realm->_realm = Realm::get_shared_realm(config);
- }
- catch (...) {
- translateSharedGroupOpenException(configuration, error);
- return nil;
- }
- // if we have a cached realm on another thread we can skip a few steps and
- // just grab its schema
- @autoreleasepool {
- // ensure that cachedRealm doesn't end up in this thread's autorelease pool
- if (auto cachedRealm = RLMGetAnyCachedRealmForPath(config.path)) {
- realm->_realm->set_schema_subset(cachedRealm->_realm->schema());
- realm->_schema = cachedRealm.schema;
- realm->_info = cachedRealm->_info.clone(cachedRealm->_realm->schema(), realm);
- }
- }
- if (realm->_schema) { }
- else if (dynamic) {
- realm->_schema = [RLMSchema dynamicSchemaFromObjectStoreSchema:realm->_realm->schema()];
- realm->_info = RLMSchemaInfo(realm);
- }
- else {
- // set/align schema or perform migration if needed
- RLMSchema *schema = configuration.customSchema ?: RLMSchema.sharedSchema;
- Realm::MigrationFunction migrationFunction;
- auto migrationBlock = configuration.migrationBlock;
- if (migrationBlock && configuration.schemaVersion > 0) {
- migrationFunction = [=](SharedRealm old_realm, SharedRealm realm, Schema& mutableSchema) {
- RLMSchema *oldSchema = [RLMSchema dynamicSchemaFromObjectStoreSchema:old_realm->schema()];
- RLMRealm *oldRealm = [RLMRealm realmWithSharedRealm:old_realm schema:oldSchema];
- // The destination RLMRealm can't just use the schema from the
- // SharedRealm because it doesn't have information about whether or
- // not a class was defined in Swift, which effects how new objects
- // are created
- RLMRealm *newRealm = [RLMRealm realmWithSharedRealm:realm schema:schema.copy];
- [[[RLMMigration alloc] initWithRealm:newRealm oldRealm:oldRealm schema:mutableSchema] execute:migrationBlock];
- oldRealm->_realm = nullptr;
- newRealm->_realm = nullptr;
- };
- }
- try {
- realm->_realm->update_schema(schema.objectStoreCopy, config.schema_version,
- std::move(migrationFunction));
- }
- catch (...) {
- RLMRealmTranslateException(error);
- return nil;
- }
- realm->_schema = schema;
- realm->_info = RLMSchemaInfo(realm);
- RLMRealmCreateAccessors(realm.schema);
- if (!readOnly) {
- // initializing the schema started a read transaction, so end it
- [realm invalidate];
- RLMAddSkipBackupAttributeToItemAtPath(config.path + ".management");
- RLMAddSkipBackupAttributeToItemAtPath(config.path + ".lock");
- RLMAddSkipBackupAttributeToItemAtPath(config.path + ".note");
- }
- }
- if (cache) {
- RLMCacheRealm(config.path, realm);
- }
- if (!readOnly) {
- realm->_realm->m_binding_context = RLMCreateBindingContext(realm);
- realm->_realm->m_binding_context->realm = realm->_realm;
- }
- return RLMAutorelease(realm);
- }
- + (instancetype)uncachedSchemalessRealmWithConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error {
- RLMRealm *realm = [[RLMRealm alloc] initPrivate];
- try {
- realm->_realm = Realm::get_shared_realm(configuration.config);
- }
- catch (...) {
- translateSharedGroupOpenException(configuration, error);
- return nil;
- }
- return realm;
- }
- + (void)resetRealmState {
- RLMClearRealmCache();
- realm::_impl::RealmCoordinator::clear_cache();
- [RLMRealmConfiguration resetRealmConfigurationState];
- }
- - (void)verifyNotificationsAreSupported:(bool)isCollection {
- [self verifyThread];
- if (_realm->config().immutable()) {
- @throw RLMException(@"Read-only Realms do not change and do not have change notifications");
- }
- if (!_realm->can_deliver_notifications()) {
- @throw RLMException(@"Can only add notification blocks from within runloops.");
- }
- if (isCollection && _realm->is_in_transaction()) {
- @throw RLMException(@"Cannot register notification blocks from within write transactions.");
- }
- }
- - (RLMNotificationToken *)addNotificationBlock:(RLMNotificationBlock)block {
- if (!block) {
- @throw RLMException(@"The notification block should not be nil");
- }
- [self verifyNotificationsAreSupported:false];
- _realm->read_group();
- if (!_notificationHandlers) {
- _notificationHandlers = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory];
- }
- RLMRealmNotificationToken *token = [[RLMRealmNotificationToken alloc] init];
- token.realm = self;
- token.block = block;
- [_notificationHandlers addObject:token];
- return token;
- }
- - (void)sendNotifications:(RLMNotification)notification {
- NSAssert(!_realm->config().immutable(), @"Read-only realms do not have notifications");
- if (_sendingNotifications) {
- return;
- }
- NSUInteger count = _notificationHandlers.count;
- if (count == 0) {
- return;
- }
- _sendingNotifications = true;
- auto cleanup = realm::util::make_scope_exit([&]() noexcept {
- _sendingNotifications = false;
- });
- // call this realm's notification blocks
- if (count == 1) {
- if (auto block = [_notificationHandlers.anyObject block]) {
- block(notification, self);
- }
- }
- else {
- for (RLMRealmNotificationToken *token in _notificationHandlers.allObjects) {
- if (auto block = token.block) {
- block(notification, self);
- }
- }
- }
- }
- - (RLMRealmConfiguration *)configuration {
- RLMRealmConfiguration *configuration = [[RLMRealmConfiguration alloc] init];
- configuration.config = _realm->config();
- configuration.dynamic = _dynamic;
- configuration.customSchema = _schema;
- return configuration;
- }
- - (void)beginWriteTransaction {
- try {
- _realm->begin_transaction();
- }
- catch (std::exception &ex) {
- @throw RLMException(ex);
- }
- }
- - (void)commitWriteTransaction {
- [self commitWriteTransaction:nil];
- }
- - (BOOL)commitWriteTransaction:(NSError **)error {
- return [self commitWriteTransactionWithoutNotifying:@[] error:error];
- }
- - (BOOL)commitWriteTransactionWithoutNotifying:(NSArray<RLMNotificationToken *> *)tokens error:(NSError **)error {
- for (RLMNotificationToken *token in tokens) {
- if (token.realm != self) {
- @throw RLMException(@"Incorrect Realm: only notifications for the Realm being modified can be skipped.");
- }
- [token suppressNextNotification];
- }
- try {
- _realm->commit_transaction();
- return YES;
- }
- catch (...) {
- RLMRealmTranslateException(error);
- return NO;
- }
- }
- - (void)transactionWithBlock:(__attribute__((noescape)) void(^)(void))block {
- [self transactionWithBlock:block error:nil];
- }
- - (BOOL)transactionWithBlock:(__attribute__((noescape)) void(^)(void))block error:(NSError **)outError {
- return [self transactionWithoutNotifying:@[] block:block error:outError];
- }
- - (void)transactionWithoutNotifying:(NSArray<RLMNotificationToken *> *)tokens block:(__attribute__((noescape)) void(^)(void))block {
- [self transactionWithoutNotifying:tokens block:block error:nil];
- }
- - (BOOL)transactionWithoutNotifying:(NSArray<RLMNotificationToken *> *)tokens block:(__attribute__((noescape)) void(^)(void))block error:(NSError **)error {
- [self beginWriteTransaction];
- block();
- if (_realm->is_in_transaction()) {
- return [self commitWriteTransactionWithoutNotifying:tokens error:error];
- }
- return YES;
- }
- - (void)cancelWriteTransaction {
- try {
- _realm->cancel_transaction();
- }
- catch (std::exception &ex) {
- @throw RLMException(ex);
- }
- }
- - (void)invalidate {
- if (_realm->is_in_transaction()) {
- NSLog(@"WARNING: An RLMRealm instance was invalidated during a write "
- "transaction and all pending changes have been rolled back.");
- }
- [self detachAllEnumerators];
- for (auto& objectInfo : _info) {
- for (RLMObservationInfo *info : objectInfo.second.observedObjects) {
- info->willChange(RLMInvalidatedKey);
- }
- }
- _realm->invalidate();
- for (auto& objectInfo : _info) {
- for (RLMObservationInfo *info : objectInfo.second.observedObjects) {
- info->didChange(RLMInvalidatedKey);
- }
- objectInfo.second.releaseTable();
- }
- }
- - (nullable id)resolveThreadSafeReference:(RLMThreadSafeReference *)reference {
- return [reference resolveReferenceInRealm:self];
- }
- /**
- Replaces all string columns in this Realm with a string enumeration column and compacts the
- database file.
- Cannot be called from a write transaction.
- Compaction will not occur if other `RLMRealm` instances exist.
- While compaction is in progress, attempts by other threads or processes to open the database will
- wait.
- Be warned that resource requirements for compaction is proportional to the amount of live data in
- the database.
- Compaction works by writing the database contents to a temporary database file and then replacing
- the database with the temporary one. The name of the temporary file is formed by appending
- `.tmp_compaction_space` to the name of the database.
- @return YES if the compaction succeeded.
- */
- - (BOOL)compact {
- // compact() automatically ends the read transaction, but we need to clean
- // up cached state and send invalidated notifications when that happens, so
- // explicitly end it first unless we're in a write transaction (in which
- // case compact() will throw an exception)
- if (!_realm->is_in_transaction()) {
- [self invalidate];
- }
- try {
- return _realm->compact();
- }
- catch (std::exception const& ex) {
- @throw RLMException(ex);
- }
- }
- - (void)dealloc {
- if (_realm) {
- if (_realm->is_in_transaction()) {
- [self cancelWriteTransaction];
- NSLog(@"WARNING: An RLMRealm instance was deallocated during a write transaction and all "
- "pending changes have been rolled back. Make sure to retain a reference to the "
- "RLMRealm for the duration of the write transaction.");
- }
- }
- }
- - (BOOL)refresh {
- return _realm->refresh();
- }
- - (void)addObject:(__unsafe_unretained RLMObject *const)object {
- RLMAddObjectToRealm(object, self, RLMUpdatePolicyError);
- }
- - (void)addObjects:(id<NSFastEnumeration>)objects {
- for (RLMObject *obj in objects) {
- if (![obj isKindOfClass:RLMObjectBase.class]) {
- @throw RLMException(@"Cannot insert objects of type %@ with addObjects:. Only RLMObjects are supported.",
- NSStringFromClass(obj.class));
- }
- [self addObject:obj];
- }
- }
- - (void)addOrUpdateObject:(RLMObject *)object {
- // verify primary key
- if (!object.objectSchema.primaryKeyProperty) {
- @throw RLMException(@"'%@' does not have a primary key and can not be updated", object.objectSchema.className);
- }
- RLMAddObjectToRealm(object, self, RLMUpdatePolicyUpdateAll);
- }
- - (void)addOrUpdateObjects:(id<NSFastEnumeration>)objects {
- for (RLMObject *obj in objects) {
- if (![obj isKindOfClass:RLMObjectBase.class]) {
- @throw RLMException(@"Cannot add or update objects of type %@ with addOrUpdateObjects:. Only RLMObjects are"
- " supported.",
- NSStringFromClass(obj.class));
- }
- [self addOrUpdateObject:obj];
- }
- }
- - (void)deleteObject:(RLMObject *)object {
- RLMDeleteObjectFromRealm(object, self);
- }
- - (void)deleteObjects:(id<NSFastEnumeration>)objects {
- id idObjects = objects;
- if ([idObjects respondsToSelector:@selector(realm)]
- && [idObjects respondsToSelector:@selector(deleteObjectsFromRealm)]) {
- if (self != (RLMRealm *)[idObjects realm]) {
- @throw RLMException(@"Can only delete objects from the Realm they belong to.");
- }
- [idObjects deleteObjectsFromRealm];
- return;
- }
- if (auto array = RLMDynamicCast<RLMArray>(objects)) {
- if (array.type != RLMPropertyTypeObject) {
- @throw RLMException(@"Cannot delete objects from RLMArray<%@>: only RLMObjects can be deleted.",
- RLMTypeToString(array.type));
- }
- }
- for (RLMObject *obj in objects) {
- if (![obj isKindOfClass:RLMObjectBase.class]) {
- @throw RLMException(@"Cannot delete objects of type %@ with deleteObjects:. Only RLMObjects can be deleted.",
- NSStringFromClass(obj.class));
- }
- RLMDeleteObjectFromRealm(obj, self);
- }
- }
- - (void)deleteAllObjects {
- RLMDeleteAllObjectsFromRealm(self);
- }
- - (RLMResults *)allObjects:(NSString *)objectClassName {
- return RLMGetObjects(self, objectClassName, nil);
- }
- - (RLMResults *)objects:(NSString *)objectClassName where:(NSString *)predicateFormat, ... {
- va_list args;
- va_start(args, predicateFormat);
- RLMResults *results = [self objects:objectClassName where:predicateFormat args:args];
- va_end(args);
- return results;
- }
- - (RLMResults *)objects:(NSString *)objectClassName where:(NSString *)predicateFormat args:(va_list)args {
- return [self objects:objectClassName withPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]];
- }
- - (RLMResults *)objects:(NSString *)objectClassName withPredicate:(NSPredicate *)predicate {
- return RLMGetObjects(self, objectClassName, predicate);
- }
- - (RLMObject *)objectWithClassName:(NSString *)className forPrimaryKey:(id)primaryKey {
- return RLMGetObject(self, className, primaryKey);
- }
- + (uint64_t)schemaVersionAtURL:(NSURL *)fileURL encryptionKey:(NSData *)key error:(NSError **)error {
- RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init];
- try {
- config.fileURL = fileURL;
- config.encryptionKey = RLMRealmValidatedEncryptionKey(key);
- uint64_t version = Realm::get_schema_version(config.config);
- if (version == realm::ObjectStore::NotVersioned) {
- RLMSetErrorOrThrow([NSError errorWithDomain:RLMErrorDomain code:RLMErrorFail userInfo:@{NSLocalizedDescriptionKey:@"Cannot open an uninitialized realm in read-only mode"}], error);
- }
- return version;
- }
- catch (...) {
- translateSharedGroupOpenException(config, error);
- return RLMNotVersioned;
- }
- }
- + (BOOL)performMigrationForConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error {
- if (RLMGetAnyCachedRealmForPath(configuration.config.path)) {
- @throw RLMException(@"Cannot migrate Realms that are already open.");
- }
- NSError *localError; // Prevents autorelease
- BOOL success;
- @autoreleasepool {
- success = [RLMRealm realmWithConfiguration:configuration error:&localError] != nil;
- }
- if (!success && error) {
- *error = localError; // Must set outside pool otherwise will free anyway
- }
- return success;
- }
- - (RLMObject *)createObject:(NSString *)className withValue:(id)value {
- return (RLMObject *)RLMCreateObjectInRealmWithValue(self, className, value, RLMUpdatePolicyError);
- }
- - (BOOL)writeCopyToURL:(NSURL *)fileURL encryptionKey:(NSData *)key error:(NSError **)error {
- key = RLMRealmValidatedEncryptionKey(key);
- NSString *path = fileURL.path;
- try {
- _realm->write_copy(path.UTF8String, {static_cast<const char *>(key.bytes), key.length});
- return YES;
- }
- catch (...) {
- __autoreleasing NSError *dummyError;
- if (!error) {
- error = &dummyError;
- }
- RLMRealmTranslateException(error);
- return NO;
- }
- return NO;
- }
- + (BOOL)fileExistsForConfiguration:(RLMRealmConfiguration *)config {
- return [NSFileManager.defaultManager fileExistsAtPath:config.pathOnDisk];
- }
- + (BOOL)deleteFilesForConfiguration:(RLMRealmConfiguration *)config error:(NSError **)error {
- auto& path = config.config.path;
- bool anyDeleted = false;
- NSError *localError;
- bool didCall = SharedGroup::call_with_lock(path, [&](auto const& path) {
- NSURL *url = [NSURL fileURLWithPath:@(path.c_str())];
- NSFileManager *fm = NSFileManager.defaultManager;
- anyDeleted = [fm removeItemAtURL:url error:&localError];
- if (localError && localError.code != NSFileNoSuchFileError) {
- return;
- }
- [fm removeItemAtURL:[url URLByAppendingPathExtension:@"management"] error:&localError];
- if (localError && localError.code != NSFileNoSuchFileError) {
- return;
- }
- [fm removeItemAtURL:[url URLByAppendingPathExtension:@"note"] error:&localError];
- });
- if (error && localError && localError.code != NSFileNoSuchFileError) {
- *error = localError;
- }
- else if (!didCall) {
- if (error) {
- NSString *msg = [NSString stringWithFormat:@"Realm file at path %s cannot be deleted because it is currently opened.", path.c_str()];
- *error = [NSError errorWithDomain:RLMErrorDomain
- code:RLMErrorAlreadyOpen
- userInfo:@{NSLocalizedDescriptionKey: msg}];
- }
- }
- return anyDeleted;
- }
- #if REALM_ENABLE_SYNC
- using Privilege = realm::ComputedPrivileges;
- static bool hasPrivilege(realm::ComputedPrivileges actual, realm::ComputedPrivileges expected) {
- return (static_cast<int>(actual) & static_cast<int>(expected)) == static_cast<int>(expected);
- }
- - (RLMRealmPrivileges)privilegesForRealm {
- auto p = _realm->get_privileges();
- return {
- .read = hasPrivilege(p, Privilege::Read),
- .update = hasPrivilege(p, Privilege::Update),
- .setPermissions = hasPrivilege(p, Privilege::SetPermissions),
- .modifySchema = hasPrivilege(p, Privilege::ModifySchema),
- };
- }
- - (RLMObjectPrivileges)privilegesForObject:(RLMObject *)object {
- RLMVerifyAttached(object);
- auto p = _realm->get_privileges(object->_row);
- return {
- .read = hasPrivilege(p, Privilege::Read),
- .update = hasPrivilege(p, Privilege::Update),
- .del = hasPrivilege(p, Privilege::Delete),
- .setPermissions = hasPrivilege(p, Privilege::Delete),
- };
- }
- - (RLMClassPrivileges)privilegesForClass:(Class)cls {
- if (![cls respondsToSelector:@selector(_realmObjectName)]) {
- @throw RLMException(@"Cannot get privileges for non-RLMObject class %@", cls);
- }
- return [self privilegesForClassNamed:[cls _realmObjectName] ?: [cls className]];
- }
- - (RLMClassPrivileges)privilegesForClassNamed:(NSString *)className {
- auto p = _realm->get_privileges(className.UTF8String);
- return {
- .read = hasPrivilege(p, Privilege::Read),
- .update = hasPrivilege(p, Privilege::Update),
- .setPermissions = hasPrivilege(p, Privilege::SetPermissions),
- .subscribe = hasPrivilege(p, Privilege::Query),
- .create = hasPrivilege(p, Privilege::Create),
- };
- }
- #endif
- - (void)registerEnumerator:(RLMFastEnumerator *)enumerator {
- if (!_collectionEnumerators) {
- _collectionEnumerators = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory];
- }
- [_collectionEnumerators addObject:enumerator];
- }
- - (void)unregisterEnumerator:(RLMFastEnumerator *)enumerator {
- [_collectionEnumerators removeObject:enumerator];
- }
- - (void)detachAllEnumerators {
- for (RLMFastEnumerator *enumerator in _collectionEnumerators) {
- [enumerator detach];
- }
- _collectionEnumerators = nil;
- }
- @end
|