我有一个我自己无法解决的问题。
我正在使用 Vapor4、Fluent 以及用于后端应用程序的 postgres 数据库。(见Package.swift
下文)
有几种不同的模型Transaction
, PlanItem
,Budget
它们对于收集财务统计数据都很有趣。在其他字段中,它们都有一个有效的日期间隔。此功能是由Statisticable
-protocol 强制执行的(见下文)
如果我现在DateInterval
根据Statisticable
every提供的值创建一个Transaction
或PlanItem
工作正常,则仅访问Budget
对象会引发以下错误:
NIO-ELT-0-#8 (10): EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
这里唯一的区别是Budget
不直接符合Statisticable
. Budget
-objects 包裹在 -object 中,它BudgetInterval
符合Statisticable
并传递给统计信息生成器。
现在的问题是:这里出了什么问题?怎样才能弄清楚这其中的原因?
包.swift
// swift-tools-version:5.2
import PackageDescription
let package = Package(
name: "backend",
platforms: [
.macOS(.v10_15)
],
dependencies: [
.package(url: "https://github.com/vapor/vapor.git", from: "4.35.0"),
.package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"),
.package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0"),
.package(url: "https://github.com/vapor/jwt.git", from: "4.0.0-rc.2"),
.package(url: "https://github.com/iLem0n/SwiftyBeaver.git", .exact("1.8.4")),
.package(url: "../SwiftSpec", .branch("master")),
.package(url: "https://github.com/Maxim-Inv/SwiftDate.git", .branch("master")),
.package(url: "https://github.com/vapor/queues.git", from: "1.0.0"),
.package(url: "https://github.com/vapor/queues-redis-driver.git", from: "1.0.0-rc.1"),
.package(url: "https://github.com/dehesa/CodableCSV", from: "0.6.2")
],
targets: [
.target(
name: "App",
dependencies: [
.product(name: "Fluent", package: "fluent"),
.product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"),
.product(name: "Vapor", package: "vapor"),
.product(name: "JWT", package: "jwt"),
.product(name: "SwiftyBeaver", package: "SwiftyBeaver"),
.product(name: "SwiftDate", package: "SwiftDate"),
.product(name: "SwiftSpec", package: "SwiftSpec"),
.product(name: "CodableCSV", package: "CodableCSV"),
.product(name: "Queues", package: "queues"),
.product(name: "QueuesRedisDriver", package: "queues-redis-driver")
],
swiftSettings: [
.unsafeFlags(["-cross-module-optimization"], .when(configuration: .release))
]
),
.target(name: "Run", dependencies: [.target(name: "App")]),
.testTarget(name: "AppTests", dependencies: [
.target(name: "App"),
.product(name: "XCTVapor", package: "vapor"),
])
]
)
可统计的.swift
public protocol Statisticable: CustomDebugStringConvertible {
var amountPerInstance: Double { get }
var repetition: RepetitionType { get }
var validFrom: Date { get }
var validUntil: Date? { get }
var validInterval: DateInterval? { get }
}
StatisticsGenerator.swift
public class StatisticsGenerator {
private let latestBalance: Balance
init(latestBalance: Balance) {
self.latestBalance = latestBalance
}
func gatherStatistics(_ elements: [Statisticable], initial: Balance, lastDate: Date) -> [Date: DayStatistic] {
var result: [Date: DayStatistic] = [:]
let interval = DateInterval(start: initial.date, end: lastDate)
for element in elements {
switch element.repetition {
case .once:
if interval.contains(element.validFrom) {
upsert(amount: element.amountPerInstance, at: element.validFrom, result: &result)
}
default:
/* HERE IS THE ERROR, but only if the object is originally a `BudgetInterval` */
let elementInterval = DateInterval(start: element.validFrom, end: element.validUntil ?? lastDate)
/* ... */
}
}
return result
}
/* ... */
}
BudgetInterval.swift
struct BudgetInterval {
let budget: Budget
let interval: DateInterval
}
extension BudgetInterval: Statisticable {
// accessing `budget.validFrom` throws error
var validFrom: Date {
return max(budget.validFrom, interval.start)
}
var validUntil: Date? {
if let validUntil = budget.validUntil {
return min(validUntil, interval.end)
}
return interval.end
}
var repetition: RepetitionType {
.daily
}
var amountPerInstance: Double {
budget.maxExpense / Double(interval.numberOf(.day)!)
}
}
预算.swift
final class Budget: Model {
static let schema: String = "budgets"
@ID(key: .id) var id: UUID?
@Parent(key: .ownerId) var owner: User
@Field(key: .name) var name: String
@Field(key: .maxExpense) var maxExpense: Double
@Field(key: .validFrom) var validFrom: Date
@Field(key: .validUntil) var validUntil: Date?
/* init stuff */
}
Transaction.swift(用于比较)
final class Transaction: Model {
static var schema: String = "transactions"
@ID(custom: .id) var id: String?
@Parent(key: .ownerId) var owner: User
@Field(key: .receiver) var receiver: String?
@Field(key: .reason) var reason: String
@Field(key: .amount) var amount: Double
@Field(key: .date) var date: Date
/* init stuff */
}
extension Transaction: Statisticable {
var amountPerInstance: Double {
return self.amount
}
// accessing this works fine
var validFrom: Date {
return self.date.dateAtStartOf(.day).date
}
var validUntil: Date? {
return self.date.dateAtEndOf(.day).date
}
}