swift - 在 Swift 中从日历中获取提醒

标签 swift multithreading race-condition reminders ekeventstore

从各种日历中获取提醒的线程安全方式是什么?我只是想计算所有提醒并打印它们。打印可以工作,但计数不行。是否存在竞争条件,因为获取提醒是异步的?

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 { $0.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)
            }
        }
    }
}

最佳答案

应对此代码进行一些更改:

  1. dispatch_group_leave(loadCalendarServiceGroup) 必须内部 fetchRemindersMatchingPredicate block 。否则,您传递给 dispatch_group_notify 的 block 将在提取完成之前执行,这完全违背了使用组的目的。

  2. requestAccessToEntityType 调用也是异步的,但您的代码只是在启动访问请求后继续,而不等待其完成。您可能希望将完成 block 链接在一起。

  3. 您正在请求访问 .Event 类型,但您可能需要 .Reminder

  4. reminderCounter += 1 不是线程安全的。您可能希望在更改计数器之前将 dispatch_async 发送到串行队列(这样线程之间就不会出现争用),或者您可以使用 OSAtomicAdd family函数。

  5. 我建议您不要使用_ = (reminders!).map {reminders 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)
    }
}

关于swift - 在 Swift 中从日历中获取提醒,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36085907/

相关文章:

ios - 我可以从数组中的第二个元素开始填充我的表格 View 吗? ( swift )

swift - 用于 Swift 编码的 iOS 应用程序的 Ffmpeg

ios - 删除和替换 subview 时如何处理它们?

dart - 多个Future/Timer同时完成时是否存在比赛条件

swift - 更改 SpriteKit 中的父节点

android - Android 中的多线程

Java多线程问题——消费者/生产者模式

c - 如何使用 sem_trywait()?

sql - INSERT-SELECT 查询可以受竞争条件的约束吗?

go - Race(?) with Mutex - map 中的数据损坏