I am retrieving posts in a data handler from a DB for a news feed and I run a php script and echo the JSON encoded data back to my application, at which point the data is parsed and stored in a model of a "post", there is a protocol that is used in the view controller to get the data once it has been downloaded. My problem is that I am getting the notorious "Unexpectedly found nil when unwrapping optional value" error when I pass the NSMutableArray of "post" objects to the function "itemsDownloaded" which is function of the protocol. I checked all the values being parsed and they exist, and I also checked the count of the array to make sure it has values. The exception is occurring on the line self.delegate.itemsDownloaded(posts)
The code to handle the data is this :
import Foundation
protocol PostDataHandlerProtocol: class {
func itemsDownloaded(items: NSArray)
}
class PostDataHandler: NSObject, NSURLSessionDataDelegate {
weak var delegate: PostDataHandlerProtocol!
var data : NSMutableData = NSMutableData()
//The path to the php script to be executed
let urlPath: String = "www.something.com/myphpscript.php"
func downloadItems() {
let url: NSURL = NSURL(string: urlPath)!
var session: NSURLSession!
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)
let task = session.dataTaskWithURL(url)
task.resume()
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
self.data.appendData(data);
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
if error != nil {
print("Failed to download data")
}else {
print("Data downloaded")
self.parseJSON()
}
}
func parseJSON() {
var jsonResult: NSMutableArray = NSMutableArray()
do{
jsonResult = try NSJSONSerialization.JSONObjectWithData(self.data, options:NSJSONReadingOptions.AllowFragments) as! NSMutableArray
} catch let error as NSError {
print(error)
}
var jsonElement: NSDictionary = NSDictionary()
let posts: NSMutableArray = NSMutableArray()
for(var i = 0; i < jsonResult.count; i++)
{
jsonElement = jsonResult[i] as! NSDictionary
let post = PostModel()
//The following insures none of the JsonElement values are nil through optional binding
let username = jsonElement["username"] as? String
let imagePath = jsonElement["user_imagePath"] as? String
let postID = (jsonElement["post_id"] as! NSString).integerValue
let postRep = (jsonElement["post_rep"] as! NSString).integerValue
let postType = jsonElement["post_type"] as? String
let postDate = jsonElement["post_date"] as? String
let comment = jsonElement["comment"] as? String
post.username = username
post.imagePath = imagePath
post.postID = postID
post.postRep = postRep
post.postType = postType
post.postDate = postDate
post.comment = comment
posts.addObject(post)
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.delegate.itemsDownloaded(posts)
})
}
}
In my view controller, I create a new data handler let postHandler = PostDataHandler() and then once the view has loaded I call postHandler.downloadItems() and in the view controller declaration I conformed to the protocol and implemented itemsDownloaded:
func itemsDownloaded(items: NSArray) {
allPosts = items as! [PostModel]
indicator.stopAnimating()
self.tableVIew.reloadData()
}
Does anyone know why this is happening? I tried to look at the numerous postings regarding this error as I'm aware it's quite common, but couldn't find anything that helped me. Many of the postings say there should be a check to ensure it's not nil, the problem is I didn't think NSMutableArray can be optional, and also I checked all the values and they don't appear to be nil, so those answers did not help me. In the thread exceptions it says something related to a closure, which I think could be causing the issue, I'm just not exactly sure what or how.
None of the code that you showed ever set your PostDataHandler's delegate property to anything, so it is reasonable to suppose that it is still nil, which perfectly explains why you crash at runtime when you try to access it as if it were an actual object.
Related
I'm getting this error on line var delegate = WeatherManagerDelegate()
import Foundation
protocol WeatherManagerDelegate {
func didUpdateWeather(weather:WeatherModel)
}
struct WeatherManager {
let weatherURL = "https://api.openweathermap.org/data/2.5/weather?&appid=d73ab8784f3b294976fc6189b3e6eba2&units=metric"
var delegate = WeatherManagerDelegate()
func fetchWeather(cityName: String) {
let urlString = "\(weatherURL)&q=\(cityName)"
performRequest(urlString: urlString)
}
func performRequest(urlString: String)
{
//Create URL
if let url = URL(string: urlString){
//Create a URL Session.
let session = URLSession(configuration: .default)
//Give session a task
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil{ //on selecting url 2nd option the seletors you get select from data onwards to error and press enter and it will be set to closure format.
print(error!)
return
}
if let safeData = data {
/* let dataString = String(data: safeData, encoding: .utf8)
print(dataString!)*/
if let weather = parseJSON(weatherData: safeData) {
self.delegate.didUpdateWeather(weather:weather)
}
}
}
//Start task
task.resume()
}
}
func parseJSON(weatherData: Data) ->WeatherModel? {
let decoder = JSONDecoder()
do{
let decodeData = try decoder.decode(WeatherData.self, from: weatherData)
let name = decodeData.name
let temp = decodeData.main.temp
print(decodeData.main.temp_max)
print(decodeData.main.temp_min)
print(decodeData.sys.country)
print(decodeData.weather[0].description)
let id = decodeData.weather[0].id
let weather = WeatherModel(conditionId: id, cityName: name, temperature: temp)
print(weather.conditionName)
print(weather.temperatureString)
}
catch{
print(error)
return nil
}
}
}
and when I'm trying to make it an optional
var delegate = WeatherManagerDelegate?()
I'm getting this error
No exact matches in call to initializer
Replace
var delegate = WeatherManagerDelegate()
with
weak var delegate: WeatherManagerDelegate?
and update the calls to read self.delegate?.didUpdateWeather()
WeatherManager should not be responsible for creating its own delegate, that is something that should come from wherever it is begin used.
The weak attribute is almost always necessary when using delegates to avoid retain cycles.
Since weak can only be applied to class objects, you also need to indicate that in the protocol definition:
protocol WeatherManagerDelegate: AnyObject { ... }
After upgrading to iOS10 users started complaining about crashes of my app.
I am testing it with iOS10 on the simulator and indeed the app crashes with a message saying "Could not cast value of type '__NSArrayI' to 'NSMutableArray'". Here's my code, please help:
import Foundation
protocol getAllListsModel: class {
func listsDownloadingComplete(downloadedLists: [ContactsList])
}
class ListsDownloader: NSObject, NSURLSessionDataDelegate{
//properties
weak var delegate: getAllListsModel!
var data : NSMutableData = NSMutableData()
func downloadLists() {
let urlPath: String = "http://..."
let url: NSURL = NSURL(string: urlPath)!
var session: NSURLSession!
let configuration = NSURLSessionConfiguration.ephemeralSessionConfiguration() //defaultSessionConfiguration()
session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)
let task = session.dataTaskWithURL(url)
task.resume()
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
self.data.appendData(data);
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
if error != nil {
print("Failed to download data")
}else {
self.parseJSON()
print("Lists downloaded")
}
}
func parseJSON() {
var jsonResult: NSMutableArray = NSMutableArray()
do{
try jsonResult = NSJSONSerialization.JSONObjectWithData(self.data, options:NSJSONReadingOptions.AllowFragments) as! NSMutableArray
} catch let error as NSError {
print(error)
}
var jsonElement: NSDictionary = NSDictionary()
var downloadedLists: [ContactsList] = []
for i in 0...jsonResult.count-1 {
jsonElement = jsonResult[i] as! NSDictionary
let tempContactsList = ContactsList()
//the following insures none of the JsonElement values are nil through optional binding
let id = jsonElement["id"] as? String
let name = jsonElement["name"] as? String
let pin = jsonElement["pin"] as? String
let lastUpdated = jsonElement["created"] as? String
let listAdminDeviceID = jsonElement["admin"] as? String
tempContactsList.id = id
tempContactsList.name = name
tempContactsList.pin = pin
tempContactsList.lastUpdated = lastUpdated
tempContactsList.listAdmin = listAdminDeviceID
downloadedLists.append(tempContactsList)
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.delegate.listsDownloadingComplete(downloadedLists)
})
}
}
Even in iOS 9, there was no guarantee NSJSONSerialization.JSONObjectWithData(_:options:) would return mutable object or not. You should have specified NSJSONReadingOptions.MutableContainers.
And in your code, you are not modifying jsonResult, which means you have no need to declare it as NSMutableArray. Just replace NSMutableArray to NSArray, and then you have no need to specify NSJSONReadingOptions.MutableContainers.
But as vadian is suggesting, you better use Swift types rather than NSArray or NSDictionary. This code should work both in iOS 9 and 10.
func parseJSON() {
var jsonResult: [[String: AnyObject]] = [] //<- use Swift type
do{
try jsonResult = NSJSONSerialization.JSONObjectWithData(self.data, options: []) as! [[String: AnyObject]] //<- convert to Swift type, no need to specify options
} catch let error as NSError {
print(error)
}
var downloadedLists: [ContactsList] = []
for jsonElement in jsonResult { //<- your for-in usage can be simplified
let tempContactsList = ContactsList()
//the following insures none of the JsonElement values are nil through optional binding
let id = jsonElement["id"] as? String
let name = jsonElement["name"] as? String
let pin = jsonElement["pin"] as? String
let lastUpdated = jsonElement["created"] as? String
let listAdminDeviceID = jsonElement["admin"] as? String
tempContactsList.id = id
tempContactsList.name = name
tempContactsList.pin = pin
tempContactsList.lastUpdated = lastUpdated
tempContactsList.listAdmin = listAdminDeviceID
downloadedLists.append(tempContactsList)
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.delegate.listsDownloadingComplete(downloadedLists)
})
}
Try this and check it on iOS 10 devices.
(The as! conversion would cause some weird crashes when your server is malfunctioning, but that would be another issue, so I keep it there.)
My friend and I are working on an app using git. We've encountered a strange problem, when the code runs on my computer but doesn't on hers. Here's the code:
import UIKit
class SelectionViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
//adding table
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
// HTTP POST
let myURL = NSURL(string: "http://.../drinks.php")
let request = NSMutableURLRequest(URL:myURL!)
request.HTTPMethod = "POST"
let postString = ""
print(postString)
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: {
(data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
dispatch_async(dispatch_get_main_queue())
{
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as? NSDictionary
if let parseJSON = json {
// Store the variables locally
NSUserDefaults.standardUserDefaults().setObject(parseJSON["Selection"], forKey: "items")
NSUserDefaults.standardUserDefaults().setObject(parseJSON["Details"], forKey: "details")
//NSUserDefaults.standardUserDefaults().synchronize()
}
} catch
{
print(error)
}
}
}).resume()
}
// Grab the values from the local storage and declare them locally
var details = (NSUserDefaults.standardUserDefaults().stringArrayForKey("details"))!
var items = (NSUserDefaults.standardUserDefaults().stringArrayForKey("items"))!
It breaks on the last two lines saying: fatal error: unexpectedly found nil while unwrapping an Optional value:
var details = (NSUserDefaults.standardUserDefaults().stringArrayForKey("details"))!
var items = (NSUserDefaults.standardUserDefaults().stringArrayForKey("items"))!
Any help will be highly appreciated!
I have a data downloader class that downloads data from mySQL server.
The problem I am having is when the result is null - the app crashes with error message
Could not cast value of type '__NSArray0' (0x105548918) to 'NSMutableArray' (0x105549c00).
class userDetailsDownloader: NSObject, NSURLSessionDataDelegate {
weak var delegate: getUserDetails!
var data : NSMutableData = NSMutableData()
func downloadUserDetails(userEmail: String) {
let urlPath: String = "http://intouchmobile.co/phpscripts/getuserpassword.php?email=\(userEmail)"
let url: NSURL = NSURL(string: urlPath)!
var session: NSURLSession!
let configuration = NSURLSessionConfiguration.ephemeralSessionConfiguration() //defaultSessionConfiguration()
session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)
let task = session.dataTaskWithURL(url)
task.resume()
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
self.data.appendData(data);
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
if error != nil
{
print("Failed to get password")
}
else
{
self.parseJSON()
print("Password Received")
}
}
func parseJSON() {
var jsonResult: NSMutableArray = NSMutableArray()
var password: String!
var firstName: String!
var lastName: String!
do{
jsonResult = try NSJSONSerialization.JSONObjectWithData(self.data, options:NSJSONReadingOptions.AllowFragments) as! NSMutableArray
} catch let error as NSError {
print(error)
}
}
Don't say as! NSMutableArray. The term as! means "crash me", so you can hardly be surprised when you do crash. Say as? NSMutableArray. Test the result for nil. If it is nil, stop.
if let jsonResult = try NSJSONSerialization.JSONObjectWithData(self.data, options:NSJSONReadingOptions.AllowFragments) as? NSMutableArray {
self.jsonResult = jsonResult
}
You may still have problems because this is never going to be a mutable array; it's just an array. So you might have to change it to:
if let jsonResult = try NSJSONSerialization.JSONObjectWithData(self.data, options:NSJSONReadingOptions.AllowFragments) as? NSArray {
self.jsonResult = NSMutableArray(array: jsonResult)
}
(But, as vadian has said in a comment, it would be even better if you could abandon use of NSArray and NSMutableArray and use Swift types instead.)
Try it like this.
if NSJSONSerialization.isValidJSONObject(data) {
//your do catch
}
That should solve your problem.
I have this same question as was asked and answered here: How to get data to return from NSURLSessionDataTask
The difference: How can I do this in Swift? I do not know Objective C at all so trying to interpret that answer is proving a bit futile for me..
So given the code below I have a FirstViewController which will call my HomeModel class to go and get the data using a NSURLSession call. Once the call is complete I want return the data to the FirstViewController so that I may go and set it in the view.
FirstViewController class looks like:
import UIKit
class FirstViewController: UIViewController
{
let homeModel = HomeModel()
override func viewDidLoad()
{
super.viewDidLoad()
// Go load the home page content
SetHomeContent()
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}
/*Call to go get home page content*/
func SetHomeContent()
{
homeModel.GetHomeData();
//TODO find a way to get result here... and set it to the textView
}
}
HomeModel class looks like:
import Foundation
class HomeModel
{
private let siteContent:String = "http://www.imoc.co.nz/MobileApp/HomeContent"
func GetHomeData()
{
var url : NSURL! = NSURL(string:siteContent)
var request: NSURLRequest = NSURLRequest(URL:url)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task : NSURLSessionDataTask = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
var error: AutoreleasingUnsafeMutablePointer<NSError?> = nil
let jsonResult: NSDictionary! = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: error) as? NSDictionary!
// At this stage I want the jsonResult to be returned to the FirstViewController
});
// do whatever you need with the task
task.resume()
}
I guess I want either a way to pass in a completion handler from the original all or something similar to C# 5 using async tasks to await the result..
Any help appreciated..
With the help and suggestion taken from Abizern I finally managed how to write up this block or closure if you want.
So what worked for me in the end:
The GetHomeData function I changed as follows:
private let siteContent:String = "http://www.imoc.co.nz/MobileApp/HomeContent"
// I changed the signiture from my original question
func GetHomeData(completionHandler: ((NSDictionary!) -> Void)?)
{
var url : NSURL! = NSURL(string:siteContent)
var request: NSURLRequest = NSURLRequest(URL:url)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task : NSURLSessionDataTask = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
var error: AutoreleasingUnsafeMutablePointer<NSError?> = nil
let jsonResult: NSDictionary! = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: error) as? NSDictionary!
// then on complete I call the completionHandler...
completionHandler?(jsonResult?);
});
task.resume()
}
Then I call the function like this:
/*Call to go get home page content*/
func SetHomeContent()
{
homeModel.GetHomeData(self.resultHandler)
}
func resultHandler(jsonResult:NSDictionary!)
{
let siteContent = jsonResult.objectForKey("SiteContent") as NSDictionary
let paraOne = siteContent.objectForKey("HomePageParagraphOne") as String
}
Change your GetHomeData() method so that it takes a block. When you call the method pass it the block that does what you want to with the data. In the completion block of the data task, call this passed in block.
You can use this framework for Swift coroutines - https://github.com/belozierov/SwiftCoroutine
DispatchQueue.main.startCoroutine {
let dataFuture = URLSession.shared.dataTaskFuture(for: url)
let data = try dataFuture.await().data
. . . parse data ...
}