正在从 Swift 中的日历中获取提醒
Fetching reminders from calendars in Swift
从各种日历中获取提醒的线程安全方式是什么?我只是想计算所有提醒并打印出来。打印有效但计数无效。是否存在竞争条件,因为获取提醒是异步的?
func loadFromCalendars(cals: [EKCalendar], completed: (NSError?)->()) {
// STEP 1 OF CREATING AN OVERALL COMPLETION BLOCK: Create a dispatch group.
let loadCalendarServiceGroup: dispatch_group_t = dispatch_group_create()
// Define errors to be processed when everything is complete.
// One error per service; in this example we'll have two
let configError: NSError? = nil
let preferenceError: NSError? = nil
var reminderCounter = 0
let eventStore : EKEventStore = EKEventStore()
eventStore.requestAccessToEntityType(EKEntityType.Event, completion: {
granted, error in
if (granted) && (error == nil) {
print("granted \(granted)")
print("error \(error)")
}
})
// Go through calendars.
for cal in cals {
let remindersPredicate = eventStore.predicateForRemindersInCalendars([cal])
// STEP 2 OF CREATING AN OVERALL COMPLETION BLOCK: Adding tasks to a dispatch group
dispatch_group_enter(loadCalendarServiceGroup)
eventStore.fetchRemindersMatchingPredicate(remindersPredicate) {
// MARK: Begininning of thread
reminders in
_ = (reminders!).map {
// TRYING TO COUNT HERE THE REMINDERS. ALWAYS PRINTS 0!
reminder -> EKReminder in
print(reminder.title)
reminderCounter += 1
return reminder
}
dispatch_async(dispatch_get_main_queue()) {
self.sendChangedNotification() // refreshes the UI
}
}
// STEP 3 OF CREATING AN OVERALL COMPLETION BLOCK: Leave dispatch group. This must be done at the end of the completion block.
dispatch_group_leave(loadCalendarServiceGroup)
// MARK: End of thread
}
// STEP 4 OF CREATING AN OVERALL COMPLETION BLOCK: Acting when the group is finished
dispatch_group_notify(loadCalendarServiceGroup, dispatch_get_main_queue(), {
print("************ reminder count: \(reminderCounter) ****************")
// Assess any errors
var overallError: NSError? = nil;
if configError != nil || preferenceError != nil {
// Either make a new error or assign one of them to the overall error. Use '??', which is the "nil Coalescing Operator". It's syntactic sugar for the longer expression:
// overallError = configError != nil ? configError : preferenceError
overallError = configError ?? preferenceError
} // Now call the final completion block
// Call the completed function passed to loadCalendarHelper. This will contain the stuff that I want executed in the end.
completed(overallError)
})
}
编辑
感谢伟大的提示,jtbandes!我简化了我的代码(很多!) 一个问题 - 我正在链接一些改变结果数据结构的函数。我怎样才能使下面的代码 groupArrayBy() 线程安全?
public extension SequenceType {
/// Categorises elements of self into a dictionary, with the keys given by keyFunc
func groupArrayBy<U : Hashable>(@noescape keyFunc: Generator.Element -> U) -> [U:[Generator.Element]] {
var dict: [U:[Generator.Element]] = [:]
for el in self {
let key = keyFunc(el)
if case nil = dict[key]?.append(el) { dict[key] = [el] }
}
return dict
}
}
func loadFromCalendars(cals: [EKCalendar], completed: (NSError?)->()) {
let configError: NSError? = nil
let preferenceError: NSError? = nil
withEstore { // retrieves the EKEventStore
estore in
let predicate = estore.predicateForRemindersInCalendars(cals)
estore.fetchRemindersMatchingPredicate(predicate) { reminders in
print("Number of reminders: \(reminders?.count ?? 0)") // Prints correct result
let list = (reminders!).map {
// this map still works, it seems thread-safe
reminder -> ReminderWrapper in
return ReminderWrapper(reminder: reminder) // This still works. ReminderWrapper is just a wrapper class. Not very interesting...
}.groupArrayBy { [=12=].reminder.calendar } // ERROR: groupArrayBy doesn't seem to be thread-safe!
print("Number of reminders: \(Array(list.values).count)") // Prints a too low count. Proves that groupArrayBy isn't thread-safe.
dispatch_async(dispatch_get_main_queue()) {
self.sendChangedNotification() // refreshes the UI
completed(configError ?? preferenceError)
}
}
}
}
应对此代码进行一些更改:
dispatch_group_leave(loadCalendarServiceGroup)
必须在 内部 fetchRemindersMatchingPredicate
块。否则,您传递给 dispatch_group_notify
的块将在提取完成之前执行,这完全违背了使用组的目的。
requestAccessToEntityType
调用也是异步的,但您的代码只是在启动访问请求后继续执行,而不是等待它完成。您可能希望将完成块链接在一起。
您正在请求访问 .Event
类型,但您可能想要 .Reminder
.
reminderCounter += 1
不是线程安全的。您可能希望在更改计数器之前 dispatch_async
进入串行队列(因此线程之间没有争用),或者您可以使用函数的 OSAtomicAdd family。
而不是_ = (reminders!).map { reminder in ... }
,我建议你只使用for reminder in reminders { ... }
。
但是……
我觉得你做的事情太复杂了。
请注意,predicateForRemindersInCalendars
采用 数组 日历。您可以简单地传递所有日历,cals
,以获得包含所有日历的单个谓词,以及 运行 单个查询:
let predicate = eventStore.predicateForRemindersInCalendars(cals)
eventStore.fetchRemindersMatchingPredicate(predicate) { reminders in
print("Number of reminders: \(reminders?.count ?? 0)")
dispatch_async(dispatch_get_main_queue()) {
self.sendChangedNotification() // refreshes the UI
completed(configError ?? preferenceError)
}
}
从各种日历中获取提醒的线程安全方式是什么?我只是想计算所有提醒并打印出来。打印有效但计数无效。是否存在竞争条件,因为获取提醒是异步的?
func loadFromCalendars(cals: [EKCalendar], completed: (NSError?)->()) {
// STEP 1 OF CREATING AN OVERALL COMPLETION BLOCK: Create a dispatch group.
let loadCalendarServiceGroup: dispatch_group_t = dispatch_group_create()
// Define errors to be processed when everything is complete.
// One error per service; in this example we'll have two
let configError: NSError? = nil
let preferenceError: NSError? = nil
var reminderCounter = 0
let eventStore : EKEventStore = EKEventStore()
eventStore.requestAccessToEntityType(EKEntityType.Event, completion: {
granted, error in
if (granted) && (error == nil) {
print("granted \(granted)")
print("error \(error)")
}
})
// Go through calendars.
for cal in cals {
let remindersPredicate = eventStore.predicateForRemindersInCalendars([cal])
// STEP 2 OF CREATING AN OVERALL COMPLETION BLOCK: Adding tasks to a dispatch group
dispatch_group_enter(loadCalendarServiceGroup)
eventStore.fetchRemindersMatchingPredicate(remindersPredicate) {
// MARK: Begininning of thread
reminders in
_ = (reminders!).map {
// TRYING TO COUNT HERE THE REMINDERS. ALWAYS PRINTS 0!
reminder -> EKReminder in
print(reminder.title)
reminderCounter += 1
return reminder
}
dispatch_async(dispatch_get_main_queue()) {
self.sendChangedNotification() // refreshes the UI
}
}
// STEP 3 OF CREATING AN OVERALL COMPLETION BLOCK: Leave dispatch group. This must be done at the end of the completion block.
dispatch_group_leave(loadCalendarServiceGroup)
// MARK: End of thread
}
// STEP 4 OF CREATING AN OVERALL COMPLETION BLOCK: Acting when the group is finished
dispatch_group_notify(loadCalendarServiceGroup, dispatch_get_main_queue(), {
print("************ reminder count: \(reminderCounter) ****************")
// Assess any errors
var overallError: NSError? = nil;
if configError != nil || preferenceError != nil {
// Either make a new error or assign one of them to the overall error. Use '??', which is the "nil Coalescing Operator". It's syntactic sugar for the longer expression:
// overallError = configError != nil ? configError : preferenceError
overallError = configError ?? preferenceError
} // Now call the final completion block
// Call the completed function passed to loadCalendarHelper. This will contain the stuff that I want executed in the end.
completed(overallError)
})
}
编辑 感谢伟大的提示,jtbandes!我简化了我的代码(很多!) 一个问题 - 我正在链接一些改变结果数据结构的函数。我怎样才能使下面的代码 groupArrayBy() 线程安全?
public extension SequenceType {
/// Categorises elements of self into a dictionary, with the keys given by keyFunc
func groupArrayBy<U : Hashable>(@noescape keyFunc: Generator.Element -> U) -> [U:[Generator.Element]] {
var dict: [U:[Generator.Element]] = [:]
for el in self {
let key = keyFunc(el)
if case nil = dict[key]?.append(el) { dict[key] = [el] }
}
return dict
}
}
func loadFromCalendars(cals: [EKCalendar], completed: (NSError?)->()) {
let configError: NSError? = nil
let preferenceError: NSError? = nil
withEstore { // retrieves the EKEventStore
estore in
let predicate = estore.predicateForRemindersInCalendars(cals)
estore.fetchRemindersMatchingPredicate(predicate) { reminders in
print("Number of reminders: \(reminders?.count ?? 0)") // Prints correct result
let list = (reminders!).map {
// this map still works, it seems thread-safe
reminder -> ReminderWrapper in
return ReminderWrapper(reminder: reminder) // This still works. ReminderWrapper is just a wrapper class. Not very interesting...
}.groupArrayBy { [=12=].reminder.calendar } // ERROR: groupArrayBy doesn't seem to be thread-safe!
print("Number of reminders: \(Array(list.values).count)") // Prints a too low count. Proves that groupArrayBy isn't thread-safe.
dispatch_async(dispatch_get_main_queue()) {
self.sendChangedNotification() // refreshes the UI
completed(configError ?? preferenceError)
}
}
}
}
应对此代码进行一些更改:
dispatch_group_leave(loadCalendarServiceGroup)
必须在 内部fetchRemindersMatchingPredicate
块。否则,您传递给dispatch_group_notify
的块将在提取完成之前执行,这完全违背了使用组的目的。requestAccessToEntityType
调用也是异步的,但您的代码只是在启动访问请求后继续执行,而不是等待它完成。您可能希望将完成块链接在一起。您正在请求访问
.Event
类型,但您可能想要.Reminder
.reminderCounter += 1
不是线程安全的。您可能希望在更改计数器之前dispatch_async
进入串行队列(因此线程之间没有争用),或者您可以使用函数的 OSAtomicAdd family。而不是
_ = (reminders!).map { reminder in ... }
,我建议你只使用for reminder in reminders { ... }
。
但是……
我觉得你做的事情太复杂了。
请注意,predicateForRemindersInCalendars
采用 数组 日历。您可以简单地传递所有日历,cals
,以获得包含所有日历的单个谓词,以及 运行 单个查询:
let predicate = eventStore.predicateForRemindersInCalendars(cals)
eventStore.fetchRemindersMatchingPredicate(predicate) { reminders in
print("Number of reminders: \(reminders?.count ?? 0)")
dispatch_async(dispatch_get_main_queue()) {
self.sendChangedNotification() // refreshes the UI
completed(configError ?? preferenceError)
}
}