问题描述
我正在使用 Vapor4、Fluent 和 postgres 数据库作为后端应用程序。 (见下面的Package.swift
)
有几种不同的模型 Transaction
、PlanItem
、Budget
,它们对于收集金融统计数据都很有趣。在其他字段中,它们都有一个有效的日期间隔。此功能由 Statisticable
协议(见下文)
如果我现在根据 DateInterval
提供的值创建一个 Statisticable
,每个 Transaction
或 PlanItem
工作正常,只有访问 Budget
对象会抛出以下错误:
NIO-ELT-0-#8 (10): EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP,subcode=0x0)
这里唯一的区别是 Budget
不直接符合 Statisticable
。 Budget
对象被包裹在符合 BudgetInterval
的 Statisticable
对象内,并被传递到统计生成器中。
现在的问题是:这里有什么问题?怎样才能找出原因?
Package.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",.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",])
]
)
Statisticable.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)!)
}
}
Budget.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
}
}
解决方法
发现问题不在于数据库访问本身,而只是我生成了一个开始日期在结束日期之后的场景。因此无法构建 DateInterval。
在这种情况下,我对 Foundation
类不检查这种常见问题感到有点失望。
无论如何,如果我之前检查过这个边缘情况,它会起作用。
谢谢