In my game, I have a function that builds the world map and configures associated data structures. I've been calling it from the the associated View Controllers' viewWillAppear function for months, and everything works as intended.
Today, I got the bright idea to move the world setup call to viewDidAppear and then display progress indicators as it does its thing. At that point, the setup function began throwing exceptions about 98.5% of the time. The few times it actually worked, I received memory warnings. Moving the call back to viewWillAppear solved the problem (and warnings) completely. But I couldn't let it go that easily and continued to debug.
I have this segment that decides where to build rivers. In that segment, there's a check for the altitude of neighboring cells to ensure rivers always flow downhill. In that segment I simply used bubble sort of the four (non-corner) neighbors, which is called while evaluating possible river locations:
for (int i=0; i<4; i++)
{
for (int j=0; j<4-i; j++)
{
if (candidates[j].tileType > candidates[j+1].tileType)
{
struct riverCandidate temp = candidates[j+1];
candidates[j+1] = candidates[j];
candidates[j] = temp;
}
}
}
Eventually, I got far enough to NSLog the values of the candidate[ ]'s before and after the sort. What I found is that when this whole thing is called from viewWillAppear, it works flawlessly 100% of the time. However, when called from viewDidAppear, occasionally one of the candidate[ ] elements is randomly corrupted (resulting in the aforementioned exception when I subsequently try to access it). I can't say I detected any clear patterns of corruption except that a) only one element of the 4 were ever corrupted at a time and b) I only saw the first [0] and last [3] elements corrupted. This chunk of code may be called a few dozen times in the course of world setup, and most passes are successful... but it only takes one of those passes to fail to screw up the works downstream.
To fix it, all I did was move the definition of temp outside the loop, like so:
struct riverCandidate temp;
for (int i=0; i<4; i++)
{
for (int j=0; j<4-i; j++)
{
if (candidates[j].tileType > candidates[j+1].tileType)
{
temp = candidates[j+1];
candidates[j+1] = candidates[j];
candidates[j] = temp;
}
}
}
Now it's working 100% of the time when called from viewDidAppear, and no men warnings.
So here are my questions: why would behavior change between viewWillAppear and viewDidAppear? If there's a potential race condition with malloc'ing the temp structure, why would it never happen from viewWillAppear and virtually always happen in viewDidAppear?
I'd like to understand this behavior better so that I don't stumble into it again.
UPDATE: So moving the definition of temp didn't actually help. Guess I just got lucky with a series of successful runs that made it appear things were working. After more debugging, I finally noticed the j loop above has a typo that lets it write beyond the end of the array, and corrected that.
But that still leaves me confused: how did I never hit that when called from viewWillAppear, but always hit it when I moved the function call to viewDidAppear? I must be missing something obvious.
Thanks
Related
I want to randomly display some images stored as attributes on an entity "Disc" in Core Data. I've been using the Swift Array.shuffled() function performed on the fetchedObjects of my NSFetchedResultsController "thisFRC."
First problem was that the desired images often did not appear when they should. Here's code that produced this problem. It's part of a function loadImages, which is called from viewWillAppear:
try thisFRC.performFetch()
fetchedData = thisFRC.fetchedObjects as! [Disc]
shuffledDiscs = fetchedData.shuffled()
thisDisc = shuffledDiscs [0]
Second problem was that when they did appear, I would very often see the same image repeated several (or many) times. I thought maybe the images were persisting for some unknown reason, so I did:
frontImageView.image = nil
rearImageView.image = nil
in prepareForSegue. Same problem upon returning to the original View Controller.
Third problem arose when I tried to fix the second problem by further randomizing the order of the images with the code below. It crashes at the commented line with this error: “Index out of range”.
try thisFRC.performFetch()
fetchedData = thisFRC.fetchedObjects as! [Disc]
shuffledIndices = fetchedData.indices.shuffled()
index = shuffledIndices [0] // Crashes here with “Index out of range”
shuffledDiscs = fetchedData.shuffled()
thisDisc = shuffledDiscs [index]
My questions:
1) Why doesn't the shuffled() function do a better job of randomizing? To be fair, I tried this code in a separate test app, and it seemed to work fine. If I can clear this up, I can dispense with my workaround.
2) I don't understand how the index could be out of range in index = shuffledIndices [0]
Note:
The images I'm using are quite large -- on the order of 2400 x 2400 -- being squeezed into a 160 x 160 image view, so the early anomalies could possibly be caused by scaling.
Any help would be greatly appreciated!
TIA
Resolved!
With insight from #Adis, and a thousand print() lines, I discovered the problem:
There was a function extraneously inserting a new Disc entity into my context every time I segued to one of the two connected View Controllers. This accounted for the "ghost" items that were showing up with no UIImage attributes, creating blank ImageViews. Once I deleted these anomalies, and killed the function that created them, everything works fine. I was also able to just use the shuffled() function without the extra flourish I had put on it.
All is well, and thanks to all who took the time to look!
I see questions regarding long delays in displaying UIImageViews after downloading, but my question involves long delays when
reading from local storage.
After archiving my hierarchy of UIImageViews to a local file (as per narohi's answer in
How to output a view hierarchy & contents to file? ),
I find that if I want to reload them, it takes 5 to 20 seconds for the views to actually appear on screen,
despite my setting setNeedsDiplay() on the main view and all the subviews.
I can immediately query the data contained in the
custom subclasses of UIView that get loaded -- showing that NSKeyedUnarchiver and all the NS-decoding and all the init()'s have completed -- however
the images just don't appear on the screen for a long time. Surely the next redraw cycle is shorter than 5-20 seconds...?
It seems odd that images from PhotoLibrary appear instantly, but anything loaded from local file storage using NSKeyedUnarchiver takes "forever."
What's going on here, and how can I speed this up?
.
.
To be explicit, the relevant part of my Swift code looks like this:
let view = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as! UIView!
if (nil == view) {
return
}
myMainView.addSubview(view)
view.setNeedsDisplay()
// now do things with the data in view ...which all works fine
I find that, even if I add something like...
for subview in view.subviews {
subview.setNeedsDisplay()
}
...it doesn't speed up the operations.
We are not talking huge datasets either, it could be just a single imageview that's being reloaded.
Now, I do also notice these delays occurring when downloading from the internet using a downloader like the one shown in
https://stackoverflow.com/a/28221670/4259243
...but I have the downloader print a completion message after not only the download but when the (synchronous operation)
data.writeToFile() is complete (and before I try to load it using NSKeyedUnarchiver), so this indicates that the delay
in UIImageView redraws is NOT because the download is still commencing....and like I say, you can query the properties of the data and it's all in memory, just not displaying on the screen.
UPDATE: As per comments, I have enclosed the needsDisplay code in dispatch_async as per Leo Dabus's advice, and done some Time Profiling as per Paulw11's. Link to Time Profiling results is here: https://i.imgur.com/sa5qfRM.png I stopped the profiling immediately after the image appeared on the screen at around 1:00, but it was actually 'loaded' during the bump around 20s. During that period it seems like nothing's happening...? The code is literally just waiting around for a while?
Just to be clear how I'm implementing the dispatch_async, see here:
func addViewToMainView(path: String) {
let view = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as! UIView!
if (nil == view) {
return
}
dispatch_async(dispatch_get_main_queue(), {
self.myMainView.addSubview(view)
view.setNeedsDisplay()
self.myMainView.setNeedsDisplay()
})
}
...Since posting this I've found a few posts where people are complaining about how slow NSKeyedUnarchiver is. Could it just be that? If so, :-(.
SECOND UPDATE: Ahh, the "let view = " needs to be in the dispatch_async. In fact, if you just put the whole thing in the dispatch_async, it works beautifully! so...
func addViewToMainView(path: String) {
dispatch_async(dispatch_get_main_queue(), {
let view = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as! UIView!
if (nil == view) {
return
}
self.myMainView.addSubview(view)
view.setNeedsDisplay()
self.myMainView.setNeedsDisplay()
})
}
This works instantly. Wow.. Credit to Leo Dabus. Leaving this here for others...
EDIT: I have simplified the code and added calls to NSThread.isMainThread(), to see if this was the problem. See more extensive edit below
I'm working on a fairly simple app to assist a professor in research over the summer. The app intends to determine word difficulty in sentences based on the accelerometer in the iPad.
Essentially, the user will tilt the iPad, thus creating a non-zero acceleration, and the text, which is situated in a UILabel placed within a scrollView will scroll accordingly.
This works excellently 99% of the time. In almost all of our tests, it works perfectly, it goes through the entire text without issue, and nothing bad happens. Very rarely however, it just breaks, throwing an error of EXC_BAD_ACCESS. I want to stress that on the rare occasions it does break, there is no apparent pattern, it sometimes happens in the middle of scrolling, near the end, or at the start.
Obviously I would like the app to be bug free, and this is a fairly major one which I just can't figure out, so any help you can give would be greatly appreciated.
Here is the total code for my ScrollingLabel Class (the bug always happens at the end of the startScrolling class).
import Foundation
import UIKit
import QuartzCore
import CoreMotion
public class ScrollingLabel {
//Instantiation of scroll view and label
var baseTextLabel:UILabel!
var baseScrollView:UIScrollView!
var frame:CGRect!
//Instantiation of accelerometer materials
var motionManager=CMMotionManager()
var queue=NSOperationQueue()
//To rectify the issue, I have changed this to:
//var queue=NSOperationQueue.mainQueue(), SEE EDIT BELOW
init(frame:CGRect) {
/*Initializes the object by calling 3 private setup functions,
each dealing one with a specific feature of the final label, and
finally calling the scroll function to activate the accelerometer
control*/
setupFrame(frame)
setupLabel()
setupScroll()
startScrolling()
}
private func startScrolling() {
//The main accelerometer control of the label
println(NSThread.isMainQueue) //THIS RETURNS TRUE
//Allows the start orientation to become default
var firstOrientation:Bool
var timeElapsed:Double=0
if letUserCreateDefaultOrientation {firstOrientation=true}
else {firstOrientation=false}
var standardAccel:Double=0
//Begins taking updates from the accelerometer
if motionManager.accelerometerAvailable{
motionManager.accelerometerUpdateInterval=updateTimeInterval
motionManager.startAccelerometerUpdatesToQueue(self.queue, withHandler: { (accelerometerData, error:NSError!) -> Void in
println(NSThread.isMainQueue) //THIS RETURNS FALSE
//Changes the input of acceleration depending on constant control variables
var accel:Double
if self.timerStarted {
timeElapsed+=Double(self.updateTimeInterval)
}
if !self.upDownTilt {
if self.invertTextMotion {accel = -accelerometerData.acceleration.y}
else {accel = accelerometerData.acceleration.y}
}
else {
if self.invertTextMotion {accel = -accelerometerData.acceleration.x}
else {accel = accelerometerData.acceleration.x}
}
//Changes default acceleration if allowed
if firstOrientation {
standardAccel=accel
firstOrientation=false
}
accel=accel-standardAccel
//Sets the bounds of the label to prevent nil unwrapping
var minXOffset:CGFloat=0
var maxXOffset=self.baseScrollView.contentSize.width-self.baseScrollView.frame.size.width
//If accel is greater than minimum, and label is not paused begin updates
if !self.pauseScrolling && fabs(accel)>=self.minTiltRequired {
//If the timer has not started, and accel is positive, begin the timer
if !self.timerStarted&&accel<0{
self.stopwatch.start()
self.timerStarted=true
}
//Stores the data, and moves the scrollview depending on acceleration and constant speed
if self.collectData {self.storeIndexAccelValues(accel,timeElapsed: timeElapsed)}
var targetX:CGFloat=self.baseScrollView.contentOffset.x-(CGFloat(accel) * self.speed)
if targetX>maxXOffset {targetX=maxXOffset;self.stopwatch.stop();self.doneWithText=true}
else if targetX<minXOffset {targetX=minXOffset}
self.baseScrollView.setContentOffset(CGPointMake(targetX,0),animated:true)
if self.baseScrollView.contentOffset.x>minXOffset&&self.baseScrollView.contentOffset.x<maxXOffset {
if self.PRIVATEDEBUG {
println(self.baseScrollView.contentOffset)
}
}
}
})
}
}
When it does crash, it happens at the end of the startScrolling, when I set the content Offset to target X. If you need more information I am happy to provide it, but as the bug happens so rarely I don't have anything to say about specifically when it occurs or anything like that... it just seems random.
EDIT: I have simplified the code to just the pertinent parts, and added the two locations where I called NSThread.isMainQueue(). When called on the first line of startScrolling, .isMainQueue() returns TRUE, but then when called inside motionManager.startAccelerometerUpdatesToQueue it returns FALSE.
To rectify this, I have changed self.queue from just a NSOperationQueue() to NSOperationQueue.mainQueue(), and after making this switch, the second .isMainThread() call (the one inside motionManager.startAccelerometerUpdatesToQueue) now returns TRUE as we hoped for.
That's a lot of code. I don't see anything in the line that sets your content offset based on targetX.
A couple of possibilities are
baseScrollView is getting deallocated and is a zombie (unlikely since it looks like you have it defined as a regular (strong) instance variable.)
You're calling startScrolling from a background thread. All bets are off if you update UI objects from a background thread. You can check for that using NSThread.isMainThread(). Put that code in your startScrolling method, and if it returns false, that is your problem.
EDIT:
Based on your comments below in response to my answer you are calling startScrolling from a background thread.
That is indeed very likely the problem. Edit your post to show the code that is being called from a background thread, including the context. (Your "accelerometer update cycle" code).
You can't manipulate UIKit objects from a background thread, so you likely need to wrap the UIKit changes in your "accelerometer update cycle" code in a call to dispatch_async that runs on the main thread.
EDIT #2:
You've finally posted enough information so that we can help you. Your original code had you receiving acellerometer updates on a background queue. You were doing UIKit calls from that code. That is a no-no, and the results of doing it are undefined. They can range from updates taking forever, to not happening at all, to crashing.
Changing your code to use NSOperationQueue.mainQueue() as the queue that processes updates should fix the problem.
If you need to do time-consuming processing in your handler for accelerometer updates then you could continue to use the background queue you were using before, but wrap your UIKit calls in a dispatch_async:
dispatch_async(dispatch_get_main_queue())
{
//UIKit code here
}
That way your time-consuming accelerometer update code doesn't bog down the main thread but UI updates are still done on the main thread.
I'm building a scrolling menu that generates new rows of buttons on the fly, and must generate each button from a large number of sprites. Because this is processor intensive, the menu sticks for about a quarter second each time it needs to load a new row of buttons. I realized I needed to add multi-threading so the button load could be handled in a different thread than the scroll animation, but when I do it crashes when it tries to load new buttons. Here is the code I'm using:
-(void)addRowBelow{
_rowIndex--;
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSMutableArray *row = [self addRow:_rowIndex];
[_buttonGrid addObject:row];
[self removeRow:[_buttonGrid objectAtIndex:0]];
});
_nextRowBelowPos += _rowHeight;
_nextRowAbovePos += _rowHeight;
}
Each time I test it I get a different error, sometimes it's a memory error or an assertion failure. I suspect it has to do with calling cocos2d functions asynchronously?
You are probably getting crashing issues because you are multithreading access to the cocos managed objects (sprites, layers, nodes, etc). Since the engine expects to use the internals of these objects for display, GPU operations, etc., and is NOT thread safe, you are probably not going to have good outcomes with multi-threading. You may be changing stuff right in the middle of when it is using it.
Creating/destroying sprites on the fly is probably the reason for your slow down. Cocos2d can display lots (I think it is on the order of 2k) objects on the screen at 60 fps...as long as you don't throttle it down by doing a lot of creation/destruction or AI.
I suggest you preload all your sprites before your scene goes on the stage. You can do this in an intro scene or in the init of the scene itself and let the sprites be owned by the scene. Then you can iterate over them during the update() call and change their positions, make the visible/invisible, etc.
For reference, I usually create different "sprite layers" that load up all their sprites on addition to the scene. If I am going to have dynamic objects, I try to allocate some up front and recycle them when possible. This also allows me to control the order of "what is in front of what" on the screen (see example here). Each layer also draws elements of specific "entity types", giving a nice "MVC" character to a lot of the display.
This is analogous to the way iPhone Apps recycle table cells.
Only create them the first time you need them and have a stash on hand before you need them at all.
Was this helpful?
The pattern you probably want to use is
Dispatch work to a background thread. (Note that the work must be safe to execute on a background thread.)
Dispatch back to the main thread to update your UI.
Here's an example of what that looks like in code:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Do work that is safe to execute in the background.
// For example, reading images from disk.
dispatch_async(dispatch_get_main_queue(), ^{
// Do work here that must execute on the main thread.
// For example, calling Cocos2D objects' methods.
NSMutableArray *row = [self addRow:_rowIndex];
[_buttonGrid addObject:row];
[self removeRow:[_buttonGrid objectAtIndex:0]];
});
});
Modern user interfaces, especially MacOS and iOS, have lots of “casual” animation -- views that appear through brief animated sequences largely orchestrated by the system.
[[myNewView animator] setFrame: rect]
Occasionally, we might have a slightly more elaborate animation, something with an animation group and a completion block.
Now, I can imagine bug reports like this:
Hey -- that nice animation when myNewView appears isn't happening in the new release!
So, we'd want unit tests to do some simple things:
confirm that the animation happens
check the duration of the animation
check the frame rate of the animation
But of course all these tests have to be simple to write and mustn't make the code worse; we don’t want to spoil the simplicity of the implicit animations with a ton of test-driven complexity!
So, what is a TDD-friendly approach to implementing tests for casual animations?
Justifications for unit testing
Let's take a concrete example to illustrate why we'd want a unit test. Let's say we have a view that contains a bunch of WidgetViews. When the user makes a new Widget by double-clicking, it’s supposed to initially appear tiny and transparent, expanding to full size during the animation.
Now, it's true that we don't want to need to unit test system behavior. But here are some things that might go wrong because we fouled things up:
The animation is called on the wrong thread, and doesn't get drawn. But in the course of the animation, we call setNeedsDisplay, so eventually the widget gets drawn.
We're recycling disused widgets from a pool of discarded WidgetControllers. NEW WidgetViews are initially transparent, but some views in the recycle pool are still opaque. So the fade doesn't happen.
Some additional animation starts on the new widget before the animation finishes. As a result, the widget begins to appear, and then starts jerking and flashing briefly before it settles down.
You made a change to the widget's drawRect: method, and the new drawRect is slow. The old animation was fine, but now it's a mess.
All of these are going to show up in your support log as, "The create-widget animation isn't working anymore." And my experience has been that, once you get used to an animation, it’s really hard for the developer to notice right away that an unrelated change has broken the animation. That's a recipe for unit testing, right?
The animation is called on the wrong thread, and doesn't get drawn.
But in the course of the animation, we call setNeedsDisplay, so
eventually the widget gets drawn.
Don't unit test for this directly. Instead use assertions and/or raise exceptions when animation is on the incorrect thread. Unit test that the assertion will raise an exception appropriately. Apple does this aggressively with their frameworks. It keeps you from shooting yourself in the foot. And you will know immediately when you are using an object outside of valid parameters.
We're recycling disused widgets from a pool of discarded
WidgetControllers. NEW WidgetViews are initially transparent, but some
views in the recycle pool are still opaque. So the fade doesn't
happen.
This is why you see methods like dequeueReusableCellWithIdentifier in UITableView. You need a public method to get the reused WidgetView which is the perfectly opportunity to test properties like alpha are reset appropriately.
Some additional animation starts on the new widget before the
animation finishes. As a result, the widget begins to appear, and then
starts jerking and flashing briefly before it settles down.
Same as number 1. Use assertions to impose your rules on your code. Unit test that the assertions can be triggered.
You made a change to the widget's drawRect: method, and the new
drawRect is slow. The old animation was fine, but now it's a mess.
A unit test can be just timing a method. I often do this with calculations to ensure they stay within a reasonable time limit.
-(void)testAnimationTime
{
NSDate * start = [NSDate date];
NSView * view = [[NSView alloc]init];
for (int i = 0; i < 10; i++)
{
[view display];
}
NSTimeInterval timeSpent = [start timeIntervalSinceNow] * -1.0;
if (timeSpent > 1.5)
{
STFail(#"View took %f seconds to calculate 10 times", timeSpent);
}
}
I can read your question two ways, so I want to separate those.
If you are asking, "How can I unit test that the system actually performs the animation that I request?", I would say it's not worth it. My experience tells me it is a lot of pain with not a lot of gain and in this kind of case, the test would be brittle. I've found that in most cases where we call operating system APIs, it provides the most value to assume that they work and will continue to work until proven otherwise.
If you are asking, "How can I unit test that my code requests the correct animation?", then that's more interesting. You'll want a framework for test doubles like OCMock. Or you can use Kiwi, which is my favorite testing framework and has stubbing and mocking built in.
With Kiwi, you can do something like the following, for example:
id fakeView = [NSView nullMock];
id fakeAnimator = [NSView nullMock];
[fakeView stub:#selector(animator) andReturn:fakeAnimator];
CGRect newFrame = {.origin = {2,2}, .size = {11,44}};
[[[fakeAnimator should] receive] setFrame:theValue(newFrame)];
[myController enterWasClicked:nil];
You don't want to actually wait for the animation; that would take the time the animation takes to run. If you have a few thousand tests, this can add up.
More effective is to mock out the UIView static method in a category so that it takes effect immediately. Then include that file in your test target (but not your app target) so that the category is compiled into your tests only. We use:
#import "UIView+SpecFlywheel.h"
#implementation UIView (SpecFlywheel)
#pragma mark - Animation
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion {
if (animations)
animations();
if (completion)
completion(YES);
}
#end
The above simply executes the animation block immediately, and the completion block immediately if it's provided as well.