RealmConfiguration.swift 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. ////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright 2015 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 Foundation
  19. import Realm
  20. import Realm.Private
  21. extension Realm {
  22. /**
  23. A `Configuration` instance describes the different options used to create an instance of a Realm.
  24. `Configuration` instances are just plain Swift structs. Unlike `Realm`s and `Object`s, they can be freely shared
  25. between threads as long as you do not mutate them.
  26. Creating configuration values for class subsets (by setting the `objectClasses` property) can be expensive. Because
  27. of this, you will normally want to cache and reuse a single configuration value for each distinct configuration
  28. rather than creating a new value each time you open a Realm.
  29. */
  30. @frozen public struct Configuration {
  31. // MARK: Default Configuration
  32. /**
  33. The default `Configuration` used to create Realms when no configuration is explicitly specified (i.e.
  34. `Realm()`)
  35. */
  36. public static var defaultConfiguration: Configuration {
  37. get {
  38. return fromRLMRealmConfiguration(RLMRealmConfiguration.default())
  39. }
  40. set {
  41. RLMRealmConfiguration.setDefault(newValue.rlmConfiguration)
  42. }
  43. }
  44. // MARK: Initialization
  45. /**
  46. Creates a `Configuration` which can be used to create new `Realm` instances.
  47. - note: The `fileURL`, `inMemoryIdentifier`, and `syncConfiguration` parameters are mutually exclusive. Only
  48. set one of them, or none if you wish to use the default file URL.
  49. - parameter fileURL: The local URL to the Realm file.
  50. - parameter inMemoryIdentifier: A string used to identify a particular in-memory Realm.
  51. - parameter syncConfiguration: For Realms intended to sync with MongoDB Realm, a sync configuration.
  52. - parameter encryptionKey: An optional 64-byte key to use to encrypt the data.
  53. - parameter readOnly: Whether the Realm is read-only (must be true for read-only files).
  54. - parameter schemaVersion: The current schema version.
  55. - parameter migrationBlock: The block which migrates the Realm to the current version.
  56. - parameter deleteRealmIfMigrationNeeded: If `true`, recreate the Realm file with the provided
  57. schema if a migration is required.
  58. - parameter shouldCompactOnLaunch: A block called when opening a Realm for the first time during the
  59. life of a process to determine if it should be compacted before being
  60. returned to the user. It is passed the total file size (data + free space)
  61. and the total bytes used by data in the file.
  62. Return `true ` to indicate that an attempt to compact the file should be made.
  63. The compaction will be skipped if another process is accessing it.
  64. - parameter objectTypes: The subset of `Object` and `EmbeddedObject` subclasses persisted in the Realm.
  65. */
  66. public init(fileURL: URL? = URL(fileURLWithPath: RLMRealmPathForFile("default.realm"), isDirectory: false),
  67. inMemoryIdentifier: String? = nil,
  68. syncConfiguration: SyncConfiguration? = nil,
  69. encryptionKey: Data? = nil,
  70. readOnly: Bool = false,
  71. schemaVersion: UInt64 = 0,
  72. migrationBlock: MigrationBlock? = nil,
  73. deleteRealmIfMigrationNeeded: Bool = false,
  74. shouldCompactOnLaunch: ((Int, Int) -> Bool)? = nil,
  75. objectTypes: [ObjectBase.Type]? = nil) {
  76. self.fileURL = fileURL
  77. if let inMemoryIdentifier = inMemoryIdentifier {
  78. self.inMemoryIdentifier = inMemoryIdentifier
  79. }
  80. if let syncConfiguration = syncConfiguration {
  81. self.syncConfiguration = syncConfiguration
  82. }
  83. self.encryptionKey = encryptionKey
  84. self.readOnly = readOnly
  85. self.schemaVersion = schemaVersion
  86. self.migrationBlock = migrationBlock
  87. self.deleteRealmIfMigrationNeeded = deleteRealmIfMigrationNeeded
  88. self.shouldCompactOnLaunch = shouldCompactOnLaunch
  89. self.objectTypes = objectTypes
  90. }
  91. // MARK: Configuration Properties
  92. /**
  93. A configuration value used to configure a Realm for synchronization with MongoDB Realm. Mutually
  94. exclusive with `inMemoryIdentifier`.
  95. */
  96. public var syncConfiguration: SyncConfiguration? {
  97. get {
  98. return _syncConfiguration
  99. }
  100. set {
  101. _inMemoryIdentifier = nil
  102. _syncConfiguration = newValue
  103. }
  104. }
  105. private var _syncConfiguration: SyncConfiguration?
  106. /// The local URL of the Realm file. Mutually exclusive with `inMemoryIdentifier`.
  107. public var fileURL: URL? {
  108. get {
  109. return _path.map { URL(fileURLWithPath: $0) }
  110. }
  111. set {
  112. _inMemoryIdentifier = nil
  113. _path = newValue?.path
  114. }
  115. }
  116. private var _path: String?
  117. /// A string used to identify a particular in-memory Realm. Mutually exclusive with `fileURL` and
  118. /// `syncConfiguration`.
  119. public var inMemoryIdentifier: String? {
  120. get {
  121. return _inMemoryIdentifier
  122. }
  123. set {
  124. _path = nil
  125. _syncConfiguration = nil
  126. _inMemoryIdentifier = newValue
  127. }
  128. }
  129. private var _inMemoryIdentifier: String?
  130. /// A 64-byte key to use to encrypt the data, or `nil` if encryption is not enabled.
  131. public var encryptionKey: Data?
  132. /**
  133. Whether to open the Realm in read-only mode.
  134. For non-synchronized Realms, this is required to be able to open Realm files which are not
  135. writeable or are in a directory which is not writeable. This should only be used on files
  136. which will not be modified by anyone while they are open, and not just to get a read-only
  137. view of a file which may be written to by another thread or process. Opening in read-only
  138. mode requires disabling Realm's reader/writer coordination, so committing a write
  139. transaction from another process will result in crashes.
  140. Syncronized Realms must always be writeable (as otherwise no synchronization could happen),
  141. and this instead merely disallows performing write transactions on the Realm. In addition,
  142. it will skip some automatic writes made to the Realm, such as to initialize the Realm's
  143. schema. Setting `readOnly = YES` is not strictly required for Realms which the sync user
  144. does not have write access to, but is highly recommended as it will improve error reporting
  145. and catch some errors earlier.
  146. Realms using query-based sync cannot be opened in read-only mode.
  147. */
  148. public var readOnly: Bool = false
  149. /// The current schema version.
  150. public var schemaVersion: UInt64 = 0
  151. /// The block which migrates the Realm to the current version.
  152. public var migrationBlock: MigrationBlock?
  153. /**
  154. Whether to recreate the Realm file with the provided schema if a migration is required. This is the case when
  155. the stored schema differs from the provided schema or the stored schema version differs from the version on
  156. this configuration. Setting this property to `true` deletes the file if a migration would otherwise be required
  157. or executed.
  158. - note: Setting this property to `true` doesn't disable file format migrations.
  159. */
  160. public var deleteRealmIfMigrationNeeded: Bool {
  161. get {
  162. return _deleteRealmIfMigrationNeeded
  163. }
  164. set(newValue) {
  165. if newValue && syncConfiguration != nil {
  166. throwRealmException("Cannot set 'deleteRealmIfMigrationNeeded' when sync is enabled ('syncConfig' is set).")
  167. }
  168. _deleteRealmIfMigrationNeeded = newValue
  169. }
  170. }
  171. private var _deleteRealmIfMigrationNeeded: Bool = false
  172. /**
  173. A block called when opening a Realm for the first time during the
  174. life of a process to determine if it should be compacted before being
  175. returned to the user. It is passed the total file size (data + free space)
  176. and the total bytes used by data in the file.
  177. Return `true ` to indicate that an attempt to compact the file should be made.
  178. The compaction will be skipped if another process is accessing it.
  179. */
  180. public var shouldCompactOnLaunch: ((Int, Int) -> Bool)?
  181. /// The classes managed by the Realm.
  182. public var objectTypes: [ObjectBase.Type]? {
  183. get {
  184. return self.customSchema.map { $0.objectSchema.compactMap { $0.objectClass as? ObjectBase.Type } }
  185. }
  186. set {
  187. self.customSchema = newValue.map { RLMSchema(objectClasses: $0) }
  188. }
  189. }
  190. /**
  191. The maximum number of live versions in the Realm file before an exception will
  192. be thrown when attempting to start a write transaction.
  193. Realm provides MVCC snapshot isolation, meaning that writes on one thread do
  194. not overwrite data being read on another thread, and instead write a new copy
  195. of that data. When a Realm refreshes it updates to the latest version of the
  196. data and releases the old versions, allowing them to be overwritten by
  197. subsequent write transactions.
  198. Under normal circumstances this is not a problem, but if the number of active
  199. versions grow too large, it will have a negative effect on the filesize on
  200. disk. This can happen when performing writes on many different threads at
  201. once, when holding on to frozen objects for an extended time, or when
  202. performing long operations on background threads which do not allow the Realm
  203. to refresh.
  204. Setting this property to a non-zero value makes it so that exceeding the set
  205. number of versions will instead throw an exception. This can be used with a
  206. low value during development to help identify places that may be problematic,
  207. or in production use to cause the app to crash rather than produce a Realm
  208. file which is too large to be oened.
  209. */
  210. public var maximumNumberOfActiveVersions: UInt?
  211. /// A custom schema to use for the Realm.
  212. private var customSchema: RLMSchema?
  213. /// If `true`, disables automatic format upgrades when accessing the Realm.
  214. internal var disableFormatUpgrade: Bool = false
  215. // MARK: Private Methods
  216. internal var rlmConfiguration: RLMRealmConfiguration {
  217. let configuration = RLMRealmConfiguration()
  218. if let syncConfiguration = syncConfiguration {
  219. configuration.syncConfiguration = syncConfiguration.asConfig()
  220. }
  221. if let fileURL = fileURL {
  222. configuration.fileURL = fileURL
  223. } else if let inMemoryIdentifier = inMemoryIdentifier {
  224. configuration.inMemoryIdentifier = inMemoryIdentifier
  225. } else if syncConfiguration == nil {
  226. fatalError("A Realm Configuration must specify a path or an in-memory identifier.")
  227. }
  228. configuration.encryptionKey = self.encryptionKey
  229. configuration.readOnly = self.readOnly
  230. configuration.schemaVersion = self.schemaVersion
  231. configuration.migrationBlock = self.migrationBlock.map { accessorMigrationBlock($0) }
  232. configuration.deleteRealmIfMigrationNeeded = self.deleteRealmIfMigrationNeeded
  233. if let shouldCompactOnLaunch = self.shouldCompactOnLaunch {
  234. configuration.shouldCompactOnLaunch = ObjectiveCSupport.convert(object: shouldCompactOnLaunch)
  235. } else {
  236. configuration.shouldCompactOnLaunch = nil
  237. }
  238. configuration.setCustomSchemaWithoutCopying(self.customSchema)
  239. configuration.disableFormatUpgrade = self.disableFormatUpgrade
  240. configuration.maximumNumberOfActiveVersions = self.maximumNumberOfActiveVersions ?? 0
  241. return configuration
  242. }
  243. internal static func fromRLMRealmConfiguration(_ rlmConfiguration: RLMRealmConfiguration) -> Configuration {
  244. var configuration = Configuration()
  245. configuration._path = rlmConfiguration.fileURL?.path
  246. configuration._inMemoryIdentifier = rlmConfiguration.inMemoryIdentifier
  247. if let objcSyncConfig = rlmConfiguration.syncConfiguration {
  248. configuration._syncConfiguration = SyncConfiguration(config: objcSyncConfig)
  249. } else {
  250. configuration._syncConfiguration = nil
  251. }
  252. configuration.encryptionKey = rlmConfiguration.encryptionKey
  253. configuration.readOnly = rlmConfiguration.readOnly
  254. configuration.schemaVersion = rlmConfiguration.schemaVersion
  255. configuration.migrationBlock = rlmConfiguration.migrationBlock.map { rlmMigration in
  256. return { migration, schemaVersion in
  257. rlmMigration(migration.rlmMigration, schemaVersion)
  258. }
  259. }
  260. configuration.deleteRealmIfMigrationNeeded = rlmConfiguration.deleteRealmIfMigrationNeeded
  261. configuration.shouldCompactOnLaunch = rlmConfiguration.shouldCompactOnLaunch.map(ObjectiveCSupport.convert)
  262. configuration.customSchema = rlmConfiguration.customSchema
  263. configuration.disableFormatUpgrade = rlmConfiguration.disableFormatUpgrade
  264. configuration.maximumNumberOfActiveVersions = rlmConfiguration.maximumNumberOfActiveVersions
  265. return configuration
  266. }
  267. }
  268. }
  269. // MARK: CustomStringConvertible
  270. extension Realm.Configuration: CustomStringConvertible {
  271. /// A human-readable description of the configuration value.
  272. public var description: String {
  273. return gsub(pattern: "\\ARLMRealmConfiguration",
  274. template: "Realm.Configuration",
  275. string: rlmConfiguration.description) ?? ""
  276. }
  277. }
  278. // MARK: Equatable
  279. extension Realm.Configuration: Equatable {
  280. public static func == (lhs: Realm.Configuration, rhs: Realm.Configuration) -> Bool {
  281. lhs.encryptionKey == rhs.encryptionKey &&
  282. lhs.fileURL == rhs.fileURL &&
  283. lhs.syncConfiguration?.partitionValue == rhs.syncConfiguration?.partitionValue &&
  284. lhs.inMemoryIdentifier == rhs.inMemoryIdentifier &&
  285. lhs.readOnly == rhs.readOnly &&
  286. lhs.schemaVersion == rhs.schemaVersion
  287. }
  288. }