Alter
Alter是一个框架,用来使映射Codable属性和键更加容易。
使用Alter,您不需要创建CodingKey来手动映射键和属性。
Alter通过使用属性包装器和反射来实现键属性映射。
要求
- Swift 5.1或更高版本
- iOS 10.0或更高版本
安装
CocoaPods
Alter可以通过CocoaPods获取。要安装它,只需将以下行添加到您的Podfile中:
pod 'Alter'
Swift 包管理器
首先,创建一个 Package.swift 文件并添加此 GitHub 网址。应如下所示
dependencies: [
.package(url: "https://github.com/nayanda1/Alter.git", from: "1.2.0")
]
然后,在您使用之前,运行 swift build 来编译依赖项
作者
Nayanda Haberty, [email protected]
许可证
ALTER 在 MIT 许可证下可用。有关更多信息,请参阅 LICENSE 文件。
用法
基本用法
例如,如果您需要将 User JSON 映射到 Swift 对象,只需创建 User 类/结构体并实现 Alterable 协议
,然后将所有需要映射到 JSON 的属性标记为 @Mapped 属性。
struct User: Codable, Alterable {
@Mapped
var name: String = ""
@Mapped
var userName: String = ""
@Mapped
var age: Int = 0
}
或直接使用 AlterCodable
,它是 Alterable & Codable
的别名。
struct User: AlterCodable {
@Mapped
var name: String = ""
@Mapped
var userName: String = ""
@Mapped
var age: Int = 0
}
然后您可以这样解析 JSON 为 User Swift 对象或反之后转换
let user: User = getUserFromSomewhere()
//to JSON
let jsonObject: [String: Any] = try! user.toJSON()
let jsonString: String = try! user.toJSONString()
let jsonData: Data = try! user.toJSONData()
//from JSON
let userFromJSON: User = try! .from(json: jsonObject)
let userFromString: User = try! .from(jsonString: jsonString)
let userFromData: User = try! .from(jsonData: jsonData)
Alterable
实际上是一个简单的协议,如果与 Codable
配对,它将具备完整的功能。从 Codable
可扩展的唯一功能是 Alterable
将使用反射来获取所有已映射的属性,并使用它们进行双向映射。
public protocol Alterable
由于 AlterCodable
符合 Codable
,您可以使用与 Codable
相同的方式进行解码和使用可编码解码器或编码器进行编码。
let user: User = getUserFromSomewhere()
let propertyListData = try! PropertyListEncoder().encode(user)
let decodedPropertyList = try! PropertyListDecoder().decode(User.self, from: propertyListData)
let jsonData = try! JSONEncoder().encode(user)
let decodedJsonData = try! JSONDecoder().decode(User.self, from: jsonData)
《Alterable》的真正力量在于映射功能,它消除了在手动进行键映射时需要枚举CodingKey
的要求。如果解码数据的属性名与Swift对象的属性不同,则可以在属性中传递该属性的名称,而不是创建CodingKey
枚举。然后,这些属性将使用这些键进行映射。
struct User: Alterable {
@Mapped(key: "full_name")
var fullName: String = ""
@Mapped(key: "user_name")
var userName: String = ""
@Mapped
var age: Int = 0
}
您可以通过实现init(from:) throws
和func encode(to:) throws
来手动进行解码和编码。`Alterable`有一些扩展可以帮助您手动进行解码和编码。
struct User: AlterCodable {
@Mapped(key: "full_name")
var fullName: String = ""
@Mapped(key: "user_name")
var userName: String = ""
@Mapped
var age: Int = 0
var image: UIImage? = nil
required init() {}
init(from decoder: Decoder) throws {
self.init()
// this will automatically decode all Mapped properties and return container which you could use to decode property that not mapped
let container = try decodeMappedProperties(from: decoder)
// you could decode any type as long is Codable and passing String as a Key
let base64Image: String = try container.decode(forKey: "image")
if let imageData: Data = Data(base64Encoded: base64Image) {
self.image = UIImage(data: imageData)
}
}
func encode(to encoder: Encoder) throws {
// this will automatically encode all Mapped properties and return container which you could use to encode property that not mapped
var container = try encodeMappedProperties(to: encoder)
if let base64Image = self.image.pngData()?.base64EncodedString() {
// you could encode any type as long is Codable and passing String as a Key
container.encode(value: base64Image, forKey: "address")
}
}
}
手动映射
如果您使用非Codable
类型的属性,或者可能想在其他Swift属性中代表不同的数据,而不是实际属性,则可以使用@AlterMapped
属性而不是@Mapped
,并将TypeAlterer
作为转换器传递。使用此方法,您不需要手动实现init(from:) throws
和func encode(to:) throws
。
struct User: AlterCodable {
@Mapped(key: "full_name")
var fullName: String = ""
@Mapped(key: "user_name")
var userName: String = ""
@Mapped
var age: Int = 0
// manual mapping
@AlterMapped(alterer: Base64ImageAlterer(format: .png))
var image: UIImage = .init()
// manual mapping with key
@AlterMapped(key: "birth_date", alterer: StringDateAlterer(pattern: "dd-MM-yyyy"))
var birthDate: Date = .distantPast
}
如果您的数据类型是可选的、数组或两者都是,您可以使用optionally
计算属性或forArray
计算属性,甚至两者的组合,因为属性是TypeAlterer
协议的扩展。属性调用顺序会影响TypeAlterer
类型的结果。
struct User: AlterCodable {
@Mapped(key: "full_name")
var fullName: String = ""
@Mapped(key: "user_name")
var userName: String = ""
@Mapped
var age: Int = 0
@AlterMapped(key: "birth_date", alterer: StringDateAlterer(pattern: "dd-MM-yyyy"))
var birthDate: Date = .distantPast
// optional
@AlterMapped(alterer: Base64ImageAlterer(format: .png).optionally)
var image: UIImage? = nil
// array
@AlterMapped(key: "login_times", alterer: UnixLongDateAlterer().forArray)
var loginTimes: [Date] = []
// array optional
@AlterMapped(key: "crashes_times", alterer: UnixLongDateAlterer().forArray.forOptional)
var crashesTimes: [Date]? = nil
// array of optional
@AlterMapped(key: "some_times", alterer: UnixLongDateAlterer().forOptional.forArray)
var someTimes: [Date?] = []
}
来自Alter的本地TypeAlterer可以使用。
UnixLongDateAlterer
,用于将Date转换为Int64或反向转化。StringDateAlterer
,用于将Date转换为模式化字符串或反向转化。Base64DataAlterer
,用于将Data转换为Base64字符串或反向转化。Base64ImageAlterer
,用于将UIImage转换为Base64字符串或反向转化。
如果您想实现自己的TypeAlterer
,只需创建一个实现TypeAlterer
的类或结构体。Value
是属性值类型,《AlteredValue》是编码值,应该实现Codable
。
public struct MyOwnDataAlterer: TypeAlterer {
public typealias Value = Data
public typealias AlteredValue = String
public init() { }
public func alter(value: Data) -> String {
value.base64EncodedString()
}
public func alterBack(value: String) -> Data {
Data(base64Encoded: value) ?? .init()
}
}
可变性
在大多数情况下,我们不想让我们的模型是可变的,但由于Alter需要属性可变,以便在对象创建时进行分配,因此您只需将setter设置为私有。
struct User: AlterCodable {
@Mapped(key: "full_name")
private(set) var fullName: String = ""
@Mapped(key: "user_name")
private(set) var userName: String = ""
@Mapped
private(set) var age: Int = 0
}
如果您希望将Alterable视为字典,有一些额外功能。任何实现MutableAlterable
协议的对象都可以像字典一样处理。
struct MutableUser: MutableAlterable {
@Mapped(key: "user_name")
var userName: String? = nil
...
...
...
}
或者使用MutableAlterCodable
,它是MutableAlterable & Codable
的类型别名
struct MutableUser: MutableAlterCodable {
@Mapped(key: "user_name")
var userName: String? = nil
...
...
...
}
然后您可以将其视为字典
let user = MutableUser()
user[mappedKey: "user_name"] = "this is username"
// will print "this is username"
print(user.userName)
let userName: String = user[mappedKey: "user_name"] ?? ""
// will print "this is username"
print(userName)
索引器可以接受任何类型,只要该类型可以转换为属性的实际类型或转换后的类型。