Partial
Partial 是一个类型安全的包装器,它镜像被包装类型属性的属性,但使每个属性都可选。
var partialSize = Partial<CGSize>()
partialSize.width = 6016
partialSize.height = 3384
try CGSize(partial: partialSize) // `CGSize(width: 6016, height: 3384)`
partialSize.height = nil
try CGSize(partial: partialSize) // Throws `Partial<CGSize>.Error<CGFloat>.keyPathNotSet(\.height)`
文档
Partial 完全 dokumentiert, mit einem code-level documentation available online. The online documentation is generated from the source code with every release, so it is up-to-date with the latest release, but may be different to the code in master
。
使用概述
Partial有一个基于KeyPath
的API,允许它完全类型安全。通过动态成员查找或函数设置、检索和删除键路径是可能的。
var partialSize = Partial<CGSize>()
// Set key paths
partialSize.width = 6016
partialSize.setValue(3384, for: \.height)
// Retrieve key paths
partialSize.width // `Optional<CGFloat>(6016)`
try partialSize.value(for: \.height) // `3384`
// Remove key paths
partialSize.width = nil
partialSize.removeValue(for: \.width)
键路径注意事项
Swift 中的键路径非常强大,但正因为如此,使用 partial 时存在一些需要注意的地方。
通常我强烈建议您不要将键路径用作属性的属性。原因有两点
- 在展开部分类型时,会创建歧义。
- 动态成员搜索不支持属性属性的关键路径。
struct SizeWrapper: PartialConvertible {
let size: CGSize
init<PartialType: PartialProtocol>(partial: PartialType) throws where PartialType.Wrapped == SizeWrapper {
// Should unwrap `size` directly...
size = try partial.value(for: \.size)
// ... or unwrap each property of `size`?
let width = try partial.value(for: \.size.width)
let height = try partial.value(for: \.size.height)
size = CGSize(width: width, height: height)
}
}
var sizeWrapperPartial = Partial<SizeWrapper>()
sizeWrapperPartial.size.width = 6016 // This is not possible
构建复杂类型
由于 Partial
是一个值类型,它不适合在多段代码之间传递。为了允许构建一个类型的单个实例,提供了 PartialBuilder
类,它还提供了订阅更新的能力。
let sizeBuilder = PartialBuilder<CGSize>()
let allChangesSubscription = sizeBuilder.subscribeToAllChanges { (keyPath: PartialKeyPath<CGSize>, builder: PartialBuilder<CGSize>) in
print("\(keyPath) was updated")
}
var widthSubscription = sizeBuilder.subscribeForChanges(to: \.width) { update in
print("width has been updated from \(update.oldValue) to \(update.newValue)")
}
// Notifies both subscribers
partial[\.width] = 6016
// Notifies the all changes subscriber
partial[\.height] = 3384
// Subscriptions can be manually cancelled
allChangesSubscription.cancel()
// Notifies the width subscriber
partial[\.width] = 6016
// Subscriptions will be cancelled when deallocated
widthSubscription = nil
// Does not notify any subscribers
partial[\.width] = 6016
在构建更复杂类型时,建议使用每个属性的构建器,并使用这些构建器在根构建器上设置关键路径
struct Root {
let size1: CGSize
let size2: CGSize
}
let rootBuilder = PartialBuilder<Root>()
let size1Builder = rootBuilder.builder(for: \.size1)
let size2Builder = rootBuilder.builder(for: \.size2)
size1Builder.setValue(1, for: \.width)
size1Builder.setValue(2, for: \.height)
// These will evaluate to `true`
try? size1Builder.unwrapped() == CGSize(width: 1, height: 2)
try? rootBuilder.value(for: \.size1) == CGSize(width: 1, height: 2)
try? rootBuilder.value(for: \.size2) == nil
使用 Subscription
同步每个属性构建器。您可以通过使用 PropertyBuilder.detach()
来取消订阅,如下所示
size2Builder.detach()
size2Builder.setValue(3, for: \.width)
size2Builder.setValue(4, for: \.height)
// These will evaluate to `true`
try? size2Builder.unwrapped() == CGSize(width: 3, height: 4)
try? rootBuilder.value(for: \.size2) == nil
Optional
处理 部分类型正好反映了包装类型的属性,这意味着可选属性仍然将是可选的。在使用动态成员搜索时,这可能不会成为大问题,因为可选值将包裹在另一个可选值中。
下面的例子将使用具有可选属性的类型
struct Foo {
let bar: String?
}
var fooPartial = Partial<Foo>()
使用 setValue(_:for:)
和 value(for:)
函数设置和获取可选值不需要特殊处理
try fooPartial.value(for: \.bar) // Throws `Partial<Foo>.Error<String?>.keyPathNotSet(\.bar)`
fooPartial.setValue(nil, for: \.bar)
try fooPartial.value(for: \.bar) // Returns `String?.none`
但是,在动态成员搜索中需要更多的考虑
fooPartial.bar = String?.none // Sets the value to `nil`
fooPartial.bar = nil // Removes the value. Equivalent to setting to `String??.none`
在检索值时,可能需要展开值两次
if let setValue = fooPartial.bar {
if let unwrapped = setValue {
print("`bar` has been set to", unwrapped)
} else {
print("`bar` has been set to `nil`")
}
} else {
print("`bar` has not been set")
}
为您自己的类型添加支持
采用 PartialConvertible
协议表示一个类型可以用部分来初始化
protocol PartialConvertible {
init<PartialType: PartialProtocol>(partial: PartialType) throws where PartialType.Wrapped == Self
}
value(for:)
函数如果没有设置键路径将抛出错误,这在添加一致性时可能很有用。例如,要将 PartialConvertible
一致性添加到 CGSize
,您可以使用 value(for:)
来检索 width
和 height
值
extension CGSize: PartialConvertible {
public init<PartialType: PartialProtocol>(partial: PartialType) throws where PartialType.Wrapped == CGSize {
let width = try partial.value(for: \.width)
let height = try partial.value(for: \.height)
self.init(width: width, height: height)
}
}
作为一个便利,您可以展开包裹符合 PartialConvertible
一致性的类型的部分
let sizeBuilder = PartialBuilder<CGSize>()
// ...
let size = try! sizeBuilder.unwrapped()
还可以将键路径设置为部分值。如果解包失败,键路径将不会更新,并且将抛出错误
struct Foo {
let size: CGSize
}
var partialFoo = Partial<Foo>()
var partialSize = Partial<CGSize>()
partialSize[\.width] = 6016
try partialFoo.setValue(partialSize, for: \.size) // Throws `Partial<CGSize>.Error.keyPathNotSet(\.height)`
partialSize[\.height] = 3384
try partialFoo.setValue(partialSize, for: \.size) // Sets `size` to `CGSize(width: 6016, height: 3384)`
使用属性包装器
PartiallyBuilt
是一个可以应用于任何 PartialConvertible
属性的属性包装器。属性包装器的 projectedValue
是一个 PartialBuilder
,允许进行以下使用:
struct Foo {
@PartiallyBuilt<CGSize>
var size: CGSize?
}
var foo = Foo()
foo.size // nil
foo.$size.width = 1024
foo.$size.height = 720
foo.size // CGSize(width: 1024, height: 720)
测试和持续集成
Partial 拥有完整的测试套件,作为拉取请求的一部分在 Travis CI 上运行。所有测试必须通过才能合并拉取请求。
代码覆盖率收集并报告给 Codecov。不可能达到 100% 的覆盖率;一些代码行应该永远不被触及,但却是为了类型安全所必需的,Swift 不会跟踪作为覆盖率的一部分的 deinit
函数。在审查降低总体代码覆盖率的拉取请求时,将考虑这些限制。
安装
SwiftPM
要通过 SwiftPM 安装,请将包添加到依赖项部分,并作为目标依赖项:
let package = Package(
...
dependencies: [
.package(url: "https://github.com/JosephDuffy/Partial.git", from: "1.0.0"),
],
targets: [
.target(name: "MyApp", dependencies: ["Partial"]),
],
...
)
Carthage
要通过 Carthage 安装,请在您的 Cartfile
中添加以下内容:
github "JosephDuffy/Partial"
运行 carthage update Partial
以构建框架,然后将构建的框架文件拖到您的 Xcode 项目中。Partial 提供预编译的二进制文件,这可能会导致一些符号问题,刷新并拖到 Xcode 项目。如果存在问题,请使用 --no-use-binaries
标志。
请记住将 Partial 添加到您的 Carthage 构建阶段
$(SRCROOT)/Carthage/Build/iOS/Partial.framework
和
$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/Partial.framework
CocoaPods
通过 CocoaPods 安装,请将以下内容添加到您的 Podfile 文件中。
pod 'Partial'
然后运行 pod install
命令。
许可证
该项目遵循 MIT 许可。请查看 LICENSE 文件以获取完整许可内容。