为什么在 Realm 对象声明中添加一个便利的 init 会弄乱私有值?
Why does adding a convenience init to a Realm object declaration mess with private values?
我创建了一个需要存储枚举值的 Realm 对象。为此,我使用了一种方法 ,它涉及声明一个 String 类型的私有 属性,然后声明另一个 属性 类型的 Enum sets/reads 私有 属性 使用 getter 和 setter。
为了便于参考,这里是代码:
@objcMembers
class PlaylistRealmObject: Object {
dynamic var id: String = UUID().uuidString
dynamic var created: Date = Date()
dynamic var title: String = ""
private dynamic var revisionTypeRaw: String = RevisionType.noReminder.rawValue
var revisionType: RevisionType {
get { return RevisionType(rawValue: revisionTypeRaw)! }
set { revisionTypeRaw = newValue.rawValue }
}
let reminders = List<ReminderRealmObject>()
let cardsInPlaylist = List<CardRealmObject>()
override static func primaryKey() -> String? {
return "id"
}
}
我注意到,如果我在 class 声明中添加一个方便的初始化(以便更容易地初始化对象),我最终使用的对象的 revisionType 属性将采用默认值在 class 中声明,而不是使用便利 init 传递给 class 的修订类型值。
这里是 class 带有便利 init
的声明
@objcMembers
class PlaylistRealmObject: Object {
dynamic var id: String = UUID().uuidString
dynamic var created: Date = Date()
dynamic var title: String = ""
private dynamic var revisionTypeRaw: String = RevisionType.noReminder.rawValue
var revisionType: RevisionType {
get { return RevisionType(rawValue: revisionTypeRaw)! }
set { revisionTypeRaw = newValue.rawValue }
}
let reminders = List<ReminderRealmObject>()
let cardsInPlaylist = List<CardRealmObject>()
convenience init(title: String, revisionType: RevisionType) {
self.init()
self.title = title
self.revisionType = revisionType
}
override static func primaryKey() -> String? {
return "id"
}
}
而且 - 让事情变得更加复杂 - 如果我简单地从 revisionTypeRaw 属性 中删除单词 'private',一切正常!
我很困惑。 1)为什么加个convenience init会有这个效果? 2) 为什么使 属性 'public' 解决问题?
我已经创建了一个演示 Xcode 项目来说明这个问题,如果有人需要,可以分享它。
更新:
我发现了问题。它与便利初始化无关。根据 Realm 文档,我在 class 的顶部使用 @objcMembers
:https://realm.io/docs/swift/latest/#property-attributes
如果删除它并将 @objc
放在 private
关键字前面,一切都会按预期进行。我想接下来的问题是:如何解释这种行为?
这是一个很好的问题,但我认为问题出在代码的其他地方。让我们测试一下。
我创建了一个 TestClass,它有一个 Realm managed publicly visible var, name,以及一个 non-managed public var visibleVar,它由 Realm managed private var 支持, 私有变量。我还为每个问题提供了一个方便的初始化。重要的部分是 privateVar 被设置为字符串 "placeholder" 所以我们需要看看它是否被覆盖了。
class TestClass: Object {
@objc dynamic var name = ""
@objc private dynamic var privateVar = "placeholder"
var visibleVar: String {
get {
return self.privateVar
}
set {
self.privateVar = newValue
}
}
convenience init(aName: String, aString: String) {
self.init()
self.name = aName
self.visibleVar = aString
}
}
然后我们创建两个实例并将它们保存在Realm
let a = TestClass(aName: "some name", aString: "some string")
let b = TestClass(aName: "another name", aString: "another string")
realm.add(a)
realm.add(b)
然后一个按钮操作从 Realm 加载两个对象并打印它们。
let testResults = realm.objects(TestClass.self)
for test in testResults {
print(test.name, test.visibleVar)
}
和输出:
some name some string
another name another string
所以在这种情况下,"placeholder" 的默认值在创建实例时被正确覆盖。
编辑:
更多信息。
通过使用@objMembers 定义您的整个 class,它会将您的属性公开给 Objective-C,但随后又将它们隐藏起来。这样 属性 就不会暴露给 ObjC。要扭转这种隐藏,你必须明确地说@objc。因此,更好的做法是使用 @objc dynamic 定义每行的托管领域属性。
我创建了一个需要存储枚举值的 Realm 对象。为此,我使用了一种方法
为了便于参考,这里是代码:
@objcMembers
class PlaylistRealmObject: Object {
dynamic var id: String = UUID().uuidString
dynamic var created: Date = Date()
dynamic var title: String = ""
private dynamic var revisionTypeRaw: String = RevisionType.noReminder.rawValue
var revisionType: RevisionType {
get { return RevisionType(rawValue: revisionTypeRaw)! }
set { revisionTypeRaw = newValue.rawValue }
}
let reminders = List<ReminderRealmObject>()
let cardsInPlaylist = List<CardRealmObject>()
override static func primaryKey() -> String? {
return "id"
}
}
我注意到,如果我在 class 声明中添加一个方便的初始化(以便更容易地初始化对象),我最终使用的对象的 revisionType 属性将采用默认值在 class 中声明,而不是使用便利 init 传递给 class 的修订类型值。
这里是 class 带有便利 init
的声明@objcMembers
class PlaylistRealmObject: Object {
dynamic var id: String = UUID().uuidString
dynamic var created: Date = Date()
dynamic var title: String = ""
private dynamic var revisionTypeRaw: String = RevisionType.noReminder.rawValue
var revisionType: RevisionType {
get { return RevisionType(rawValue: revisionTypeRaw)! }
set { revisionTypeRaw = newValue.rawValue }
}
let reminders = List<ReminderRealmObject>()
let cardsInPlaylist = List<CardRealmObject>()
convenience init(title: String, revisionType: RevisionType) {
self.init()
self.title = title
self.revisionType = revisionType
}
override static func primaryKey() -> String? {
return "id"
}
}
而且 - 让事情变得更加复杂 - 如果我简单地从 revisionTypeRaw 属性 中删除单词 'private',一切正常!
我很困惑。 1)为什么加个convenience init会有这个效果? 2) 为什么使 属性 'public' 解决问题?
我已经创建了一个演示 Xcode 项目来说明这个问题,如果有人需要,可以分享它。
更新:
我发现了问题。它与便利初始化无关。根据 Realm 文档,我在 class 的顶部使用 @objcMembers
:https://realm.io/docs/swift/latest/#property-attributes
如果删除它并将 @objc
放在 private
关键字前面,一切都会按预期进行。我想接下来的问题是:如何解释这种行为?
这是一个很好的问题,但我认为问题出在代码的其他地方。让我们测试一下。
我创建了一个 TestClass,它有一个 Realm managed publicly visible var, name,以及一个 non-managed public var visibleVar,它由 Realm managed private var 支持, 私有变量。我还为每个问题提供了一个方便的初始化。重要的部分是 privateVar 被设置为字符串 "placeholder" 所以我们需要看看它是否被覆盖了。
class TestClass: Object {
@objc dynamic var name = ""
@objc private dynamic var privateVar = "placeholder"
var visibleVar: String {
get {
return self.privateVar
}
set {
self.privateVar = newValue
}
}
convenience init(aName: String, aString: String) {
self.init()
self.name = aName
self.visibleVar = aString
}
}
然后我们创建两个实例并将它们保存在Realm
let a = TestClass(aName: "some name", aString: "some string")
let b = TestClass(aName: "another name", aString: "another string")
realm.add(a)
realm.add(b)
然后一个按钮操作从 Realm 加载两个对象并打印它们。
let testResults = realm.objects(TestClass.self)
for test in testResults {
print(test.name, test.visibleVar)
}
和输出:
some name some string
another name another string
所以在这种情况下,"placeholder" 的默认值在创建实例时被正确覆盖。
编辑:
更多信息。
通过使用@objMembers 定义您的整个 class,它会将您的属性公开给 Objective-C,但随后又将它们隐藏起来。这样 属性 就不会暴露给 ObjC。要扭转这种隐藏,你必须明确地说@objc。因此,更好的做法是使用 @objc dynamic 定义每行的托管领域属性。