I am facing a problem which must be deep in iOS8 - or I have overseen some
obvious problem. Any help is welcome.
Description:
In have created a JobTableViewController, which is displaying a list of jobs (filenames) and their job status in a table as
label and detail label.
If the user selects one entry more details of this job are displayed in the next controllers and can be changed on the following pages. On the return
to this controller the job status should be updated and displayed in the detail label. I have tested the app in different simulators and it is working as expected.
But if I run the app on my iPhone, it crashes with
EXC_BAD_ACCESS(code 1, address =… ) // the address is changing on every run
while it is accessing m_jobListStatus. If have added a println() statement, which is printing fine. The app is always crashing on the second return to this controller.
class JobTableViewController: UITableViewController {
// some private vars here
private var m_jobListStatus = [String:String]()
override func viewDidLoad() {
…
if let s = m_defaults.dictionaryForKey(CONSTANTS.JOB_LIST_STATUS) as? [String:String] {
m_jobListStatus = s
}
}
…
// update the status in the table, save the status in the system defaults file and save the changes in file
override func viewDidAppear(animated: Bool) {
if let file = m_filename {
println("\nin ViewDidAppear m_jobListStatus = \(m_jobListStatus) , \(checkJobStatus(file)), \(file)")
self.m_jobListStatus[file] = checkJobStatus(file) // < < < < - - - - - - C r a s h is here
m_defaults.setObject(m_jobListStatus, forKey: CONSTANTS.JOB_LIST_STATUS)
tableView.reloadData()
var json = MeterReading.saveToJSON(m_readingList)
json?.writeToFile(CONSTANTS.DOCS_DIR + file, atomically: true)
m_filename = nil
}
}
…
}
The output of the println looks as expected:
in ViewDidAppear m_jobListStatus =
[Auftrag-2015-03-01_12.17.39.meters: In Bearbeitung,
Auftrag-2015-02-27_20.36.33.meters: Neu] , Neu,
Auftrag-2015-02-27_20.36.33.meters
in ViewDidAppear m_jobListStatus =
[Auftrag-2015-03-01_12.17.39.meters: In Bearbeitung,
Auftrag-2015-02-27_20.36.33.meters: Neu] , In Bearbeitung,
Auftrag-2015-03-01_12.17.39.meters
Any ideas?
Thanks in advance
Found the problem. It looks like that
NSUserDefaults.setObject(m_jobListStatus, forKey: CONSTANTS.JOB_LIST_STATUS)
is the cause. It looks like it is turning m_jobListStatus to immutable.
Related
I've done a call kit + twilio IOS app. The problem is (as far as I can tell) with ios 12.
When I run the app on a device with IOS 11 the call start as normal. When I run the app on a device with IOS 12, when I try to make the first call I get this error :
StartCallAction transaction request failed: The operation couldn’t be
completed. (com.apple.CallKit.error.requesttransaction error 7.)
This error represent this: The requested transaction contains actions that, if performed, would exceed the maximum number of call groups for the provider. But i set the callGroupMax number to 1 ( I tried to set it 2,3 but still the same)
I found just one thread with this error on google but no solution was provided. Pleas give me a hint on what causes this error because I'm stuck on this.
This error appears only when the first call is made after a fresh install. Then I can make calls as it was intended.
This is the callkitManager class:
class CallKitManager: NSObject {
class var shared: CallKitManager {
struct Static {
static let instance: CallKitManager = CallKitManager()
}
return Static.instance
}
fileprivate let callKitProvider: CXProvider
override init() {
callKitProvider = CXProvider(configuration: type(of: self).providerConfiguration)
super.init()
callKitProvider.setDelegate(self, queue: nil)
}
static var providerConfiguration: CXProviderConfiguration {
let localizedName = NSLocalizedString("NAME", comment: "Name of application")
let configuration = CXProviderConfiguration(localizedName: localizedName)
configuration.supportsVideo = false
configuration.maximumCallsPerCallGroup = 1
configuration.ringtoneSound = "myringtone"
configuration.supportedHandleTypes = [.generic]
if let callKitIcon = UIImage(named: "callKitIcon") {
configuration.iconTemplateImageData = callKitIcon.pngData()
}
return configuration
}
I expect that the call to connect from the first time, but the result is that in the performStartCallAction() method I get the error from above.
So after 2 days I figure it out. The problem was that I use the callKit as a singleton which is wrong. You need "to mimic" a singleton using AppDelegate. See this tutorial https://www.raywenderlich.com/701-callkit-tutorial-for-ios and look in the AppDelegate and se how this was implemented.
I created a brand new single view application and added one line to the viewDidLoad method of the ViewController.swift file:
override func viewDidLoad() {
super.viewDidLoad()
_ = NSLocale.current.description
}
The NSLocale.current.description line crashes in Xcode 9 with no stack trace (just a EXC_BAD_ACCESS code=EXC_I386_GPFLT error message). The same project runs fine in Xcode 8.3.3. Anyone have any ideas why this is happening?
Here is my simulator region settings:
Based on the stack trace, it looks like it's trying to treat description as an ObjC property rather than a Swift property. Based on the source code, this shouldn't be happening. It's likely a bug in the latest Swift compiler that is producing the Swift libraries because it crashes on an iOS 9 device as well.
Be sure to file a bug with Swift since this seems to be a language bug. I've verified that it is still broken in the latest Swift 4 toolchain. In the meantime, you can get the same behavior of description using your own extension by simply duplicating the intended implementation like I've shown here.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// let brokenDescription = NSLocale.current.description
// let otherBrokenDescription = Locale.current.description
let objcDescription = (Locale.current as NSLocale).debugDescription //"<__NSCFLocale: 0x1c00dbc10> \'en_US\'}"
let myDescription = Locale.current.myDescription // "en_US (current)"
}
}
extension Locale {
private var _kindDescription : String {
if self == Locale.autoupdatingCurrent {
return "autoupdatingCurrent"
} else if self == Locale.current {
return "current"
} else {
return "fixed"
}
}
public var myDescription: String {
return "\(identifier) (\(_kindDescription))"
}
public var myDebugDescription : String {
return "\(identifier) (\(_kindDescription))"
}
}
This was a bug fixed in Xcode 9 Beta 5.
I started getting crash reports for the sort lamdba in the below code, the third line in the grey box below:
private func fixOverlaps(inout blocks: [TimeBlock], maxOverlaps: Int? = nil) {
blocks.sortInPlace { a,b in
if a.startTime < b.startTime {
return true
} else if a.startTime == b.startTime && a.endTime < b.endTime {
return true
}
return false
}
...
Note the crash does not occur on debug builds from XCode. Only the App Store and Ad Hoc archives will crash, and only when the length of the blocks list is in the hundreds.
I modified the code to this, and the problem went away:
private func fixOverlaps(inout blocks: [TimeBlock], maxOverlaps: Int? = nil) {
blocks = blocks.sort { a,b in
if a.startTime < b.startTime {
return true
} else if a.startTime == b.startTime && a.endTime < b.endTime {
return true
}
return false
}
...
Is there something I've missed about how to use inout or sortInPlace? I can try to do a demo of this. It's on multiple versions of iOS (8/9) and Swift 2.1.
EDIT--------------------
Ok here's a minimal version that crashes. Turns out the inout was a red herring. If you start a new single view project in XCode 7.1, you can replace the view controller with this:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var blocks = [TimeBlock]()
for var i in 0...20 { //Works if you put in a small number like 8
let t = TimeBlock()
t.start = Int(arc4random_uniform(1000)) //Get some random numbers so the sort has to do some work
t.end = Int(arc4random_uniform(1000))
blocks.append(t)
}
blocks.sortInPlace { a,b in
if a.start > b.start {
return true
}
return false
}
print("done") //Gets here on debug, not release
}
class TimeBlock {
var start = 0
var end = 0
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
So run it in release, and you should see it prints "Done" if you end the loop at around 17 but crashes with 20. Exact number might be different for you.
Worked around this without loss of functionality by using self.list = self.list.sort() instead of self.list.sortInPlace().
This is a bug in Xcode 7.1 . Turning the swift compiler optimization level from fast to none fixed this problem for me.
This code looks correct. It sounds like you've run across a bug in the compiler, which is generally the case when you have a crash in release configuration but not debug. You can perhaps verify this by enabling optimizations in your debug build and testing to see if it generates the problem. Aside from your workaround, the only other thing you need to do is file a bug.
I logged a bug on bugs.swift.org about this earlier today and received a prompt response from one of the developers that this is indeed an issue with Xcode 7.1. He pointed out that its resolution is outlined in the Xcode 7.2 Release Notes:
A bug in the optimizer has been fixed that caused in-place sort on mutable collections to crash. (23081349)
So using Xcode 7.2 to compile should also fix the issue.
I started getting crash reports for the sort lamdba in the below code, the third line in the grey box below:
private func fixOverlaps(inout blocks: [TimeBlock], maxOverlaps: Int? = nil) {
blocks.sortInPlace { a,b in
if a.startTime < b.startTime {
return true
} else if a.startTime == b.startTime && a.endTime < b.endTime {
return true
}
return false
}
...
Note the crash does not occur on debug builds from XCode. Only the App Store and Ad Hoc archives will crash, and only when the length of the blocks list is in the hundreds.
I modified the code to this, and the problem went away:
private func fixOverlaps(inout blocks: [TimeBlock], maxOverlaps: Int? = nil) {
blocks = blocks.sort { a,b in
if a.startTime < b.startTime {
return true
} else if a.startTime == b.startTime && a.endTime < b.endTime {
return true
}
return false
}
...
Is there something I've missed about how to use inout or sortInPlace? I can try to do a demo of this. It's on multiple versions of iOS (8/9) and Swift 2.1.
EDIT--------------------
Ok here's a minimal version that crashes. Turns out the inout was a red herring. If you start a new single view project in XCode 7.1, you can replace the view controller with this:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var blocks = [TimeBlock]()
for var i in 0...20 { //Works if you put in a small number like 8
let t = TimeBlock()
t.start = Int(arc4random_uniform(1000)) //Get some random numbers so the sort has to do some work
t.end = Int(arc4random_uniform(1000))
blocks.append(t)
}
blocks.sortInPlace { a,b in
if a.start > b.start {
return true
}
return false
}
print("done") //Gets here on debug, not release
}
class TimeBlock {
var start = 0
var end = 0
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
So run it in release, and you should see it prints "Done" if you end the loop at around 17 but crashes with 20. Exact number might be different for you.
Worked around this without loss of functionality by using self.list = self.list.sort() instead of self.list.sortInPlace().
This is a bug in Xcode 7.1 . Turning the swift compiler optimization level from fast to none fixed this problem for me.
This code looks correct. It sounds like you've run across a bug in the compiler, which is generally the case when you have a crash in release configuration but not debug. You can perhaps verify this by enabling optimizations in your debug build and testing to see if it generates the problem. Aside from your workaround, the only other thing you need to do is file a bug.
I logged a bug on bugs.swift.org about this earlier today and received a prompt response from one of the developers that this is indeed an issue with Xcode 7.1. He pointed out that its resolution is outlined in the Xcode 7.2 Release Notes:
A bug in the optimizer has been fixed that caused in-place sort on mutable collections to crash. (23081349)
So using Xcode 7.2 to compile should also fix the issue.
Context
I've been playing around with Today Extensions using this example project.
The app is quite simple:
In the containing app, you have a list of todo items, which you can mark completed
In the Today widget, you see the same list, but you can switch between completed, and incomplete items using a segmented control.
My goal is the following: whenever there is a data change, either in the container app, or the widget, I want both to reflect the changes:
If I mark an item as completed in the container app, then pull down the Notification Center, the widget should be updated
When I do the same in the widget, then return to the app, the app's state should be updated
The implementation
I understand, that the container app, and the extension run in their separate processes, which means two constraints:
NSUserDefaultsDidChangeNotification is useless.
Managing the model instances in memory is useless.
I also know, that in order to access a shared container, both targets must opt-in to the App Groups entitlements under the same group Id.
The data access is managed by an embedded framework, TodoKit. Instead of keeping properties in memory, it goes straight to NSUserDefaults for the appropriate values:
public struct ShoppingItemStore: ShoppingStoreType {
private let defaultItems = [
ShoppingItem(name: "Coffee"),
ShoppingItem(name: "Banana"),
]
private let defaults = NSUserDefaults(suiteName: appGroupId)
public init() {}
public func items() -> [ShoppingItem] {
if let loaded = loadItems() {
return loaded
} else {
return defaultItems
}
}
public func toggleItem(item: ShoppingItem) {
let initial = items()
let updated = initial.map { original -> ShoppingItem in
return original == item ?
ShoppingItem(name: original.name, status: !original.status) : original
}
saveItems(updated)
}
private func saveItems(items: [ShoppingItem]) {
let boxedItems = items.map { item -> [String : Bool] in
return [item.name : item.status]
}
defaults?.setValue(boxedItems, forKey: savedDataKey)
defaults?.synchronize()
}
private func loadItems() -> [ShoppingItem]? {
if let loaded = defaults?.valueForKey(savedDataKey) as? [[String : Bool]] {
let unboxed = loaded.map { dict -> ShoppingItem in
return ShoppingItem(name: dict.keys.first!, status: dict.values.first!)
}
return unboxed
}
return nil
}
}
The problem
Here's what works:
When I modify the list in my main app, then stop the simulator, and then launch the Today target from Xcode, it reflects the correct state. This is true vice-versa.
This verifies, that my app group is set up correctly.
However, when I change something in the main app, then pull down the Notification Center, it is completely out of sync. And this is the part, which I don't understand.
My views get their data straight from the shared container. Whenever a change happens, I immediately update the data in the shared container.
What am I missing? How can I sync up these two properly? My data access class is not managint any state, yet I don't understand why it doesn't behave correctly.
Additional info
I know about MMWormhole. Unfortunately this is not an option for me, since I need to reach proper functionality without including any third party solutions.
This terrific article, covers the topic, and it might be possible, that I need to employ NSFilePresenter, although it seems cumbersome, and I don't completely understand the mechanism yet. I really hope, there is an easier solution, than this one.
Well, I have learned two things here:
First of all, Always double check your entitlements, mine somehow got messed up, and that's why the shared container behaved so awkwardly.
Second:
Although viewWillAppear(_:) is not called, when you dismiss the notification center, it's still possible to trigger an update from your app delegate:
func applicationDidBecomeActive(application: UIApplication) {
NSNotificationCenter.defaultCenter().postNotificationName(updateDataNotification, object: nil)
}
Then in your view controller:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
NSNotificationCenter.defaultCenter().addObserverForName(updateDataNotification, object: nil, queue: NSOperationQueue.mainQueue()) { (_) -> Void in
self.tableView.reloadData()
}
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self)
}
Updating your Today widget is simple: each time the notification center is pulled down, viewWillAppear(:_) is called, so you can query for new data there.
I'll update the example project on GitHub shortly.