Non-Linear Thinking Part 2 - A Mysterious Doouubbllee.

montyharper

Monty Harper

Posted on December 3, 2023

Non-Linear Thinking Part 2 - A Mysterious Doouubbllee.

As I refine my app in preparation for turning it in, I continue to struggle with the non-linear nature of object-oriented programming.

Don't get me wrong, it's fantastic. It allows me to do stuff I couldn't have dreamed of in a previous life! But it also opens the door to weird and challenging behaviors.

A Mysterious Mysterious Double Double

Yesterday I solved one of those mysteries.

My app displays events from Apple's Calendar App, so the user is presented a list of Apple calendars and asked to choose which ones will supply events to be displayed. That page was working great except on first launch, when some or all of the available calendars were showing up twice. This happened consistently, although which of the calendars got doubled seemed random.

Careful Sleuthing

I used some print statements and breakpoints to determine more specifically what was happening.

The function that collects my calendars into an array clears out the current array, then adds new calendars to it. I put some print statements in, believing it was silly because the function itself runs linearly from top to bottom as functions do, so my print statements should obviously output:

"I'm clearing the calendar array."
"I'm adding new calendars."

But when I ran the app, here's what I got instead:

"I'm clearing the calendar array."
"I'm clearing the calendar array."
"I'm adding new calendars."
"I'm adding new calendars."

That explains the doubling, sort of. But how is it possible? The function seems to be looping where there aren't any loops!

The only explanation I could think of would be that the function is getting called twice at the same time. Sure enough, adding a break point showed that this was the case; it was running concurrently on two different threads. But why?

More Sleuthing

So I searched the code for the function name, and no - I only ever call it explicitly one time, from the init of my EventManager class. The app creates only one instance of EventManager, so...

Well, there is one other place the function gets called, in the same init, buried within this statement:

NotificationCenter.default.addObserver(self, selector: #selector(self.updateCalendarsAndEvents), name: .EKEventStoreChanged, object: eventStore)
Enter fullscreen mode Exit fullscreen mode

This notification will call the updateCalendarsAndEvents function whenever the data in the Apple Calendar App changes. But I'm not making any changes!

Although... the user IS asked for permission to access the Calendar app at the start of my update function. The way permissions work, the system only actually asks the user for permission once - on first launch. That could explain why the calendar list only misbehaves on first launch!

A Working Theory

So here's my theory: The app calls the update function when it initializes my EventManager. The function pauses for a response to the permissions request. Meanwhile, the Notification command is executed, so we are now subscribed. When the user responds with permission, the update function continues. Simultaneously, NotificationCenter interprets the change in permissions as reason enough to trigger the update function. So before the function call from init can be completed, the same function gets a second call from notifications, and now it's running on two different threads. The timing is close enough that both calls create new calendars after both calls erase existing calendars. It's also close enough that sometimes a new calendar created by the first call gets erased by the second call, which explains why the number of duplicated calendars seems random.

The Fix

A fix seemed impossible for a moment... All three parts are required: I need to updateCalendarsAndEvents when the EventManager is initialized, I need to ask the user for permission to access Calendar, and I need to subscribe to changes, so my app can fetch new calendars and events when they become available.

So I dug into the documentation, and soon discovered that the logic of my app was based on a misunderstanding.

I had thought I needed to check permissions every time I accessed the EventStore. But that is not the case. Requesting permission is a separate function from fetching data. So!

My mistaken code looked like this:

func updateCalendarsAndEvents() {
eventStore.requestAccess(to: EKEntityType.event) {
 // closure containing the logic for erasing the current array and replacing it with new calendars
}
Enter fullscreen mode Exit fullscreen mode

I fixed it by creating a Bool to check my current permission status, and moving the request for access into my EventManager init, like so:

init() {
        NotificationCenter.default.addObserver(self, selector: #selector(self.updateCalendarsAndEvents), name: .EKEventStoreChanged, object: eventStore)

        if StateBools.shared.noPermissionForCalendar {
            eventStore.requestAccess(to: EKEntityType.event) {_,_ in}
            // If permission is newly given, notifications will fetch new data
        } else {
            // If permission is already given, fetch new data.
            updateCalendarsAndEvents()
        }
    }
Enter fullscreen mode Exit fullscreen mode

With my new logic in place, the doubling has ceased! I just needed to make sure permissions were requested only when necessary, and allow the correct function call to handle the update so they weren't both trying to do it at once.

In Conclusion

Non-linear programming can lead to odd behaviors such as duplicated data caused by one function running twice concurrently, but careful sleuthing and clear logic will save the day!

πŸ’– πŸ’ͺ πŸ™… 🚩
montyharper
Monty Harper

Posted on December 3, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Non-Linear Thinking, part 1
programming Non-Linear Thinking, part 1

November 25, 2023