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:
What method might work to avoid this EKEventStore failure?
Is there any method that can replace a complete reboot of my Mac?
Is there any method to peer behind EKEventStore’s process, to find the error that is causing EKEventStore to return a null set?
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.
Could there be some background engine that quits when ScriptDebugger or the calling application quits
What methods might be used to improve Cocoa’s Garbage Collection?
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:
What ASOC method finds a specific calendar, based upon the 36 character calendar Identification, such as listed in my OfficeCalendarList using EKEventStore?
What ASOC method finds a calendar based upon a returned predicate using an x-apple-eventkit protocol, such as
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:
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.