EKEventStore's calendarsForEntityType problem

The following script to search for calendars in EKEventStore’s calendars, intermittently fails.

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use framework "EventKit"
use scripting additions

# Init EKEventStore and authorize access to EKEventStore
set theEKEventStore to current application's EKEventStore's (alloc()'s initWithAccessToEntityTypes:(current application's EKEntityMaskEvent))

#  Check whether app has access
if (my EventStoreAuthorize:(theEKEventStore)) is not true then return --abort for lack of authorization to EventStore

#	Set Entity Type to calendars that can store events
set EntireCalendarList to theEKEventStore's calendarsForEntityType:0
--> often yields (NSArray) {}

on EventStoreAuthorize:theEKEventStore
	set authorizationStatus to current application's EKEventStore's authorizationStatusForEntityType:0 -- work around enum bug
	if authorizationStatus is 3 then set AuthorizedBool to true
	if authorizationStatus is not 3 then
		set AuthorizedBool to false
		my DialogAuthorizationStatus:authorizationStatus
	end if
	AuthorizedBool
end EventStoreAuthorize:

On occasion, the script yields {}, after which, I have no option to correct the command, but to reboot my Mac. This yield failure appear to occur after the AppleScript has been run several times.
I am perplexed, and have several questions:

  1. What method might work to avoid this EKEventStore failure?
  2. Is there any method that can replace a complete reboot of my Mac?
  3. Is there any method to peer behind EKEventStore’s process, to find the error that is causing EKEventStore to return a null set?
  4. Can the repetition of this AppleScript cause EKEventStore’s allocated memory to fail?

When it happens to me, I just restart “Script Debugger”. I don’t have to restart the Mac.
I suspect it has to do with COcoa’s garbage collection, where it releases the ‘theEKEventStore’ or doesn’t release.
What is the scope of the variable ‘theEKEventStore’?

Here is my script.
I had to move theEventStore, Predicate, calendarList, eventList to a local variable so the garbage collection would release them when the run script ends

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use framework "EventKit"
use scripting additions
-- https://developer.apple.com/documentation/eventkit/retrieving_events_and_reminders?language=objc

property ca : current application
property statusList : {}
property arguments : missing value
property calendarNames : {}

on run
	local theEventStore, Predicate, calendarList, eventList, dateStart, dateEnd, aStatus
	set theEventStore to ca's EKEventStore's (alloc()'s initWithAccessToEntityTypes:(ca's EKEntityMaskEvent)) --ca's class "EKEventStore"'s new()
	if not (my EventStoreAuthorize:theEventStore) then return
	set dateEnd to current date
	set dateStart to dateEnd - 360 * days -- get a full years
	set calendarList to theEventStore's calendarsForEntityType:(ca's EKEntityTypeEvent)
	repeat with i in calendarList
		set i to contents of i
		set end of calendarNames to (i's title as text)
	end repeat
	--set Predicate to theEventStore's predicateForRemindersInCalendars:calendarList -- all events
	set Predicate to theEventStore's predicateForEventsWithStartDate:dateStart endDate:dateEnd calendars:calendarList --{"Work"}
	set eventList to (theEventStore's eventsMatchingPredicate:Predicate) as list
	set text item delimiters to ", "
	set progress description to "EventKit Calendars - " & (count eventList) & " events in (" & (calendarNames as text) & ")"
	set progress total steps to (count eventList)
	set progress completed steps to 0
	set progress additional description to ""
	display alert "Starting enumerating events…" giving up after 1
	if (count eventList) > 0 then
		repeat with i from 1 to count eventList
			set anEvent to item i of eventList
			set myOrganizer to anEvent's organizer()
			if myOrganizer = missing value then
				set myOrganizer to ""
			else
				set myOrganizer to (myOrganizer's |name|()) as text
			end if
			set aStatus to (anEvent's status()) as integer
			set tmp to {(anEvent's title()) as text, myOrganizer, short date string of ((anEvent's startDate()) as date), short date string of ((anEvent's endDate()) as date), item (aStatus + 1) of {"None", "Confirmed", "Tentative", "Canceled"}}
			if aStatus > 0 then -- (anEvent's status()) as integer
				set end of statusList to tmp
			end if
			set item i of eventList to tmp --contents of anEvent to tmp
			set progress completed steps to progress completed steps + 1
			set progress additional description to " " & i & ", Start date: " & (item 3 of tmp) & ", End Date: " & (item 4 of tmp) & ", status: " & (item 5 of tmp) & " - " & item 1 of tmp
			delay 0.6
		end repeat
	else
		display alert "No events were found!" giving up after 5
	end if
	-- theEventStore's dealloc() -- crashes
	beep
end run

on EventStoreAuthorize:theEKEventStore
	local authorizationStatus, AuthorizedBool
	set authorizationStatus to current application's EKEventStore's authorizationStatusForEntityType:0 -- work around enum bug
	if authorizationStatus is 3 then
		set AuthorizedBool to true
	else
		set AuthorizedBool to false
		--my DialogAuthorizationStatus:authorizationStatus
	end if
	AuthorizedBool
end EventStoreAuthorize:

Thank you, as that did solve the problem when I ran it from the Script Debugger application.
However, when I ran the script from another application such as FileMaker Pro, the same response occurred, even with the addition of defining local variables.

  1. Could there be some background engine that quits when ScriptDebugger or the calling application quits
  2. What methods might be used to improve Cocoa’s Garbage Collection?
  3. What method can be employed to release theEKEventStore’s calendarsForEntityType?

Unfortunately, using local variables failed to resolve the problem.
When the script runs correctly, I am able to assemble a list of calendars with their 36 character identifications. I have truncated the 36 digits to show the list format and properties

		set OfficeCalendarList to {title:"Manage", id:"E8...[Sequence contains a total of 36 characters]...58F"}

When EKEventStore is operating, the command yields the following:

set EntireCalendarList to theEKEventStore's calendarsForEntityType:0
--> (NSArray) {
	EKCalendar <0x600...[Sequence contains a total of 14 characters]...800> {title =Manage; type = CalDAV; allowsModify = YES; color = #F691B2FF;}
}

And the command to obtain a predicate based upon ASOC’s predicateForEventsWithStartDate yields the following:

	set thePred to theEKEventStore's predicateForEventsWithStartDate:startNSDate endDate:endNSDate calendars:calendarsToSearch
-->CADEventPredicate start:4/14/23, 11:15 AM; end:4/14/23, 11:30 AM; cals:(
    "x-apple-eventkit:///Calendar/p56"
), exclusions:[]

When the theEKEventStore’s calendarsForEntityType:0 command fails, it yields an empty array. I would like to find the EKEventStore Calendar based upon its 36 character identification, or upon the x-apple-eventkit protocol identifying Calendar/p56.

I have several questions:

  1. What ASOC method finds a specific calendar, based upon the 36 character calendar Identification, such as listed in my OfficeCalendarList using EKEventStore?
  2. What ASOC method finds a calendar based upon a returned predicate using an x-apple-eventkit protocol, such as
"x-apple-eventkit:///Calendar/p56"

It appears to me that the problem experienced with EventKit was an actual failure of EventKit as a class or as an instance. Robert Fern’s solution to quit Script Debugger has been the only successful solution for me. It appears that an instance of EventKit is possibly kept alive by Script Debugger, and quitting the application removes that instance.
In further researching this problem, I came upon a discussion at Microsoft’s Learn EKEventStore Class in which a Microsoft author states that

Because EKEventStore is like a database engine, it should be long-lived, meaning that it should be created and destroyed as little as possible during the lifetime of an application instance. In fact, it’s recommended that once you create one instance of an ED(sic!)EventStore in an application, you keep that reference around for the entire lifetime of the application, unless you’re sure you won’t need it again. Additionally, all calls should go to a single EKEventStore instance. For this reason, the Singleton pattern is recommended for keeping a single instance available.

The author then proceeds to model a method of structuring a singleton to be called once.

As I call my AppleScript up to 30 times per day to schedule various events, and as the script often fails after five to ten calls, I wonder whether this Microsoft explanation is in fact reliable, and might explain EventKit’s initial success and subsequent failure.
If so, how can I write an AppleScript to create a persisting eventStore singleton that will then allow repeated calls of an AppleScript to create multiple calendar events?

I think you just need to move the contents of the special on run() handler to a custom handler where local variables are truly and guaranteed to be local.

Name it, for example, on mainPart(). In the on run() handler, place only the call to this custom handler. Because the variable declared local in on run() leaves the possibility of access from lower-level handlers in the AppleScript language. This may be due to the fact that your script editor does not release the instance.

I modeled my understanding of your instructions using the first part of RobertFern’s script.

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use framework "EventKit"
use scripting additions

on run
	local theEventStore
	set theEventStore to my mainPart()
	if not ((current application's EKEventStore's authorizationStatusForEntityType:0) = 3) then return
	theEventStore
end run

on mainPart()
	local theEventStore
	set theEventStore to current application's EKEventStore's (alloc()'s initWithAccessToEntityTypes:(ca's EKEntityMaskEvent)) --ca's class "EKEventStore"'s new()
end mainPart

KniazidisR, Does my script represent what you intended? Also, do you recommend that I declare

local theEventStore

as both a local in the run function , as well as in the mainPart() function?

No, I suggested something else. Here’s what I suggested:
 

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use framework "EventKit"
use scripting additions
-- "https://developer.apple.com/documentation/eventkit/retrieving_events_and_reminders?language=objc"
property statusList : {}
property calendarNames : {}

on run {}
	main()
end run

on main()
	local theEventStore, Predicate, calendarList, eventList, dateStart, dateEnd, aStatus
	set theEventStore to my EKEventStore's (alloc()'s initWithAccessToEntityTypes:(my EKEntityMaskEvent))
	if not (my EventStoreAuthorize:theEventStore) then return
	set dateEnd to current date
	set dateStart to dateEnd - 360 * days
	set calendarList to theEventStore's calendarsForEntityType:(my EKEntityTypeEvent)
	set calendarNames to (calendarList's title) as list
	set Predicate to theEventStore's predicateForEventsWithStartDate:dateStart endDate:dateEnd calendars:calendarList --{"Work"}
	set eventList to theEventStore's eventsMatchingPredicate:Predicate
	if eventList is {} then
		display alert "No events were found!" giving up after 5
	else
		set eventsCount to count eventList
		set AppleScript's text item delimiters to ", "
		set progress description to "EventKit Calendars - " & eventsCount & " events in (" & calendarNames & ")"
		set progress total steps to eventsCount
		set progress completed steps to 0
		display alert "Starting enumerating events…" giving up after 1
		repeat with anEvent in eventList
			set myOrganizer to anEvent's organizer()
			if myOrganizer = missing value then
				set myOrganizer to ""
			else
				set myOrganizer to (myOrganizer's |name|()) as text
			end if
			set aStatus to (anEvent's status()) as integer
			set aTitle to anEvent's title() as text
			set startDate to short date string of (anEvent's startDate() as date)
			set endDate to short date string of (anEvent's endDate() as date)
			set aSatus to item (aStatus + 1) of {"None", "Confirmed", "Tentative", "Canceled"}
			set end of statusList to {aTitle, myOrganizer, startDate, endDate, aSatus}
			set progress completed steps to progress completed steps + 1
			set progress additional description to " " & progress completed steps & ", Start date: " & startDate & ", End Date: " & endDate & ", status: " & aSatus & " - " & aTitle
		end repeat
	end if
	beep
end main

on EventStoreAuthorize:theEKEventStore
	local authorizationStatus, AuthorizedBool
	set authorizationStatus to my (EKEventStore's authorizationStatusForEntityType:0) -- work around enum bug
	if authorizationStatus is 3 then return true
	return false
end EventStoreAuthorize:

 

1 Like

KniadisR, Thanks for your suggestions. Unfortunately, the outcome was similar to that which I have experienced previously. The theEventStore function quits, after the script is called several times. This is noted following the command

 set calendarList to theEventStore's calendarsForEntityType:(my EKEntityTypeEvent) -> {}

As recommended by RobertFern, when I ran the AppleScript from ScriptDebugger, quitting and restarting ScriptDebugger corrected the problem, EventStore regained its function and yielded results.
Of interest to me was that I experienced the same pattern when I ran the AppleScript from FileMaker Pro. After a few runs, after the AppleScript failed, quitting and restarting FileMaker Pro corrected the problem, and EventStore came back to life, so to speak.
It appears to me that the calling applications, whether they be Script Debugger or FileMaker Pro, play a role with EventKit, that require the application to be quitted and restarted, in order to make EventKit operational. I am not sure what that role is.
I welcome any further insight on correcting or working around this EventKit problem, so that the script can run reliably.