1
votes

I am currently testing out my cocoapod in my xcode project and am getting the following error when running my app in xcode.

"Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value"

I know it has something to do with unwrapping an optional value but I'm not exactly sure what the issue is. Any ideas what I'm doing wrong?

error being thrown when running my app

I've attached my .swift file for reference

import Foundation
import CoreLocation
import UserNotifications

open class Rule {
    public let notificationCenter = UNUserNotificationCenter.current()

    public let action: Action
    public let enabled: Bool
    public let id: String
    public let geofence: Geofence
    public let name: String
    public let trigger: Trigger
    public let locationManager: CLLocationManager = CLLocationManager()


    public init (ruleDict: NSDictionary) {
        self.action = Action(actionDict: ruleDict["action"] as! NSDictionary)
        self.enabled = ruleDict["enabled"] as! Bool
        self.id = ruleDict["id"] as! String
        self.geofence = Geofence(geofenceDict: ruleDict["geofence"] as! NSDictionary)
        self.name = ruleDict["name"] as! String
        self.trigger = Trigger(triggerDict: ruleDict["trigger"] as! NSDictionary)
    }

    open func initGeofence() {
        // do something
        let geofenceRegionCenter = self.geofence.center
        let geofenceRegion = CLCircularRegion(center: geofenceRegionCenter, radius: CLLocationDistance(self.geofence.radiusSize), identifier: self.id)
        geofenceRegion.notifyOnExit = true
        geofenceRegion.notifyOnEntry = true

        locationManager.startMonitoring(for: geofenceRegion)
    }

    open func scheduleNotification() {
        if (self.getActionType() == "notification") {
            let content = UNMutableNotificationContent()
            content.body = self.action.message!

            let center = self.geofence.center
            let region = CLCircularRegion(center: center, radius: self.geofence.radiusSize, identifier: self.id)

            switch self.getTriggerType() {
                case "enters":
                    region.notifyOnEntry = true
                    region.notifyOnExit = false
                case "exits":
                    region.notifyOnEntry = false
                    region.notifyOnExit = true
                case "returns":
                    region.notifyOnEntry = true
                    region.notifyOnExit = false
                default:
                    region.notifyOnEntry = true
                    region.notifyOnExit = false
            }

            let trigger = UNLocationNotificationTrigger(region: region, repeats: true)
            let request = UNNotificationRequest(identifier: self.id, content: content, trigger: trigger)

            notificationCenter.add(request) { (error) in
                if error != nil {
                    print("\(String(describing: error))")  // compiler warning patched
                } else {
                    print("Added notification to center")
                }
            }
        } else {
//            print("Not a notification")
        }

    }

    open func removeNotification() {
        notificationCenter.removePendingNotificationRequests(withIdentifiers: [self.id])
    }

    open func getActionType() -> String {
        return self.action.actionType.rawValue
    }

    open func getTriggerType() -> String {
        return self.trigger.triggerType.rawValue
    }

    open func userNotificationCenter(_ center: UNUserNotificationCenter,
                                willPresent notification: UNNotification,
                                withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        // Play sound and show alert to the user
        print("Showing notification")
        completionHandler([.alert,.sound])
    }

    open func triggerSegue(segueID: String) {
        // TODO: implement
        print("We need to trigger \(segueID)")
    }
}

open class Trigger {
    public enum triggerType: String {
        case enters
        case exits
        case dwells
        case returns
    }

    public let time: Int
    public let triggerType: triggerType

    public init (triggerDict: NSDictionary) { // time is 0 when not a dwell rule
        if let t = triggerDict["time"] as? Int {
            self.time = t
        } else {
            self.time = 0
        }

        let triggerTypeStr = triggerDict["triggerType"] as! String

        switch triggerTypeStr {
            case "enters":
                self.triggerType = .enters
            case "exits":
                self.triggerType = .exits
            case "dwells":
                self.triggerType = .dwells
            case "returns":
                self.triggerType = .returns
            default:
                self.triggerType = .enters
        }
    }
}

open class Geofence {
    public let center: CLLocationCoordinate2D
    public let radiusSize:Double

    public let geofenceType:geofenceType
    public enum geofenceType: String {
        case radius
        case polygon
    }

    public init(geofenceDict: NSDictionary) {
        let centerDict = geofenceDict["center"] as! NSDictionary

        let latStr = centerDict["lat"] as! String
        let lonStr = centerDict["lon"] as! String
        let lat = Double(latStr)!
        let lon = Double(lonStr)!
        self.center = CLLocationCoordinate2DMake(lat, lon)

        let radiusSize = geofenceDict["radiusSize"] as! Double
        self.radiusSize = radiusSize

        let geofenceTypeStr = geofenceDict["geofenceType"] as! String

        switch geofenceTypeStr { // polygon not supported yet
            case "radius":
                self.geofenceType = .radius
            default:
                self.geofenceType = .radius
            }
        }
}

open class Action {
    public let actionType: actionType
    public enum actionType: String {
        case notification
        case segue
        case monitor
        case http
    }

    public var message:String?
    public var segueID:String?

    public init(actionDict: NSDictionary) {
        let actionTypeStr = actionDict["actionType"] as! String

        switch actionTypeStr {
            case "notification":
                self.actionType = .notification
                self.message = (actionDict["message"] as! String)
            case "segue":
                self.actionType = .segue
                self.segueID = (actionDict["segueID"] as! String)
            case "monitor":
                self.actionType = .monitor
            case "http":
                self.actionType = .http
            default:
                self.actionType = .monitor
        }
    }
}

open class NotifyResponse {
    public let triggered:Bool
    public let message:String
    public var action:Action?
    public var httpResponse:NSDictionary?
    public var segueID:String?

    public init(responseDict: NSDictionary) {
        self.triggered = responseDict["triggered"] as! Bool
        self.message = responseDict["message"] as! String
        if (self.triggered) {
            if let actionDict = responseDict["action"] as? NSDictionary {
                self.action = Action(actionDict: actionDict)
                print("Type in constructor: \(self.getActionType())")
                if (self.getActionType() == "segue") {
                    self.segueID = action?.segueID
                }
            } else if let httpResponseDict = responseDict["httpResponse"] as? NSDictionary {
                self.httpResponse = httpResponseDict
            }
        }
    }

    open func getHTTPResponse() -> NSDictionary? {
        return self.httpResponse
    }

    open func getActionType() -> String {
        if let action = self.action {
            return action.actionType.rawValue
        } else {
            return ""
        }
    }
}

3

3 Answers

1
votes

You force unwrapping an optional value which is nil. That is why it is crashing. You should safely unwrap it instead.

if let actionDict = dict["action"] as? [String: Any] { 
    self.action = Action(actionDict: actionDict)
} else { 
    print("action is empty")
}

You should consider safely unwrapping for all other variables too.

I would suggest reading this : How to safely unwrap optional

0
votes

Optional value, would allow to store nil value, so when one try to class cast it and nil value found, it crashes your application.

Class/Object class can be done forcefully and safe one in Swift. You have done force downcast which is why it is crashing.

So modify your code as below,

 public init (ruleDict: NSDictionary) {
        if ruleDict != nil  {

            if let action =  Action(actionDict: ruleDict["action"] **as?** NSDictionary){
                self.action = action
            }

            if let enabled = ruleDict["enabled"] **as?**  Bool {
                self.enabled = enabled
            }

            if let id = ruleDict["id"] **as?** String {
                self.id = id
            }

            if let geofence = Geofence(geofenceDict: ruleDict["geofence"] **as?** NSDictionary) {
                self.geofence = geofence
            }

            if let name = ruleDict["name"] **as?** String {
                self.name = name
            }

            if let trigger = Trigger(triggerDict: ruleDict["trigger"] **as?** NSDictionary) {
                self.trigger = trigger
            }

        }
    }
0
votes

When the ruleDict doesn't have a key "action" it crashes

if let actionDict = ruleDict["action"] as? [String: Any] {
    self.action = Action(actionDict: actionDict)
}

Note

Don't use force cast

Don't use NSDictionary in Swift