Realm crashes, not just Realm crashes, but any database crashes are always a problem, and there are always a few random bugs that don’t make sense.
This article provides a solution to Realm database crashes
Date: April 28, 2020
See the highlights for the code section, as well as Java and other platforms for reference. The current database crash rate is probably: one in hundreds of thousands
Keep the following in mind:
- Writing data to a Realm blocks synchronously, but reading does not
- Realm managed objects are not cross-threaded, meaning that different threads cannot modify each other’s objects
- Any modifications to realm-managed objects must be done in realm.write{}
- Realm uses a zero-copy architecture.
- Use write events as little as possible. You can try to write more data in batches
- Load the write operation into a dedicated thread for execution.
- Delay initialization of any type that uses a Realm API property until the application is configured with a Realm. Otherwise it will crash.
Official restrictions:
- Class names can store up to 57 UTF8 characters in length.
- The attribute name can contain a maximum of 63 UTF8 characters.
- The Data and String attributes cannot hold Data larger than 16 MB
- Each individual Realm file size cannot exceed the amount of memory an app is allowed to use on iOS – this amount varies from device to device and depends on how fragmented the memory space is at the time (there is a related Radar: RDAR ://17119975 on this issue). If you need to store large amounts of data, you can choose to use multiple Realm files and map them.
- String sorting and case-insensitive queries only support basic Latin Character set, Supplementary Latin Character Set, Extended Latin Character set A, and Extended Latin Character set B (UTF-8 ranges from 0 to 591).
A problem with multithreading in Realm
Modify data across threads
Condition 1: Thread A creates the object Xiaoming and hosts it into A realm
Thread B creates xiaomei and hosts it into realm
Can I modify xiaomei created in thread B directly from thread A?
No, once objects are hosted in realm, modifying realm objects in other threads will crash
Two, cross-thread transfer
Official example:
let person = Person(name: "Jane")
try! realm.write {
realm.add(person)
}
let personRef = ThreadSafeReference(to: person)
DispatchQueue(label: "background").async {
autoreleasepool {
let realm = try! Realm()
guard let person = realm.resolve(personRef) else {
return} try! realm.write { person.name ="Jane Doe"}}}Copy the code
Realm provides a mechanism for safe delivery of thread-constrained instances through three steps:
- Construct a ThreadSafeReference from a thread-constrained object;
- Pass this ThreadSafeReference to the target thread or queue;
- Resolve this reference by calling realm.resolve (_:) on the target Realm.
Get rid of Realm data hosting and modify objects freely
If I change the age of Xiaoming from 29 to 28, I do not want to save it in the database immediately, because I am not sure whether the age of 28 is correct. I want to change it temporarily, so that the database can not host it. What should I do in this case?
The answer is: deep copy + primary key update
Import Foundation import RealmSwift /// Int, String, Float, Double, Bool, Date, Data, /// List<Object>, List<Int>, List<String>, List<Float>, /// List<Double>, List<Bool>, List<Date>, List<Data> all support protocol DetachableObject: AnyObject { func detached() -> Self } extension Object: DetachableObject { func detached() -> Self {let detached = type(of: self).init()
for property in objectSchema.properties {
guard let value = value(forKey: property.name) else {
continue
}
if let detachable = value as? DetachableObject {
detached.setValue(detachable.detached(), forKey: property.name)
} else { // Then it is a primitive
detached.setValue(value, forKey: property.name)
}
}
return detached
}
}
extension List: DetachableObject {
func detached() -> List<Element> {
let result = List<Element>()
forEach {
if let detachableObject = $0 as? DetachableObject,
let element = detachableObject.detached() as? Element {
result.append(element)
} else { // Then it is a primitive
result.append($0)}}return result
}
}
Copy the code
Int, String, Float, Double, Bool, Date, Data, List, List, List, List, List, List, List, List, List, List, List, List etc
How can different threads use different realms
fileprivate init() {_realm = try Realm(configuration: ······} fileprivate var _realm: Realm? public var realm: Realm? { get {if Thread.isMainThread {
return _realm ?? (try? Realm())
} else {
return try? Realm()
}
}
}
Copy the code
Five, the above seems to solve the problem, in fact, there will be a great hidden trouble.
This is the reason most relam crashes in the program.
Xiangming and xiaoming will not be the same as xiangming and the main thread will not be the same as xiangming.
Solutions:
- All managed objects are written uniformly using the ThreadSafeReference mentioned above.
Disadvantages: ThreadSafeReference objects can only be parsed once at most. If ThreadSafeReference parsing fails, the original version of the Realm will be locked until the reference is released. Therefore, the lifetime of a ThreadSafeReference should be short.
- Objects are read and written on the same thread (including realm instances, and objects)
I recommend getting realm instances and object reads and writes in the same thread. How do I guarantee the same thread?
- Low-priority data operations consideration: GCD’s asynchronous serial queue opens up a new thread and can take advantage of this
- High priority data operation consideration: main thread
Key contents:
Deep copy, primary key update
Without further ado, see the code:
import Foundation
import RealmSwift
class RealmManager{
static let shared = RealmManager()
private init() {
_realmMain = try? Realm()
}
private var _realmMain: Realm?
public var realm: Realm? {
get {
if Thread.isMainThread {
return _realmMain ?? (try? Realm())
} else {
returntry? Realm()}}} /// query, return objects hosted in Realm func Objects <Element: Object>(_type: Element.Type) -> [Element] { var result = [Element]() realm! .objects(type).forEach { (element) in
result.append(element)
}
returnFunc object<Element: object, KeyType>(ofTypetype: Element.Type, forPrimaryKey key: KeyType) -> Element? {
returnrealm! .object(ofType:type.forFunc safeQuery<Element: Object>(_type: Element.Type) -> [Element] { var result = [Element]() realm! .objects(type).forEach { (element) in
result.append(element.detached())
}
returnFunc safeQuery<Element: Object, KeyType>(ofTypetype: Element.Type, forPrimaryKey key: KeyType) -> Element? {
returnrealm! .object(ofType:type.forPrimaryKey: key)? .detached()} func safeWrite<T>(object:T)where T:Object {
letNewRealm = realm /// Deep copylet obj = object.detached()
ifT.primarykey () == nil{// Delete old data and then update newRealm? .delete(object) try? newRealm?.write { newRealm?.add(obj) } }else{// Update try with primary key? newRealm? .write { newRealm? .add(obj, update: .all) } } } }Copy the code
In the above code, I implemented them separately
- Common query
- Security query
- Ordinary writing
- In our
If the programmer can guarantee thread-safety, use ordinary query, ordinary write, cannot guarantee, at least one safe operation, do not need to use safe operation for all query write.
It can also be optimized, for example, as long as the main thread gets the data, make a mark, do not operate, do not have to do deep copy, write, direct operation.
Six, other matters needing attention
1. Bypass the App Store submission bug
Create a new “Run Script Phase” in the “Build Phases” of the application target, and paste the following code into the Script text box:
bash "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework/strip-frameworks.sh"
Copy the code
Two, a variety of database initialization
1. In memory
let realm = try! Realm(configuration: Realm.Configuration(inMemoryIdentifier: "MyInMemoryRealm"))
Copy the code
Create an in-memory Realm database that runs entirely in memory and will not be stored on disk.
2. Generally, version upgrades are included
do {
_realm = try Realm(configuration: Realm.Configuration(schemaVersion: schemaVersion, migrationBlock: { migration, oldSchemaVersion in// Database migrationif(oldSchemaVersion < 2) {/ / migration. To add a new field in enumerateObjects (ofType: RealmTPPlanItemModel.className()) { oldObject, newObjectinnewObject? ["timeStyle"] = "EEEE"
}
}
}))
}catch{
TPLog.log(error.localizedDescription)
}
Copy the code
3. Use of the database packaged into the project
public var appConfigureRealm: Realm? {
let config = Realm.Configuration(
fileURL: Bundle.main.url(forResource: "defaultAPP", withExtension: "realm"), readOnly: true, schemaVersion:2) // Open the Realm database through configurationlet realm = try! Realm(configuration: config)
return realm
}
Copy the code
In-depth understanding of Realm’s multithreaded processing mechanism
This article is what you want to know: Database Design: An in-depth look at Realm’s multithreaded processing mechanism
Add V note: nuggets; Get into groups and learn together 🐻