Swift 中的字符串子类解决方法
String subclass workaround in Swift
在我的应用程序中,我使用了各种结构,这些结构基本上应该只是 String 的子类,但 String 本身就是一个结构,因此不可能对其进行子类化。例如,我有一个带有单个 address
变量的电子邮件对象,然后是 isValid
等计算变量或 compose()
等函数。关键是,当整个对象实际上应该是地址时,通过 address
属性 访问实际电子邮件地址似乎很荒谬。
那么创建 Email
对象的正确方法是什么?这是唯一的方法吗?或者有没有办法绕过子类化字符串,以便我的电子邮件结构实际上是一个字符串,只包含一些特定于电子邮件的函数?
我会使用这样的东西:
import Foundation
struct EmailAddress {
let stringValue: String
static func isValidEmailAddress(_ s: String) -> Bool {
//implement me
return true
}
init?(fromString s: String) {
guard EmailAddress.isValidEmailAddress(s) else { return nil }
self.stringValue = s
}
var localPart: String {
return stringValue.components(separatedBy: "@").first!
}
var domain: String {
return stringValue.components(separatedBy: "@").last!
}
// expose other functions that make sense to e-mail addresses.
// importantly, this doesn't expose *all* String methods,
// most of which wouldn't make sense in the context of a String
}
我相信 extension
也是您的想法,但是如果您对 Email
对象使用名称 String
感到不自在...那么我只是建议以下内容:
protocol Email {
func compose() -> Email
var isValidEmail: Bool
}
extension String: Email {
func compose() -> Email {
return something...
}
...
}
var a: Email = "abc"
a.compose()
但是,这当然不能消除您对 String
与 Email
功能有联系的疑虑。
如果你的子类没有属性,只有辅助函数,我建议使用 typealias
。
typealias EmailString = String
extension EmailString {
func isValid() -> Bool { ... }
}
而且我认为电子邮件不应该具有 compose
功能。
作为上述 Alexander 的替代方案,如果您想在 Codable 符合 API 的模型之一中使用此自定义类型:
struct SomeModel: APIModel, Codable {
let id: String
let emailAddress: EmailAddress?
enum CodingKeys: String, CodingKey {
case id
case emailAddress = "email_address"
}
}
您的 EmailAddress
类型可能如下所示:
struct EmailAddress: RawRepresentable, Codable {
typealias RawValue = String
let rawValue: RawValue
init?(rawValue: RawValue) {
guard isValidEmail(rawValue) else { return nil }
self.rawValue = rawValue
}
var localPart: String {
rawValue.components(separatedBy: "@").first!
}
var domain: String {
rawValue.components(separatedBy: "@").last!
}
// expose other functions that make sense to e-mail addresses.
// importantly, this doesn't expose *all* String methods,
// most of which wouldn't make sense in the context of a String
}
然后可能会有 ff 结果:
1.有效的电子邮件地址
let model = try! SomeModel.decode([
"id": "xyz",
"email_address": "user@domain.com"
])
print(model.emailAddress) // Optional(EmailAddress(rawValue: "user@domain.com"))
print(model.emailAddress?.localPart) // Optional("user")
print(model.emailAddress?.domain) // Optional("domain.com")
2。无效的电子邮件地址
(不知道为什么服务器会给你一个无效的电子邮件地址。但可能。)
let model = try! SomeModel.decode([
"id": "xyz",
// Fatal error: "Cannot initialize EmailAddress from invalid String value foo.bar"
"email_address": "foo.bar"
])
3。完全没有价值
let model = try! SomeModel.decode([
"id": "xyz"
])
print(model.emailAddress) // nil
将 Codable 与 RawRepresentable 协议配对的好处是它隐式调用后者的可失败初始化器并抛出 DecodingError.dataCorrupted
万一失败了。
在我的应用程序中,我使用了各种结构,这些结构基本上应该只是 String 的子类,但 String 本身就是一个结构,因此不可能对其进行子类化。例如,我有一个带有单个 address
变量的电子邮件对象,然后是 isValid
等计算变量或 compose()
等函数。关键是,当整个对象实际上应该是地址时,通过 address
属性 访问实际电子邮件地址似乎很荒谬。
那么创建 Email
对象的正确方法是什么?这是唯一的方法吗?或者有没有办法绕过子类化字符串,以便我的电子邮件结构实际上是一个字符串,只包含一些特定于电子邮件的函数?
我会使用这样的东西:
import Foundation
struct EmailAddress {
let stringValue: String
static func isValidEmailAddress(_ s: String) -> Bool {
//implement me
return true
}
init?(fromString s: String) {
guard EmailAddress.isValidEmailAddress(s) else { return nil }
self.stringValue = s
}
var localPart: String {
return stringValue.components(separatedBy: "@").first!
}
var domain: String {
return stringValue.components(separatedBy: "@").last!
}
// expose other functions that make sense to e-mail addresses.
// importantly, this doesn't expose *all* String methods,
// most of which wouldn't make sense in the context of a String
}
我相信 extension
也是您的想法,但是如果您对 Email
对象使用名称 String
感到不自在...那么我只是建议以下内容:
protocol Email {
func compose() -> Email
var isValidEmail: Bool
}
extension String: Email {
func compose() -> Email {
return something...
}
...
}
var a: Email = "abc"
a.compose()
但是,这当然不能消除您对 String
与 Email
功能有联系的疑虑。
如果你的子类没有属性,只有辅助函数,我建议使用 typealias
。
typealias EmailString = String
extension EmailString {
func isValid() -> Bool { ... }
}
而且我认为电子邮件不应该具有 compose
功能。
作为上述 Alexander
struct SomeModel: APIModel, Codable {
let id: String
let emailAddress: EmailAddress?
enum CodingKeys: String, CodingKey {
case id
case emailAddress = "email_address"
}
}
您的 EmailAddress
类型可能如下所示:
struct EmailAddress: RawRepresentable, Codable {
typealias RawValue = String
let rawValue: RawValue
init?(rawValue: RawValue) {
guard isValidEmail(rawValue) else { return nil }
self.rawValue = rawValue
}
var localPart: String {
rawValue.components(separatedBy: "@").first!
}
var domain: String {
rawValue.components(separatedBy: "@").last!
}
// expose other functions that make sense to e-mail addresses.
// importantly, this doesn't expose *all* String methods,
// most of which wouldn't make sense in the context of a String
}
然后可能会有 ff 结果:
1.有效的电子邮件地址
let model = try! SomeModel.decode([
"id": "xyz",
"email_address": "user@domain.com"
])
print(model.emailAddress) // Optional(EmailAddress(rawValue: "user@domain.com"))
print(model.emailAddress?.localPart) // Optional("user")
print(model.emailAddress?.domain) // Optional("domain.com")
2。无效的电子邮件地址
(不知道为什么服务器会给你一个无效的电子邮件地址。但可能。)
let model = try! SomeModel.decode([
"id": "xyz",
// Fatal error: "Cannot initialize EmailAddress from invalid String value foo.bar"
"email_address": "foo.bar"
])
3。完全没有价值
let model = try! SomeModel.decode([
"id": "xyz"
])
print(model.emailAddress) // nil
将 Codable 与 RawRepresentable 协议配对的好处是它隐式调用后者的可失败初始化器并抛出 DecodingError.dataCorrupted
万一失败了。