0

When I create an NSPredicate via EKEventStore predicateForRemindersInCalendars; and pass it to EKEventStore fetchRemindersMatchingPredicate:completion:^ I can loop through the reminders array provided by the completion code block, but when I try to store a reference to the reminders array, or create a copy of the array into a local variable or instance variable, both array's remain empty. The reminders array is never copied to them.

This is the method I am using, in the method, I create a predicate, pass it to the event store and then loop through all of the reminders logging their title via NSLog. I can see the reminder titles during runtime thanks to NSLog, but the local arrayOfReminders object is empty. I also try to add each reminder into an instance variable of NSMutableArray, but once I leave the completion code block, the instance variable remains empty.

Am I missing something here? Can someone please tell me why I can't grab a reference to all of the reminders for use through-out the app? I am not having any issues at all accessing and storing EKEvents, but for some reason I can't do it with EKReminders.

- (void)findAllReminders {

    NSPredicate *predicate = [self.eventStore predicateForRemindersInCalendars:nil];

    __block NSArray *arrayOfReminders = [[NSArray alloc] init];

    [self.eventStore fetchRemindersMatchingPredicate:predicate completion:^(NSArray *reminders) {

        arrayOfReminders = [reminders copy]; //Does not work.

        for (EKReminder *reminder in reminders) {

            [self.remindersForTheDay addObject:reminder];

            NSLog(@"%@", reminder.title);
       }
   }];

   //Always = 0;
   if ([self.remindersForTheDay count]) {
       NSLog(@"Instance Variable has reminders!");
   }

    //Always = 0;
   if ([arrayOfReminders count]) {
       NSLog(@"Local Variable has reminders!");
   }
}

The eventStore getter is where I perform my instantiation and get access to the event store.

- (EKEventStore *)eventStore {
   if (!_eventStore) {
       _eventStore = [[EKEventStore alloc] init];

        //respondsToSelector indicates iOS 6 support.
       if ([_eventStore respondsToSelector:@selector(requestAccessToEntityType:completion:)]) {
           //Request access to user calendar
           [_eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
               if (granted) {
                   NSLog(@"iOS 6+ Access to EventStore calendar granted.");
               } else {
                   NSLog(@"Access to EventStore calendar denied.");
               }
           }];

            //Request access to user Reminders
           [_eventStore requestAccessToEntityType:EKEntityTypeReminder completion:^(BOOL granted, NSError *error) {
               if (granted) {
                   NSLog(@"iOS 6+ Access to EventStore Reminders granted.");
               } else {
                   NSLog(@"Access to EventStore Reminders denied.");
               }
           }];
       } else { //iOS 5.x and lower support if Selector is not supported
           NSLog(@"iOS 5.x < Access to EventStore calendar granted.");
       }

        for (EKCalendar *cal in self.calendars) {
           NSLog(@"Calendar found: %@", cal.title);
       }

       [_eventStore reset];
   }

    return _eventStore;
}

Lastly, just to show that I am initializing my remindersForTheDay instance variable using lazy instantiation.

- (NSMutableArray *)remindersForTheDay {
   if (!_remindersForTheDay) _remindersForTheDay = [[NSMutableArray alloc] init];

    return _remindersForTheDay;
}

I've read through the Apple documentation and it doesn't provide any explanation that I can find to answer this. I read through the Blocks Programming docs and it states that you can access local and instance variables without issues from within a block, but for some reason, the above code does not work.

Any help would be greatly appreciated, I've scoured Google for answers but have yet to get this figured out.

Thanks everyone!

Johnathon.

UPDATE:

I have since created a new app that does nothing but instance an event store, setup a table view, setup a KVO and updates the UI as per the recommendations provided. The code below is the app in its entirety. The UITableView is never updated with the reminders content. The app launches, and stares at me. The reloadData method invocation should eventually have the UI updated with the reminder data.

The reminder data exists in self.reminders, because my NSLog outputs that I have 372 objects in the array.

Lastly, if I touch the UITableView on the device, the app crashes with "'NSInternalInconsistencyException', reason: 'UITableView dataSource must return a cell from tableView:cellForRowAtIndexPath:'".

@import EventKit;
#import "RTViewController.h"

@interface RTViewController ()
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (strong, nonatomic) EKEventStore *eventStore;
@property (strong, nonatomic) NSArray *reminders;
@property (nonatomic) BOOL accessGranted;
@property (strong, nonatomic) NSDate *date;
@end

@implementation RTViewController

- (EKEventStore *)eventStore {
    if (!_eventStore) {
        _eventStore = [[EKEventStore alloc] init];

        if ([_eventStore respondsToSelector:@selector(requestAccessToEntityType:completion:)]) {
            [_eventStore requestAccessToEntityType:EKEntityTypeReminder completion:^(BOOL granted, NSError *error) {
                if (granted) self.accessGranted = YES;
                else self.accessGranted = NO;
            }];
        }

        [_eventStore reset];
    }

    return _eventStore;
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.reminders = [[NSArray alloc] init];
    self.date = [NSDate date]; //Returns NSDate allocated/init to NOW

    NSPredicate *predicate = [self.eventStore predicateForRemindersInCalendars:nil]; //nil will cause all calendars to be used.

    //Ran on different thread.
    [self.eventStore fetchRemindersMatchingPredicate:predicate completion:^(NSArray *reminders) {
        _reminders = [reminders copy]; //NEVER happens.
        [self updateReminders];
    }];

    //Setup the "radio" and watch ourself for when self.reminders is changed from the other thread
    [self addObserver:self forKeyPath:@"reminders" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:NULL];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    //If no data exists, then this will return 0 so no rows created.
    return [self.reminders count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"cell"];

    if (self.reminders) {
        EKReminder *reminder = self.reminders[indexPath.row];
        cell.detailTextLabel.text = reminder.title;
    }

    return cell;
}

//Called by self when self.remidners is changed. - NEVER gets called.
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    [self.tableView reloadData];
}

- (void)updateReminders {
    NSLog(@"%d objects found in array", [self.reminders count]);
    [self.tableView reloadData];
}
@end
4

1 回答 1

1

您看到的问题是“完成块”比您标记为“Always = 0”的代码执行“晚得多”(在计算机时间内)。

一般来说,如果一个块在 Apple API 中被称为“完成”,它将异步运行:Apple 将保存您的块,运行您请求的 fetch,然后使用结果调用您的块。当方法返回时,结果不会准备好fetchRemindersMatchingPredicate:completion:,只有当你的完成块被调用时。他们说的文档证实了这一点:“此方法异步获取提醒。”。

所以,你需要将你的逻辑移动到回调中……</p>

- (void)findAllReminders {
  NSPredicate *predicate = [self.eventStore predicateForRemindersInCalendars:nil];

  // I removed the arrayOfReminders, I think it wasn’t necessary, and was just to show
  // your problem.

  [self.eventStore fetchRemindersMatchingPredicate:predicate completion:^(NSArray *reminders) {
    for (EKReminder *reminder in reminders) {
        [self.remindersForTheDay addObject:reminder];
        // You probably were seing this logging, right?
        NSLog(@"%@", reminder.title);
    }
    [self refreshUI];
  }];
}

- (void)refreshUI {
  // Do whatever you need to do with the UI in refreshUI, inside it, the
  // self.remindersForTheDay will be already filled.
}

问题是你的 eventStore 方法,因为你可以看到它也有一个“完成”回调,它并不总是能够立即返回一个值,所以你需要稍微改变你的逻辑。

// Instead of returning the value directly, we will return the value in our own
// completion callback.
// The first argument will be a block that accepts an EKEventStore as parameter.
- (void)performWithEventStore:(void (^)(EKEventStore *eventStore))completion {
  if (!completion) return; // if no completion callback is provided, just return.

  if (!_eventStore) {
    _eventStore = [[EKEventStore alloc] init];

    if ([_eventStore respondsToSelector:@selector(requestAccessToEntityType:completion:)]) {
      // Request access to user calendar
      [_eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
        if (granted) {
          // We have been granted access, invoke our completion, and let them do the
          // work
          // Be careful, because here, we are in an arbitrary queue, you might want to
          // switch to the main queue here or later.
          completion(_eventStore);
        } else {
          // you haven’t been given access, you should probably show an error
          NSLog(@"Access to EventStore calendar denied.");
          // you might want to nilify _eventStore here.
        }
      }];

      // I think you cannot use the same store to access reminders and event, but I’m
      // not sure. You might need to different instances of EKEventStore (following
      // this example, two methods: performWithEventsStore and
      // performWithRemindersStore)
    } else { //iOS 5.x and lower support if Selector is not supported
      // Because 5.x gives access immediately, just call our completion block
      completion(_eventStore);
    }
  } else {
    // A previous call to this method created the event store, just invoke the
    // callback with the saved event store.
    completion(_eventStore);
  }
}

然后,findAllReminders方法必须更改如下:

- (void)findAllReminders {
  NSPredicate *predicate = [self.eventStore predicateForRemindersInCalendars:nil];

  [self performWithEventStore:^(EKEventStore *eventStore) {
    // We are using the eventStore parameter of the block
    [eventStore fetchRemindersMatchingPredicate:predicate completion:^(NSArray *reminders) {
      for (EKReminder *reminder in reminders) {
          [self.remindersForTheDay addObject:reminder];
          // You probably were seing this logging, right?
          NSLog(@"%@", reminder.title);
      }
      [self refreshUI];
    }];
  }];
}

是的,这是完成块内的完成块。这是因为您在这里处理两个异步过程:向用户请求授权,并从事件存储中获取事件。

希望能帮助到你!

于 2013-06-30T22:17:44.830 回答