肇鑫的技术博客

业精于勤,荒于嬉

Realm的坑(二)

managed与unmanaged

我们都知道,变量都有其生命周期。当存在变量的强引用时,变量会一直存在。比如,有

struct Foo {
   var name:String
}
let bar = Foo(name:"John")

bar在赋值后一直存在。我们始终都可以使用bar.name来获得name属性的值John。但是在Realm中一切都变得不一样了。在Realm中,你通过子类继承Object对象来构建你的Model。然后通过Realm.add来添加这个子类的实例到Realm中。

class Foo:Object {
    dynamic var name:String = ""
}
let bar = Foo()

let realm = try! Realm()
try! realm.write {
	realm.add(bar)
}

你新建出来的bar是unmanaged的实例。realm.add(bar)之后,bar就是managed的实例了。一旦受管,该实例的用法就不能再那么随心所欲了,之后对它的操作就必须在Realm的交易里完成。如:

let bar = Foo()
bar.name = "Marry" // ok

let realm = try! Realm()
try! realm.write {
	realm.add(bar)
	bar.name = "Kelly" // ok
}

bar.name = "Jimmy" // *** Terminating app due to uncaught exception 'RLMException', reason: 'Attempting to modify object outside of a write transaction - call beginWriteTransaction on an RLMRealm instance first.'

为什么name被设置成Marry和Kelly的时候没问题,但是一设置成Jimmy就不行了呢?

  1. 这是因为bar的状态发生了改变。在realm.add()之前,bar是非受管的状态,此时bar在Realm数据库中没有记录。它的行为和一般的类的实例类似,因此可以直接设置name
  2. realm.add()之后,bar在数据库中有了记录,变成了受管的状态,此时再更改它,就必须在realm的写入交易中完成。就像Kelly那样。
  3. Jimmy的部分,由于没有在写入交易中完成,程序会抛出异常并终止运行。

如果不想添加额外的写入交易,就需要添加新建一个非托管对象,然后在添加它到Realm,如:

let anotherBar = Foo()
anotherBar.name = bar.name
  
try! realm.write {
    realm.add(anotherBar)
}

这样的代码有一点儿问题。anotherBarbar对应的是两个不同的对象。现在数据库中同时存在baranotherBar对应的对象了。因此我们需要删除bar

let anotherBar = Foo()
anotherBar.name = bar.name
  
try! realm.write {
    realm.add(anotherBar)
    realm.delete(bar)
}

这很不环保。对于有主键的类的实例,可以使用add(object: Object, update: true),比如:

class Foo:Object {
    dynamic var id:Int = 0
    dynamic var name:String = ""
    
    override class func primaryKey() -> String? {
        return "id"
    }
}

let bar = Foo()
bar.id = 1
bar.name = "Marry" // ok

let realm = try! Realm() 
try! realm.write {
    realm.add(bar)
    bar.name = "Kelly" // ok
}
  
let anotherBar = Foo()
anotherBar.id = 1
anotherBar.name = bar.name
  
try! realm.write {
    realm.add(anotherBar, update:true)
}

每次还要重新设置非受管对象还是挺麻烦的,如果能直接生成一个就好了。这就是第三个坑。Realm的坑(三)

相关

Realm的坑(一)
Realm的坑(二)
Realm的坑(三)
Realm的坑(四)

Realm的坑(一)

Realm中常见的“Realm accessed from incorrect thread”问题的分析总结

Realm的Results中,有一个很有用的特性,叫做自动更新。顾名思义,就是这个结果会在使用时自动重新查询,已保证最新。当然,这也带来了新的问题。

一旦在创建该变量的线程之外使用该变量,就会导致程序崩溃。

比如,今天我就是在请求通知权限的代码中使用了tasks变量。结果导致程序崩溃。因为这个请求是调用了系统的请求,不是程序本身的线程。

解决办法

  1. 新建realm,重新查询
  2. 使用临时变量,而不使用Results的变量。
  3. 使用DispatchQueue.main.async,更改执行的thread。

备注

print(Thread.current) //可以调试当前的thread

相关

Realm的坑(一)
Realm的坑(二)
Realm的坑(三)
Realm的坑(四)

命令行下使用Swift 2.3 工具链(Toolchain)的方法

我们知道Xcode 8 beta 同时提供了Swift 3.0 和Swift 2.3来供我们选择。并且我们还知道,可以在Xcode的偏好里,选择对应的工具,如图1-1所示。

图1-1 在Xcode中选择命令行工具

在Xcode中选择命令行工具w750

此时,在终端中查看swift的版本,会是3.0的,如图1-2所示。

代码 1-2 终端查看Swift版本

~ zhaoxin$ swiftc -v
Apple Swift version 3.0 (swiftlang-800.0.42.1 clang-800.0.36.1)
Target: x86_64-apple-macosx10.9
~ zhaoxin$ xcrun swiftc -v
Apple Swift version 3.0 (swiftlang-800.0.42.1 clang-800.0.36.1)
Target: x86_64-apple-macosx10.9

Swift.org官方给出的建议是使用添加环境变量的方式。

$ export PATH=/Library/Developer/Toolchains/swift-latest.xctoolchain/usr/bin:"${PATH}"

但是这样做只会影响swiftc,不会影响xcrun swiftc。

~ zhaoxin$ export PATH=/Applications/Xcode-beta.app/Contents/Developer/Toolchains/Swift_2.3.xctoolchain/usr/bin:"${PATH}"
$ swiftc -v
Apple Swift version 2.3 (swiftlang-800.10.11 clang-800.0.36)
Target: x86_64-apple-macosx10.9
$ xcrun swiftc -v
Apple Swift version 3.0 (swiftlang-800.0.42.1 clang-800.0.36.1)
Target: x86_64-apple-macosx10.9

阅读xcrun的帮助,我们可以发现:

--toolchain <name> 
		Specifies which toolchain to use to perform the  lookup.  If  no
		--toolchain argument is provided, then the toolchain to use will
		be taken from the TOOLCHAINS environment variable, if present.

OK