简体   繁体   English

在 swift 和 realm 中展开可选值

[英]Unwrapping an Optional value in swift and realm

I wrote a working function for the application, but the error came out "The nil value was unexpectedly found when an optional value was implicitly deployed" limit Limit label.the text I can't fix.我为应用程序写了一个有效的 function,但出现错误“当隐式部署可选值时意外发现 nil 值”限制限制 label.文本我无法修复。

Properties:特性:

@IBOutlet weak var limitLabel: UILabel!

Function: Function:

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)" 

I will be glad if you tell me the correct code如果您告诉我正确的代码,我会很高兴

在此处输入图像描述

As from comments if appears that your view is not yet loaded and some of your views are still nil .从评论看来,您的视图尚未加载,并且您的某些视图仍为nil Your app crashes because in line limitLabel.text = limit[0].limitSum the limitLabel is nil .您的应用程序崩溃,因为在limitLabel.text = limit[0].limitSum行中limitLabelnil It would crash regardless of Realm even by calling limitLabel.text = "Hello world!"无论 Realm 即使调用limitLabel.text = "Hello world!"都会崩溃

You can always guard data that you need to avoid changes in your code.您始终可以保护您需要避免更改代码的数据。 Simply add只需添加

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

and so on.等等。

I tried to clean up your code a bit.我试着清理一下你的代码。 It is hard to understand what exactly are you trying to achieve but something like the following may seem a bit more appropriate:很难理解您到底想达到什么目标,但类似以下的内容可能看起来更合适:

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)
    }()
}

Note some practices which help you better to structure and solve your code.请注意一些有助于您更好地构建和解决代码的做法。

  • Guard dangerous data at first首先保护危险数据
  • Create a list of reusable items for your method (there should be as fewer as possible, in most cases none).为您的方法创建一个可重用项目的列表(应该尽可能少,在大多数情况下没有)。 Note how these can be later assigned to.请注意以后如何分配这些。 And if you try using it before assigning to it, you will be warned by your compiler.如果你在分配给它之前尝试使用它,你的编译器会警告你。
  • Wrap as much code into closed sections such as availableForSpendingLabel.text = {... code here... }()将尽可能多的代码包装到封闭的部分中,例如availableForSpendingLabel.text = {... code here... }()
  • Use tuples such as let dates: (start: Date?, end: Date?)使用元组,例如let dates: (start: Date?, end: Date?)
  • Don't be afraid of using long names such as availableForSpendingLabel不要害怕使用长名称,例如availableForSpendingLabel

I would even further try and break this down into multiple methods.我什至会进一步尝试将其分解为多种方法。 But I am not sure what this method does and assume that you have posted only part of it...但我不确定这种方法的作用,并假设您只发布了其中的一部分......

========== EDIT: Adding alternate approach ========== ========== 编辑:添加替代方法 ==========

From comments this is a financial application so probably at least dealing with Decimal numbers would make sense.从评论来看,这是一个金融应用程序,因此至少处理Decimal数字可能是有意义的。 Also introducing approach with adding a new structure which resolves data internally.还引入了添加一种在内部解析数据的新结构的方法。 A formatter is also used to format the number.格式化程序也用于格式化数字。 And some other improvements:以及其他一些改进:

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)
}

Matic provided a good, comprehensive answer to your question (voted). Matic 为您的问题提供了一个很好、全面的答案(已投票)。 I thought I'd provide an answer narrowly focused on your crash and a "short and sweet" way to fix it:我想我会提供一个专注于你的崩溃的答案和一个“简短而甜蜜”的解决方法:

The line in question could crash 2 different ways:有问题的线路可能会以两种不同的方式崩溃:

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

Your limitLabel IBOutlet is declared as an "implicitly unwrapped Optional" (Note the ! after the type, UILabel :您的limitLabel IBOutlet 被声明为“隐式解包 Optional”(注意!类型后的UILabel

@IBOutlet weak var limitLabel: UILabel!

An implicitly unwrapped Optional is an Optional where, essentially, the compiler adds a hidden "."一个隐式展开的 Optional 是一个 Optional,本质上,编译器添加了一个隐藏的“.”。 force-unwrap every time you try to reference that object.每次尝试引用该对象时强制展开。

That means that这意味着

limitLabel.text = //something

Is compiled as编译为

limitLabel!.text = //something

and if limitLabel is nil, you crash.如果limitLabel为nil,你就会崩溃。

If you call your leftLabels() function before your view has been loaded, or if that outlet is never connected, you will crash.如果您在加载视图之前调用leftLabels() function,或者该插座从未连接,您将崩溃。

You can fix that by adding an optional unwrap to the statement:您可以通过在语句中添加可选的 unwrap 来解决此问题:

limitLabel?.text = //something

(That construct is known as "optional chaining".) (该结构称为“可选链接”。)

Given that the crash message you're getting mentions "implicitly unwrapping an Optional value" it's likely that that is what is crashing in your case.鉴于您收到的崩溃消息提到“隐式展开可选值”,这很可能是您的情况崩溃的原因。 However, you should fix the other issue as well.但是,您也应该解决其他问题。

The second way you can crash is in your array indexing.第二种崩溃的方法是在数组索引中。

    limitLabel.text = limit[0].limitSum  

When you fetch an object from an array by index, your app will crash if the array does not contain an item at that index.当您按索引从数组中获取 object 时,如果该数组不包含该索引处的项目,您的应用程序将崩溃。 The expression limit[0] will crash if the limit array is empty.如果limit数组为空,表达式limit[0]将崩溃。

The array type has a computed property first that will return an optional if the array is empty.数组类型first有一个计算属性,如果数组为空,它将返回一个可选项。 You should change that to limit.first?.limitSum .您应该将其更改为limit.first?.limitSum

Change the whole line to be:将整行更改为:

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

And it won't crash any more.而且它不会再崩溃了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM