Facing an issue with xcode.
I'm trying to develop an app that gives me the weather info. The build succeeds, but everytime I click on the console to check the output (to search, copy etc) xcode crashes.
The following text comes from the reporting tool to Apple,
Application Specific Information:
ProductBuildVersion: 7C1002
UNCAUGHT EXCEPTION (NSRangeException): -[__NSCFString characterAtIndex:]: Range or index out of bounds
UserInfo: (null)
Hints: None
Here's the code I'm running,
override func viewDidLoad() {
super.viewDidLoad()
let url = NSURL(string:"http://www.weather-forecast.com/locations/Hyderabad/forecasts/latest")
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) { (data, response, error) -> Void in
if let webContent = data {
let decodedContent = NSString(data: webContent, encoding: NSUTF8StringEncoding)
//print(decodedContent)
let weatherSiteSourceArray = decodedContent?.componentsSeparatedByString("3 Day Weather Forecast Summary:</b><span class=\"read-more-small\"><span class=\"read-more-content\"> <span class=\"phrase\">")
print(weatherSiteSourceArray)
// if weatherSiteSourceArray?.count > 0 {
//
// let weatherInfo = weatherSiteSourceArray![1]
// print(weatherInfo)
// }
}
}
task.resume()
// Do any additional setup after loading the view, typically from a nib.
}
though the report tool informs me UNCAUGHT EXCEPTION (NSRangeException): -[__NSCFString characterAtIndex:]: Range or index out of bounds.
I am unable to figure out where this is happening.
From what i have learned you can print an array in Swift using the print(items: Any...) method.
help on this would be greatly appreciated !
You are checking is there any element in your array and then you are trying to read second element. There are chances that your array is having only one item in it and hence when you are choosing weatherSiteSourceArray![1], it fails as accessing element 2 is out of bound of array.
if weatherSiteSourceArray?.count > 0 {
let weatherInfo = weatherSiteSourceArray![1]
Either check for
if weatherSiteSourceArray?.count > 1
or use first element i.e.
let weatherInfo = weatherSiteSourceArray![0]
I have no idea of swift and assume it uses zero based index for array.
A good place to start would be to figure out exactly which line is causing the crash. Try commenting out these three lines:
let decodedContent = NSString(data: webContent, encoding: NSUTF8StringEncoding)
//print(decodedContent)
let weatherSiteSourceArray = decodedContent?.componentsSeparatedByString("3 Day Weather Forecast Summary:</b><span class=\"read-more-small\"><span class=\"read-more-content\"> <span class=\"phrase\">")
print(weatherSiteSourceArray)
And if you code doesn't crash after that, then try commenting in you code one line at the time until you have the line crashing.
Another thing:
Looking at your code I'm guessing that maybe this line:
let weatherSiteSourceArray = decodedContent?.componentsSeparatedByString("3 Day Weather Forecast Summary:</b><span class=\"read-more-small\"><span class=\"read-more-content\"> <span class=\"phrase\">")
might be the one causing your problems. Its a bit risky and brittle to try separating your content based on an HTML string like that. The second that string changes (and it will :-)), you've lost.
A better way would be to receive the data in pure JSON or XML if they have an API providing that.
Just a thought :-)
Related
I've got a released app using Realm and there are some crash logs showing that there is sometimes a failure to create the realm with a configuration resulting in a EXC_BREAKPOINT (SIGTRAP) crash. (there's 9 crash files for a few hundred app installations so its not something which is happening frequently)
#objc class Database : NSObject
{
let configuration = Realm.Configuration(encryptionKey: Database.getKey() as Data)
var transactionRealm:Realm? = nil
override init()
{
let realm = try! Realm(configuration: configuration) // Crash here
<snip>
}
// This getKey() method is taken from the Realm website
class func getKey() -> NSData {
let keychainIdentifier = "Realm.EncryptionKey.AppKey"
let keychainIdentifierData = keychainIdentifier.data(using: String.Encoding.utf8, allowLossyConversion: false)!
// First check in the keychain for an existing key
var query: [NSString: AnyObject] = [
kSecClass: kSecClassKey,
kSecAttrApplicationTag: keychainIdentifierData as AnyObject,
kSecAttrKeySizeInBits: 512 as AnyObject,
kSecReturnData: true as AnyObject
]
// To avoid Swift optimization bug, should use withUnsafeMutablePointer() function to retrieve the keychain item
// See also: http://stackoverflow.com/questions/24145838/querying-ios-keychain-using-swift/27721328#27721328
var dataTypeRef: AnyObject?
var status = withUnsafeMutablePointer(to: &dataTypeRef) { SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) }
if status == errSecSuccess {
return dataTypeRef as! NSData
}
// No pre-existing key from this application, so generate a new one
let keyData = NSMutableData(length: 64)!
let result = SecRandomCopyBytes(kSecRandomDefault, 64, keyData.mutableBytes.bindMemory(to: UInt8.self, capacity: 64))
assert(result == 0, "REALM - Failed to get random bytes")
// Store the key in the keychain
query = [
kSecClass: kSecClassKey,
kSecAttrApplicationTag: keychainIdentifierData as AnyObject,
kSecAttrKeySizeInBits: 512 as AnyObject,
kSecValueData: keyData,
kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlock
]
status = SecItemAdd(query as CFDictionary, nil)
assert(status == errSecSuccess, "REALM - Failed to insert the new key in the keychain")
return keyData
}
Here's the relevant portion of the crash file:
Exception Type: EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x0000000103c0f30c
Termination Signal: Trace/BPT trap: 5
Termination Reason: Namespace SIGNAL, Code 0x5
Terminating Process: exc handler [0]
Triggered by Thread: 0
Thread 0 name:
Thread 0 Crashed:
0 libswiftCore.dylib 0x0000000103c0f30c 0x103ab4000 + 1422092
1 libswiftCore.dylib 0x0000000103c0f30c 0x103ab4000 + 1422092
2 libswiftCore.dylib 0x0000000103b13d2c 0x103ab4000 + 392492
3 libswiftCore.dylib 0x0000000103b13bf4 0x103ab4000 + 392180
4 My app 0x000000010334ff14 _TFC14Caller_Name_ID8DatabasecfT_S0_ + 1648 (Database.swift:21)
5 My app 0x0000000103330624 -[Model createDatabase] + 200 (Model.m:271)
Line 21 of the Database.swift file is the one indicated in the code above in the Init() method.
If the reason for the Realm crash is because getKey() fails, then why would that be failing? If thats not the cause, that what are other reasons for the failure?
And is there anything the code can do (such as retry to create the Realm object) if there is a failure?
The init method
I'm going to get this out of the way first: It's unlikely that this has anything to do with your crash in this instance, but when you override any init method, you should always call super's version of that init method unless there is no super class or no available superclass implementation to call. In Objective-C you assign the results of [super init] to self, however, this pattern was not adopted by swift and it is unclear, from Apple's documentation, what happens when you call super.init() from a direct NSObject subclass in Swift. I'm running out of steam at this hour, thought I did spend some time looking at the apple/swift GitHub repository Apple/Swift Github among other place and was unable to find any really satisfying information about calling NSObject init from a Swift subclass. I would definitely encourage you to continue the search if you really want to know more and not just take my word for it. Until then, it is highly unlikely that it will cause issues if you call NSObject's init() from a Swift subclass and it just might save your butt at some point too! ;)
The crash
If the reason for the Realm crash is because getKey() fails, then why would that be failing?
I'm not sure why getKey() might be failing, but it's unlikely this is the cause of your crash. I believe default values for properties are available by the time code inside init() is called in which case your app would crash before it reached that call inside init(). I will do a little more research on this tomorrow.
If thats not the cause, that what are other reasons for the failure?
I've looked at the Realm API you are using there and it's unclear why the call to Realm(configuration:) is failing although clearly that's expected to be possible since the call can throw. However, the documentation doesn't state the conditions under which it will throw.
And is there anything the code can do (such as retry to create the Realm object) if there is a failure?
Yes! Fortunately, the "try" mechanism has other variation than just "try!". Actually, in this case, the reason the app appears to be crashing is that you are using try! which in production code should only be used in situations where you know the call is extremely unlikely to ever fail such as retrieving a resource your app ships with from inside it's app package. Per Apple's documentation:
Sometimes you know a throwing function or method won’t, in fact, throw an error at runtime. On those occasions, you can write try! before the expression to disable error propagation and wrap the call in a runtime assertion that no error will be thrown. If an error actually is thrown, you’ll get a runtime error. Swift Error Handling Documentation
When it comes to impacting the user experience, I always like to err on the side of caution so I would be highly surprised to find even one occurrence of try! in any of my code (even Swift playground toys and experiments).
In order to fail gracefully and retry or take another course of action your code either needs to use try? which will convert the thrown error to an optional as in:
if let realm = try? Realm(configuration: configuration) {
// Do something with realm and ignore the error
}
Or you can use a full try-catch mechanism like so:
let realm: Realm? = nil
do {
realm = try Realm(configuration: configuration)
// do something with realm
} catch let e {
realm = nil
// do something with the error
Swift.print(e)
}
Or alternatively:
do {
let realm = try Realm(configuration: configuration)
// do something with realm
} catch let e {
// do something with the error
Swift.print(e)
}
So this may not be your golden ticket to never having this Realm call fail, but I hope it provides some help towards making your code a little more robust. I apologize for any errors, it's getting late for me here. Good luck and cheers! :)
I have experiencing very strange crash from iOS App. The function below is an implementation of some protocol so I cannot change its declaration to use some success/failure callback. It has input parameters and expects AVAsset at the output. My problem is during writing asset I get strange crash during leaving dispatch group (dg variable). I marked line of the crash with comment. This crash is not always happens. Just from time to time. This is the function:
func writeAsset(to url: URL, metadataArray: [AVTimedMetadataGroup]) -> AVAsset {
let writer = try! AVAssetWriter(url: url, fileType: AVFileTypeQuickTimeMovie)
writer.movieTimeScale = track.timeScale
// setup writer, inputs and metadata adaptor and so on ...
if writer.startWriting() {
writer.startSession(atSourceTime: kCMTimeZero)
}
let writeQueue = DispatchQueue(label: "HH.Write.Track.Queue")
let dg = DispatchGroup()
var i = 0
dg.enter() // Entering to the group
writerMetadataIn.requestMediaDataWhenReady(on: writeQueue) {
while writerMetadataIn.isReadyForMoreMediaData {
//let group = ..fetch next group to write
if i < metadataArray.count {
let group = metadataArray[i]
if writerMetadataAdaptor.append(group) {
}
i += 1
} else {
writerMetadataIn.markAsFinished()
writer.finishWriting {
dg.leave() // CRASH IN THIS LINE
}
break
}
}
}
dg.wait()
let writtenAsset = AVAsset(url: url)
return writtenAsset
}
Can somebody have idea what is the cause of this crash? I have only this information from crash report in xCode.
I suspect your issue is that since you are entering the dispatch group once, and then (sometimes) leaving it more than once inside the loop, that you do not have balanced calls. ie. you are calling leave more times than you have called enter.
Found solution for the problem. It was not related to DispatchGroup but with AVAssetWriter and input array of AVTimedMetadataGroup elements. Each of this elements has time range. If start times for two of them is identical then writter during appending this groups is going to be in error state and behavior is very unpredictible. I don't know why error was in this line during leaving group but solution for me was to detect groups with the same start times and skip them.
I'm trying to transfer an array of MPMediaItem(s) to another device using MultipeerConnectivity, so I can show a list (TableView) of songs and therefore remote control the peer.
This piece of code encodes my music library items so I can send them to another peer.
func encodeLibrary(lib: [MPMediaItem]) -> NSData {
print("before encoding: \(lib.count)")
// --> prints "before encoding: 511"
let data = NSMutableData()
let archiver = NSKeyedArchiver.init(forWritingWithMutableData: data)
archiver.encodeObject(lib, forKey: "data")
archiver.finishEncoding()
let unarchiver = NSKeyedUnarchiver.init(forReadingWithData: data)
let newLib = unarchiver.decodeObjectForKey("data") as! [MPMediaItem]
print("decoded: \(newLib.count)")
// --> prints "decoded: 511"
return data
}
The following code is then executed on another peer:
func decodeLibrary(data: NSData) -> [MPMediaItem] {
let unarchiver = NSKeyedUnarchiver.init(forReadingWithData: data)
let lib = unarchiver.decodeObjectForKey("data") as! [MPMediaItem]
print("items: \(lib.count)")
//prints "items: 0"
return lib
}
To send the data I use the following call:
try! session.sendData(data, toPeers: [peerID], withMode: .Reliable)
It's not a problem with the en-/decoding because it works when i run the decoding on the same device right after the encoding as you can see. It prints 511 songs before and after.
There has to be a problem while being transmitted or anything I can't think of why.
When data arrives on another device, everything except these MPMediaItems is available as well.
I do not receive any errors and other parts of the communication are working fine. Just this one array does not seem to be available on other devices. Any idea how to fix this?
Thanks in advance,
KlixxOne
EDIT: Actually the array is there, but it has no content (which it had on the other device (511 entries)).
I am using some Facebook IDs in my app, and I have an array of serveral ID's, the array can be 10 numbers but can also be 500 numbers..
Right now the numbers are displayed in a tableview, and I want all the results there too, so they need to be in an array.
let profileUrl = NSURL(string:"http://www.facebook.com/" + newArray[0])!
let task = NSURLSession.sharedSession().dataTaskWithURL(profileUrl) {
(data, response, error) -> Void in
// Will happen when task completes
if let urlContent = data {
let webContent = NSString(data: urlContent, encoding: NSUTF8StringEncoding)
dispatch_async(dispatch_get_main_queue(),
{ () -> Void in
let websiteArray = webContent!.componentsSeparatedByString("pageTitle\">")
//print(websiteArray[1])
let secondArray = websiteArray[1].componentsSeparatedByString("</title>")
print(secondArray[0])
})
}
}
this code takes the first number of the array, goes to facebook.com/[the actual number], and then downloads the data and splits the data into pieces, so that the data that I want it in the secondArray[0]. I want to do this for every number of the array, take the result data and put it back into an array. I have no idea how to do this because you don't know how much numbers there are gonna be etc, does someone has a good solution for this?
Any help would be appreciated, really!
Thanks
You have several problems here, and you should take them one at at a time to build up to your solution.
First, forget the table for the moment. Don't worry at all about how you're going to display these results. Just focus on getting the results in a simple form, and then you'll go back and convert that simple form into something easy to display, and then you'll display it.
So first, we want this in a simple form. That's a little bit complicated because it's all asynchronous. But that's not too hard to fix.
func fetchTitle(identifier: String, completion: (title: String) -> Void) {
let profileUrl = NSURL(string:"http://www.facebook.com/" + identifier)!
let task = NSURLSession.sharedSession().dataTaskWithURL(profileUrl) {
(data, response, error) -> Void in
if let urlContent = data {
let webContent = NSString(data: urlContent, encoding: NSUTF8StringEncoding)
let websiteArray = webContent!.componentsSeparatedByString("pageTitle\">")
let secondArray = websiteArray[1].componentsSeparatedByString("</title>")
let title = secondArray[0]
completion(title: title)
}
}
task.resume()
}
Now this is still pretty bad code because it doesn't handle errors at all, but it's a starting point, and the most important parts are here. A function that takes a string, and when it's done fetching things, calls some completion handler.
(Regarding error handling, note how many places this code would crash if it were returned surprising data. Maybe the data you get isn't a proper string. Maybe it's not formatted like you think it is. Every time you use ! or subscript an array, you run the risk of crashing. Try to minimize those.)
So you might then wrap it up in something like:
var titles = [String]()
let identifiers = ["1","2","3"]
let queue = dispatch_queue_create("titles", DISPATCH_QUEUE_SERIAL)
dispatch_apply(identifiers.count, queue) { index in
let identifier = identifiers[index]
fetchTitle(identifier) { title in
dispatch_async(queue) {
titles.append(title)
}
}
}
This is just code to get you on the right track and start studying the right things. It certainly would need work to be production quality (particularly to handle errors).
Once you have something that returns your titles correctly, you should be able to write a program that does nothing but take a list of identifiers and prints out the list of titles. Then you can add code to integrate that list into your tableview. Keep the parts separate. The titles are the Model. The table is the View. Read up on the Model-View-Controller paradigm, and you'll be in good shape.
To repeat code for whole array put your code in a loop and run that loop from 0 to array.count-1
You don't need to know how many items there will be an array. You can just get the count at run time array.count here array is your array.
I hope this is what you wanted to know, your question doesn't make much sense though.
I am trying out Swift with some of the new iOS 8 API's and have tried the best part of the day to get the CMPedometer queryPedometerDataFromDate API to return any data within the handler. I believe its an error on my part, getting a little confused with the syntax.
Here is my code, with comments on what prints out:
var ped = CMPedometer()
var stepsTaken = NSNumber(int: 0)
println(dateNow) // 2014-06-07 21:23:55 +0000
println(dateMidnight) // 2014-06-07 00:00:00 +0000
ped.queryPedometerDataFromDate(dateMidnight, toDate: dateNow, withHandler:{
data, error in
println("Test1") // Does not print
println(error) // Does not print
stepsTaken = data.numberOfSteps
})
println("My Int Value \(stepsTaken)") // My Int Value 0
It works for me with CMPedometer queryPedometerDataFromDate, if i define the CMPedometer as a class wide constant:
let pedometer = CMPedometer()
and then in a func i'm using:
self.pedometer.queryPedometerDataFromDate(today, toDate: now, withHandler: ...
A code sample from Hipster.
import CoreMotion
let lengthFormatter = NSLengthFormatter()
let pedometer = CMPedometer()
pedometer.startPedometerUpdatesFromDate(NSDate(), withHandler: { data, error in
if !error {
println("Steps Taken: \(data.numberOfSteps)")
var distance = data.distance.doubleValue
println("Distance: \(lengthFormatter.stringFromMeters(distance))")
var time = data.endDate.timeIntervalSinceDate(data.startDate)
var speed = distance / time
println("Speed: \(lengthFormatter.stringFromMeters(speed)) / s")
}
})
After many hours playing around with the syntax, thinking I have made a rookie error, I tried alternative API's and found that the following works to grab pedometer data:
var ped2 = CMStepCounter()
ped2.queryStepCountStartingFrom(dateMidnight, to: dateNow, toQueue: NSOperationQueue(), withHandler:{data, error in
println("Test 2") // "Test 2"
println(data) // 491 (I really should go for a walk!)
println(error) // nil
})
I will file a radar with Apple as it looks like a bug in the new API. Also the CMStepCounter class is due to be deprecated for CMPedometer. From CMStepCounter.h:
NS_CLASS_DEPRECATED_IOS(7_0,8_0,"Use CMPedometer instead")
You're querying the data, which goes onto another thread. The execution continues after your call, and your NSNumber is still at 0. By the time you've come back you've printed the message a long time ago, and have no way of recovering the stepsTaken.
You need to keep a reference to your ped variable. Something like:
self.ped = CMPedometer();
Currently your ped variable is going out of scope before the handler is even called.