0

I'd like to understand whether a function that may take time to execute will delay the execution of the function after it. Let me share the code:

First, we have a function that calculates an average value based on. This should be almost instant if the user has a few habits, but if the user has a high number of habits with a high number of entries, it might take a little longer.

var averagePercentage = 0.0

func calculateAveragePercentage() {
    var percentages: [Double] { habits.map { $0.percentageFormed } }
    if percentages.reduce(0,+) == 0 { averagePercentage = 0.0 }
    else { averagePercentage = Double(percentages.reduce(0,+)) / Double(percentages.count) }
}

percentageFormed for each habit is calculated like this:

var percentageFormed: Double {
    let validEntries = entries.filter({ $0.completed })
    if validEntries.count == 0 { return 0.0 }
    else { return Double(validEntries.count) / Double(daysToForm) }
}

What I am trying to understand, and I hope someone could help clarify this is the following: If in the viewDidLoad of a controller I call calculateAveragePercentage() and then I call a method that relies on this value, will the functions be executed in parallel? In which case there is a chance that setCircle() will be called before calculateAveragePercentage() has finished. There's no completion handler for the operations in calculateAveragePercentage() so I am not sure if there are situations where this can break or if setCircle() will wait for calculateAveragePercentage() to finish no matter how long it takes.

override func viewDidLoad() {
    calculateAveragePercentage()
    setCircle()
}
CristianMoisei
  • 2,071
  • 2
  • 22
  • 28
  • Thank you, but I am not able to create any significant delay even if I add several habits with 10k entries each, so the functions just print instantly. This may not be the case on slower devices or under circumstances I can't think of, so would you mind telling me which is it? – CristianMoisei Jan 15 '23 at 17:46
  • 1
    Unrelated, but remember to call `super.viewDidLoad()` in your override of `viewDidLoad`. – Rob Jan 16 '23 at 00:11
  • Hey @Rob, thank you for the suggestion. Is that still necessary if I am just subclassing UIViewController? If so, why? I understand the need to call the superclass' override method if I wrote additional logic in a custom superclass, but does UIViewController do anything with the lifecycle methods other than offer them to be overridden? – CristianMoisei Jan 16 '23 at 00:16
  • 1
    See https://stackoverflow.com/a/40152226/1271826. In short, whenever you override, calling `super` is well advised unless the documentation for the method explicitly advises you not to do so. – Rob Jan 16 '23 at 00:31

2 Answers2

2

In short, they never run in parallel unless you explicitly use a mechanism (like you can with async-await or GCD) to do so. So, you don’t need to worry that the above will get to setCircle prematurely.

Now, if you want the above to run asynchronously with respect to the loading of your view, we can show you how to do that, but, in general, averaging a bunch of numeric values is unlikely (even on older devices) to warrant that, especially in optimized, release builds. Averaging numeric values is simply not computationally intensive enough to necessitate that unless you are dealing with millions of values.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
1

It would be cool if it could work this way, but no, Swift statements are executed one line at a time, just as you see when hopping through the debugger. Of course that excludes code that uses concurrency primitives like threads, dispatch queues or tasks.

There is instruction-level parallelism happening at the CPU level, but that’s mostly impossible to observe as at the software level.

There are languages that auto-parallelize expressions, but they’re mostly an academic pursuit for now. It turns out to be quite difficult because:

  1. it’s pretty tricky to find out which parts are worth the overhead to parallelize, and which aren’t. There’s lots of non-local (and hardware specific) effects that make it hard to reason about (e.g. parallelizing one computation speeds it up, but it thrashes the cache and slows down another)
  1. Impure functions (which are hugely prevalent in most mainstream languages today) are hard to reason about and limit what optimizations can be made without accidentally introducing observable distinctions in behaviour.
Alexander
  • 59,041
  • 12
  • 98
  • 151
  • Thank you for explaining - this is clear. In my case, is there anything I can do to make sure the first function finished executing before the second one is called? I tried to create scenarios where I would slow down the first function with several habits times 10k entries to go through, but it's not having any noticeable impact. Still, I worry there will be a scenario or device where this will no be calculated correctly. – CristianMoisei Jan 15 '23 at 17:59
  • 1
    @CristianMoisei If it’s not a dominant part of an instruments trace, I wouldn’t worry about it, unless you see stuttering (which is more likely now that there are 120 fps screens to worry about) Ultimately it’s up to profiling and measuring it in real world scenarios (e.g. the slowest scenario might be an old device, while on a phone call, in battery saver mode, while hot) is the ultimate answer. – Alexander Jan 15 '23 at 18:06
  • 1
    Moving the computation off the main thread isn’t necessarily a silver bullet, either. It comes with a few substantial draw backs. Perhaps the most important one is that it just makes your code more complicated. Worse yet, it might be for nothing, because it’s entirely possible that your calculation is quicker to compute than it is to spin up a background task. – Alexander Jan 15 '23 at 18:09