I'm using PencilKit sample as an example. I added a willResignActiveNotification modification. After you quit move move back and fourth between collectionView and note 3 times.
Then move the app to background, you'll observe willResignActiveNotification active 3 times. I expect to see it called once only. But why 3 times? Is there a way to avoid it?
In DrawingViewController class, I have:
lazy var willResignActive: (Notification) -> Void = { [weak self] _ in
print("Saved on willResignActive")
}
In viewWillAppear, I have
_ = NotificationCenter.default.addObserver(forName: UIApplication.willResignActiveNotification,
object: nil,
queue:.main,
using: willResignActive)
Here are the sample Xcode. https://github.com/legolasW/DrawingWithPencilKit
To reproduce the result, click the note then back. Repeat.
Then move the app to background, and you'll observe willResignActiveNotification runs multiple times.
The results are like below.
Edits:
I tried to move addObserver to viewDidLoad, problem persists.
I tried to removeObserver in willResignActive closure, problem
persists.
I double checked with the memory graph, can confirm its not a
DrawingViewController memory retain.
I tried willResignActiveNotification, willResignActiveNotification, willBecomeActiveNotificication, willEnterBackground, showed same multiple call behavior.
Move
NotificationCenter.default.addObserver(forName: UIApplication.willResignActiveNotification,
object: nil,
queue:.main,
using: willResignActive)
to viewDidLoad
Related
I'm struggling with asynchronous code in Swift. I have to disable 2 buttons if there is no internet connection. I have 2 DispatchQueue.main.async calls in 2 different functions. 1 of them works, but the other one doesn't disable the button (the good thing is that you can click on the button and nothing happens). I am testing my code on my iPhone because the simulator does not run the SDK I am using properly.
All these functions look like this (this is the one at the end that does not work):
public func disableButton2(_ check: Bool) {
DispatchQueue.main.async{
self.button2.isEnabled = check
}
I have used this and it was working fine
DispatchQueue.main.asyncAfter(deadline: .now() + delayInSeconds) { [weak self] in
guard let self = self else {
return
}
try this you will get the result.
A kind of more accurate approach, in order to explain "why asyncAfter works instead of just async" would be that DispatchQueue.main.asyncAfter would capture the thread in order to make the UI change to happend.
But most of the times could be because your are trying to update a view element within another view, say UICollectionView inside a UIViewController. In order to solve this you should call async method inside the method that makes this UI update
I'm working on a project in Swift that requires strict control of image display and removal timings in certain sections. I'm finding that when I set the image property of an NSImageView from inside a block that's fired by a Timer, the actual display of the image on the screen is delayed by up to a second after the assignment is complete. (This is measured by eyeballing it and using a stopwatch to gauge the time between when an NSLog line is written and when the image actually appears on-screen.)
Triggering image display with a click appears to happen instantaneously, whether it's done by setting the image property of an existing NSImageView, or constructing one on the spot and adding it as a subview.
I have attempted to reduce the behavior down to a fairly simple test case, which, after basic setup of the view (loading the images into variables and laying out several target image locations, which are NSImageViews stored to the targets array), sets a Timer, with an index into the targets array stored in its userInfo property, to trigger the following method:
#objc func testATimer(fromTimer: Timer) {
if let targetLocation = fromTimer.userInfo as? Int {
NSLog("Placing target \(targetLocation)")
targets[targetLocation].image = targetImage
OperationQueue.main.addOperation {
let nextLocation = targetLocation + 1
if (nextLocation < self.targets.count) {
NSLog("Setting timer for target \(nextLocation)")
let _ = Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: #selector(ViewController.testATimer(fromTimer:)), userInfo: nextLocation, repeats: false)
}
}
}
}
The amount of time observed between the appearance of the log entry "Placing target x" and that of the image that is set to be displayed in the very next line seems to average between 0.3 and 0.6 seconds. This is far beyond the delay that this project can tolerate, as the image will only be on screen for a total of 1 second at a time.
My best guess as to why this is happening is that it is something to do with Timers running on a separate thread from the main display thread; however, I do not have enough experience with multi-threaded programming to know exactly how to mitigate that.
Any assistance would be most appreciated, and if I've left out information that would make answering easier (or possible) I'm more than happy to give it.
Well, after poking at it with some helpful people on in #macdev on irc.freenode.net, I found that the source of the problem was the program scaling the image down on the fly every time it set it to the NSImageView. Reducing the size of the image ahead of time solved the problem. (As did setting the image once, then hiding and showing the NSImageView instead.)
I'm seeing crashes that either shouldn't be possible, or are very much possible and the documentation just isn't clear enough as to why.
UPDATE:
Although I disagree with the comment below asking me to separate this into multiple SO questions, if someone could focus on this one I think it would help greatly:
When are notifications delivered to the main thread? Is it possible that the results on the main thread are different than they were in a previous runloop without being notified yet of the difference?
If the answer to this question is yes the results could be different than a previous runloop without notifying then I would argue it is CRUCIAL to get this into the documentation somewhere.
Background Writes
First I think it's important to go over what I am already doing for writes. All of my writes are performed through a method that essentially looks like this (error handling aside):
func write(block: #escaping (Realm) -> ()) {
somePrivateBackgroundSerialQueue.async {
autoreleasepool {
let realm = try! Realm()
realm.refresh()
try? realm.write { block(realm) }
}
}
}
Nothing crazy here, pretty well documented on your end.
Notifications and Table Views
The main question I have here is when are notifications delivered to the main thread after being written from a background thread? I have a complex table view (multiple sections, ads every 5th row) backed by realm results. My main data source looks like:
enum StoryRow {
case story(Story) // Story is a RealmSwift.Object subclass
case ad(Int)
}
class StorySection {
let stories: Results<Story>
var numberOfRows: Int {
let count = stories.count
return count + numberOfAds(before: count)
}
func row(at index: Int) -> StoryRow {
if isAdRow(at: index) {
return .ad(index)
} else {
let storyIndex = index - numberOfAds(before: index)
return .story(stories[storyIndex])
}
}
}
var sections: [StorySection]
... sections[indexPath.section].row(at: indexPath.row) ...
Before building my sections array I fetch the realm results and filter them based on the type of stories for the particular screen, sort them so they are in the proper order for their sections, then I build up the sections by passing in results.filter(...date query...) to the section constructor. Finally, I results.observe(...) the main results object (not any of the results passed into the section) and reload the table view when the notification handler is called. I don't bother observing the results in the sections because if any of those results changed then the parent had to change as well and it should trigger a change notification.
The ad slots have callbacks when an ad is filled or not filled and when that happens instead of calling tableView.reloadData() I am doing something like:
guard tableView.indexPathsForVisibleRows?.contains(indexPath) == true else { return }
tableView.beginUpdates()
tableView.reloadRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
The problem is, I very rarely see a crash either around an index being out of bound when accessing the realm results or an invalid table view update.
QUESTIONS
Is it possible the realm changed on the main thread before any notifications were delivered?
Should table view updates other than reloadData() simply not be used anywhere outside of a realm notification block?
Anything else crucial I am missing?
There's nothing in your code snippets or description of what you're doing that jumps out at me as obviously wrong. Having a separate callback mechanism that updates specific slots independent of Realm change notifications has a lot of potential for timing related bugs, but since you're explicitly checking if the indexPath is visible before reloading the row, I would expect that to at worst manifest as reloading the wrong row and not a crash.
The intended behavior is that refreshing the Realm and delivering notifications is an atomicish operation: anything that causes the read version to advance will deliver all notifications before returning. In simple cases, this means that you'll never see the new data without the associated notification firing first. However, there's some caveats to this:
Nested notification delivery doesn't work correctly, so beginning a write transaction from within a notification block can result in a notification being skipped (merely calling refresh() can't cause this, as it's just a no-op within a notification). If you're performing all writes on background threads you shouldn't be hitting this.
If you have multiple notification blocks, then obviously anything which gets invoked from the first one will run before the second notification block gets a chance to do things, and a call to tableView.reloadData() may result in quite a lot of things happening within the notification block. If this is the source of problems, you would hopefully see exceptions being thrown with a stack trace coming from within a notification block.
I'm working on a little project in spritekit and can't quite figure something out. I am animating a sprite using SKAction.animateWithTextures and moving through and array. Works fine just like it should. The issue is that I would like to a function that starts when the animation starts and one when it ends. I see there is a .animationDidStart(CAAnimation), but because what I'm doing is not a CAAnimation I can't really use it. Is there something like this for the method I'm using? As you can may or may not be able to tell I'm still rather new to swift. Thanks for any help in advance.
I'd create a sequence of actions. First a block action to call your method you want called when the animation starts, then your animateWithTextures action, finally another block action that calls your finished method.
let startAction = SKAction.runBlock {
self.startAnimation()
}
let textureAction = SKAction.animateWithTextures...
let finishedAction = SKAction.runBlock {
self.finishedAnimation()
}
SKAction.sequence([startAction, textureAction , finishedAction])
I have a universal app written in Swift using xCode 6.3.2. It is currently very simple in that when I push a button a random number is generated and then stored using CoreData. This works perfectly until I implement iCloud. With iCloud enabled storing a new random number doesn't always propagate onto additional devices. It does most of the time, but not always.
I am testing using an iPad Air, iPhone 6 Plus and iPhone 4s
I am using the following three notification observers:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "persistentStoreDidChange", name: NSPersistentStoreCoordinatorStoresDidChangeNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "persistentStoreWillChange:", name: NSPersistentStoreCoordinatorStoresWillChangeNotification, object: managedContext.persistentStoreCoordinator)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "receiveiCloudChanges:", name: NSPersistentStoreDidImportUbiquitousContentChangesNotification, object: managedContext.persistentStoreCoordinator)
and here is the function for the third one:
func receiveiCloudChanges(notification: NSNotification)
{
println("iCloud changes have occured")
dispatch_async(dispatch_get_main_queue())
{
self.activityIndicator.startAnimating()
self.updateLabel.text = "iCloud changes have occured"
self.managedContext.performBlockAndWait
{ () -> Void in
self.managedContext.mergeChangesFromContextDidSaveNotification(notification)
}
self.reloadTextViewAndTableView()
self.activityIndicator.stopAnimating()
}
}
I am not attempting to update the UI until the managedContext is finished with the merge, and I am performing everything on the main thread. I am really at a loss why the changes on one device are only displayed on the second or third one about 90-95% of the time.
As part of my trial and error, when I went to delete the app from my test devices and reinstall there is sometimes a message that an iCloud operation is pending, but it doesn't matter how long I wait, once the devices are out of sync they stay that way. Even when they are out of sync if I add another number or two those will still propagate to the other devices, but then I will invariably lose more data. It seems to work about 90% of the time.
I use the following to update the UI:
func reloadTextViewAndTableView()
{
let allPeopleFetch = NSFetchRequest(entityName: "Person")
var error : NSError?
let result = managedContext.executeFetchRequest(allPeopleFetch, error: &error) as! [Person]?
//Now reload the textView by grabbing every person in the DB and appending it to the textView
textView.text = ""
allPeople = managedContext.executeFetchRequest(allPeopleFetch, error: &error) as! [Person]
for dude in allPeople
{
textView.text = textView.text.stringByAppendingString(dude.name)
}
tableView.reloadData()
println("allPeople.count = \(allPeople.count)")
}
I am really at a stand still here. I am just not sure why it "usually" works...
So I am still not sure how or why CoreData is sometimes getting out of sync as described above. I have found though that if I enter one new number after another very rapidly is when it usually occurs.
As a workaround I have added a button to the UI that allows the user to force a resync with iCloud by rebuilding the NSPersistentStore using the following option.
NSPersistentStoreRebuildFromUbiquitousContentOption: true
I would much rather the store stayed in sync with all other devices all the time, but at least this way the user will never lose data. If they notice that they are missing a record they know they entered on another device, then all they have to do is hit the resync button.