【问题标题】:Unwrapping an Optional value in swift and realm在 swift 和领域中展开可选值
【发布时间】:2022-01-26 09:46:47
【问题描述】:

我为应用程序编写了一个工作函数,但出现错误“隐式部署可选值时意外发现nil值”限制限制标签。文本我无法修复。

属性:

@IBOutlet weak var limitLabel: UILabel!

功能:

func leftLabels(){ 
        let limit = self.realm.objects(Limit.self)
        guard limit.isEmpty == false else {return} 
        
        limitLabel.text = limit[0].limitSum //Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value 
        
        let calendar = Calendar.current 
        
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy/MM/dd HH:mm"
        
        let firstDay = limit[0].limitDate as Date
        let lastDay = limit[0].limitLastDate as Date
        
        let firstComponent = calendar.dateComponents([.year, .month, .day], from: firstDay) 
        let lastComponent = calendar.dateComponents([.year, .month, .day], from: lastDay) 
        
        let startDate = formatter.date(from: "\(firstComponent.year!)/\(firstComponent.month!)/\(firstComponent.day!) 00:00") 

        let endDate = formatter.date(from: "\(lastComponent.year!)/\(lastComponent.month!)/\(lastComponent.day!) 23:59")
        
        let filterLimit: Int = realm.objects(SpendingDB.self).filter("self.date >= %@ && self.date <= %@", startDate ?? "", endDate ?? "").sum(ofProperty: "cost")
        
        ForThePeriod.text = "\(filterLimit)" 
        
        let a = Int(limitLabel.text!)!
        let b = Int(ForThePeriod.text!)!
        let c = a - b 
        
        availableForSpending.text = "\(c)" 

如果你告诉我正确的代码,我会很高兴

【问题讨论】:

  • 你确定不是limitLabelnil 吗?您正在强制展开它,如果在加载视图之前调用它,或者如果此标签未在您的 IB 中连接,那么这将崩溃。尝试使用limitLabel?.text。甚至将其定义为@IBOutlet private var limitLabel: UILabel?,这将迫使您检查这些内容。
  • 如果您将limitLabel.textlimitLabel 属性本身设为可选,那么整个函数都需要功能。但是如果你重做它,那么ForThePeriod 的类似问题已经出现(这与limitLabel 完全相同的属性)

标签: ios swift xcode swift3 uikit


【解决方案1】:

从 cmets 看来,如果您的视图尚未加载,并且您的某些视图仍然是 nil。您的应用程序崩溃,因为在 limitLabel.text = limit[0].limitSum 行中,limitLabelnil。即使调用limitLabel.text = "Hello world!"

,它也会崩溃,无论 Realm

您始终可以保护您需要避免更改代码的数据。只需添加

guard let limitLabel = limitLabel else { return nil } 
guard let ForThePeriod = ForThePeriod else { return nil }

等等。

我尝试稍微清理一下您的代码。很难理解您到底想达到什么目标,但类似以下的内容可能看起来更合适:

func leftLabels() {
    // Elements needed for method to execute.
    guard let limitLabel = limitLabel else { return }
    guard let forThePeriodLabel = forThePeriodLabel else { return }
    guard let availableForSpendingLabel = availableForSpendingLabel else { return }
    
    // Items that will be reused throughout the method later on
    let limits: [Limit]
    let firstLimit: Limit
    let dates: (start: Date?, end: Date?)
    let filterLimit: Int
    
    limits = self.realm.objects(Limit.self)
    guard limits.isEmpty == false else { return }
    firstLimit = limits[0]
    
    // limitLabel
    limitLabel.text = firstLimit.limitSum
    
    // Date components
    dates = {
        let calendar = Calendar.current
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy/MM/dd HH:mm"
        
        let firstDay = firstLimit.limitDate as Date
        let lastDay = firstLimit.limitLastDate as Date
        
        let firstComponent = calendar.dateComponents([.year, .month, .day], from: firstDay)
        let lastComponent = calendar.dateComponents([.year, .month, .day], from: lastDay)
        
        let startDate = formatter.date(from: "\(firstComponent.year!)/\(firstComponent.month!)/\(firstComponent.day!) 00:00")
        let endDate = formatter.date(from: "\(lastComponent.year!)/\(lastComponent.month!)/\(lastComponent.day!) 23:59")
        
        return (startDate, endDate)
    }()
    
    // forThePeriodLabel
    filterLimit = realm.objects(SpendingDB.self).filter("self.date >= %@ && self.date <= %@", startDate ?? "", endDate ?? "").sum(ofProperty: "cost")
    forThePeriodLabel.text = String(filterLimit)
    
    // availableForSpendingLabel
    availableForSpendingLabel.text = {
        guard let a = Int(firstLimit.limitSum) else { return "" }
        let b = filterLimit
        let c = a - b
        return String(c)
    }()
}

注意一些有助于更好地构建和解决代码的做法。

  • 首先保护危险数据
  • 为您的方法创建一个可重复使用的项目列表(应该尽可能少,在大多数情况下没有)。请注意以后如何分配这些。如果你在分配给它之前尝试使用它,你的编译器会警告你。
  • 将尽可能多的代码包装到封闭的部分,例如availableForSpendingLabel.text = { ... code here ... }()
  • 使用元组,例如let dates: (start: Date?, end: Date?)
  • 不要害怕使用长名称,例如availableForSpendingLabel

我什至会进一步尝试将其分解为多种方法。但是我不确定这个方法是做什么的,并假设你只发布了它的一部分......

========== 编辑:添加替代方法 ==========

来自 cmets,这是一个金融应用程序,因此至少处理 Decimal 数字可能是有意义的。还引入了添加一种在内部解析数据的新结构的方法。格式化程序也用于格式化数字。以及其他一些改进:

struct Limit {
    let amount: Decimal
    let startDate: Date
    let endDate: Date
}

struct Spending {
    let cost: Decimal
    let date: Date
}

struct LimitReport {
    let limitAmount: Decimal
    let spendingSum: Decimal
    let balance: Decimal
    
    init(limit: Limit) {
        let limitAmount: Decimal = limit.amount
        let spendingSum: Decimal = {
            let calendar = Calendar.autoupdatingCurrent // Is this OK or should it be some UTC or something?
            func beginningOfDate(_ date: Date) -> Date {
                let components = calendar.dateComponents([.day, .month, .year], from: date)
                return calendar.date(from: components)!
            }
            let startDate = beginningOfDate(limit.startDate)
            let endDate = calendar.date(byAdding: .day, value: 1, to: startDate)
            
            let spendings: [Spending] = realm.objects(Spending.self).filter { $0.date >= startDate && $0.date < endDate }
            return spendings.reduce(0, { $0 + $1.cost })
        }()
        let balance = limitAmount - spendingSum
        
        self.limitAmount = limitAmount
        self.spendingSum = spendingSum
        self.balance = balance
    }
    
}

func leftLabels() {
    // Elements needed for method to execute.
    guard let limitLabel = limitLabel else { return }
    guard let forThePeriodLabel = forThePeriodLabel else { return }
    guard let availableForSpendingLabel = availableForSpendingLabel else { return }
    
    guard let limit = self.realm.objects(Limit.self).first else { return }
    
    let formatter = NumberFormatter()
    formatter.numberStyle = .currency
    formatter.currencySymbol = "$"
    
    let report = LimitReport(limit: limit)
    
    limitLabel.text = formatter.string(from: report.limitAmount)
    forThePeriodLabel.text = formatter.string(from: report.spendingSum)
    availableForSpendingLabel.text = formatter.string(from: report.balance)
}

【讨论】:

  • 非常感谢,我一定会尝试的,这个功能更新了几个标签,因为在金融应用中,如果我花了一些金额,那么标签如:限额,一定时期的费用,总费用 - 应基于元计算计算
  • @ruzaykina 我在底部添加了另一种方法,使其更适合您在评论中提供的额外信息。它可能假设有点过多,但请看看这是否更适合您想要实现的目标。
  • 您可以将保护语句链接在一起。您甚至可以让每个保护子句使用前一个保护子句中定义的变量,如guard let a = b, let c = d, let e = c.property else { return }。我发现这种语法更好,除非我需要不同的 else 子句用于单独的保护语句。 (为了便于阅读,我将保护语句的每个子句放在单独的行中。)
【解决方案2】:

Matic 为您的问题提供了一个很好、全面的答案(已投票)。我想我会提供一个专注于你的崩溃的答案和一个“简短而甜蜜”的修复方法:

有问题的线路可能会以两种不同的方式崩溃:

limitLabel.text = limit[0].limitSum //Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value 

您的 limitLabel IBOutlet 被声明为“隐式展开的 Optional”(注意类型后面的 ! UILabel

@IBOutlet weak var limitLabel: UILabel!

一个隐式展开的 Optional 是一个 Optional,本质上,编译器添加了一个隐藏的“!”每次尝试引用该对象时强制展开。

也就是说

limitLabel.text = //something

编译为

limitLabel!.text = //something

如果 limitLabel 为 nil,你就会崩溃。

如果您在加载视图之前调用 leftLabels() 函数,或者该插座从未连接,您将崩溃。

您可以通过在语句中添加可选的 unwrap 来解决此问题:

limitLabel?.text = //something

(该结构称为“可选链接”。)

鉴于您收到的崩溃消息提到“隐式展开可选值”,这很可能是您的情况崩溃的原因。但是,您也应该解决其他问题。

第二种崩溃的方式是在数组索引中。

    limitLabel.text = limit[0].limitSum  

当您按索引从数组中获取对象时,如果该数组不包含该索引处的项目,您的应用程序将崩溃。如果limit 数组为空,则表达式limit[0] 将崩溃。

数组类型有一个计算属性first,如果数组为空,它将返回一个可选项。 你应该把它改成limit.first?.limitSum

将整行更改为:

    limitLabel?.text = limit.first()?.limitSum

而且它不会再崩溃了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多