iOS Parse (Reduce API Requests) 每周在同一天执行一次函数

iOS Parse (Reduce API Requests) Perform function once a week every week on the same day

使用 Parse,我试图减少要解析的网络调用数量,以限制我对非必要数据的 API 请求。为此,我认为最好每周只在同一天调用一次函数,并将与前一周的任何差异上传到安装 class(在我的情况下用于同步推送通知渠道)

那么如何每周在特定的一天调用一次函数呢?如果这一天已经过去了,你会怎么办?假设您希望每个星期四都发生一些事情,但用户直到星期四才打开应用程序,您应该在星期四同步数据?

您要做的第一件事是检查今天是否是您希望同步数据的日子。因此,例如,我希望每个星期四调用一个函数来将对象数组同步到 'channels' 列。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    if ([self isTodayThursday]) {

    } else {

    }
}

- (BOOL) isTodayThursday {
    NSDateFormatter *nameOfDayOfWeekFormatter = [[NSDateFormatter alloc] init];
    [nameOfDayOfWeekFormatter setDateFormat:@"EEEE"];

    if ([[nameOfDayOfWeekFormatter stringFromDate:[NSDate date]] isEqualToString:@"Thursday"]) {
    return YES;
    }
    return NO;
}

很简单,这里我们要检查星期几的名称是否为星期四。现在我们要确保我们没有堆叠同步,并确保我们今天已经同步或还没有同步,如果是星期四的话。我们通过验证 NSUserDefaults 中的对象来做到这一点:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    if ([self isTodayThursday]) {
        if ([[[NSUserDefaults standardUserDefaults] objectForKey:@"lastSynced"] isEqualToString:[[self dateFormatter] stringFromDate:[NSDate date]]]) {
            //So we check against a custom date formatter that we want of the last sync date we performed, and if it's already been synced today, then do nothing
        } else {
            //if not, sync plist (or whatever we want) to channels array
            [self syncPlistToParse];
        }
    } else {

    }
}

- (NSDateFormatter *)dateFormatter {

    NSDateFormatter *formatedDate = [[NSDateFormatter alloc] init];
    [formatedDate setDateFormat:@"yyyyMMdd"];
    NSLog(@"Today is %@", [formatedDate stringFromDate:[NSDate date]]);
    return formatedDate;
}

将 plist 同步到 PFInstallation 通道数组

-(void)syncPlistToParse {
    //First obtain whatever data you have from where ever you store it for the week. I chose to save all the data in a plist until the next sync date to reduce API calls.
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *path = [paths objectAtIndex:0];
    NSString *plistPath = [path stringByAppendingPathComponent:@"AlertSubscriptions.plist"];
    NSMutableArray *array = [[NSMutableArray alloc] initWithContentsOfFile:plistPath];

    PFInstallation *currentInstallation = [PFInstallation currentInstallation];
    [currentInstallation addUniqueObjectsFromArray:array forKey:@"channels"];
    [currentInstallation saveInBackgroundWithBlock:^(BOOL success, NSError *error) {
      if (success) {
        //if successful save lastSynced date to NSUserDefaults to today
        [[NSUserDefaults standardUserDefaults] setObject:[[self dateFormatter] stringFromDate:[NSDate date]] forKey:@"lastSynced"];
      } else {
       //It wasn't a successful save so you can do whatever you want on an unsuccessful save, but I chose to use `saveEventually` as a back-up for connectivity issues etc
        PFInstallation *currentInstallation = [PFInstallation currentInstallation];
        [currentInstallation addUniqueObjectsFromArray:array forKey:@"channels"];
        [currentInstallation saveEventually];
      }
  }];
}

那么,如果今天 不是 星期四,我们如何确定它是在上次同步日期之前还是之后,以及我们如何确定如何设置下一个同步日期?

让我们以这些日期为例:

                  June 2015
---------------------------------------------
|SUN | MON | TUES | WED | THURS | FRI | SAT |
---------------------------------------------
|    |     |      |     |  18   | 19  | 20  |
---------------------------------------------
| 21 |  22 |  23  |  24 |  25   | 26  | 27  |
---------------------------------------------
| 28 |  29 |  30  |  1  |  02   | 03  | 04  |
---------------------------------------------

假设最后一次同步的星期四是 6 月 18 日,而用户直到 6 月 26 日(星期五)才打开他们的应用程序,我们需要对此进行验证。我们已经检查过今天是否是星期四,我们知道不是,因为它是 26 号星期五。所以我们需要检查和平衡两个差异,最重要的是,我们需要检查那个星期五是否在当前同步周期(第 19 个星期五)的同一周内,但在这种情况下,因为当前周周期已经通过了,因为我们直到下一个周期(应该是 6 月 25 日)才打开它

    if ([self isTodayThursday]) {
        if ([[[NSUserDefaults standardUserDefaults] objectForKey:@"lastSynced"] isEqualToString:[[self dateFormatter] stringFromDate:[NSDate date]]]) {
            //So we check against a custom date formatter that we want of the last sync date we performed, and if it's already been synced today, then do nothing
        } else {
            //if not, sync plist (or whatever we want) to channels array
            [self syncPlistToParse];
        }
    } else {
        //Not Thursday. So we need to check if its within the same week of the sync cycle or passed that date
        int lastSyncDate = [[self lastSyncDate] intValue];
        NSLog(@"lastSyncDate int %d", lastSyncDate);
        int nextSyncDate = [[self nextSyncDate] intValue];
        NSLog(@"nextSyncDate int %d", nextSyncDate);

        if (lastSyncDate < nextSyncDate) {
            NSLog(@"next sync date is this coming thursday");
        } else {
            // if not before thursday (already passed Thursday), sync and save today as lastSynced
            [self syncPlistToParse];
        }
    }
}

- (NSString *)lastSyncDate {
    NSString *date = [[NSUserDefaults standardUserDefaults] objectForKey:@"lastSynced"];
    return date;
}

- (NSString *)nextSyncDate {

    NSCalendar *cal = [NSCalendar currentCalendar];
    NSDate *lastSync = [[self dateFormatter] dateFromString:[self lastSyncDate]];
    NSDateComponents *components = [cal components:(NSCalendarUnitDay) fromDate:lastSync];
    [components setDay: +[self daysUntilNearestThursday]];
    NSDate *nextSync = [cal dateByAddingComponents:components toDate:lastSync options:0];    
    return [[self dateFormatter] stringFromDate:nextSync];
}

-(int)daysUntilNearestThursday {
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"EEEE"];
    //Note the day of the week is including today thats why it's always +1
    if ([[dateFormatter stringFromDate:[NSDate date]] isEqualToString:@"Monday"]) {
      return 4;
    }
    if ([[dateFormatter stringFromDate:[NSDate date]] isEqualToString:@"Tuesday"]) {
      return 3;
    }
    if ([[dateFormatter stringFromDate:[NSDate date]] isEqualToString:@"Wednesday"]) {
      return 2;
    }
    if ([[dateFormatter stringFromDate:[NSDate date]] isEqualToString:@"Friday"]) {
      return 7;
    }
    if ([[dateFormatter stringFromDate:[NSDate date]] isEqualToString:@"Saturday"]) {
      return 6;
    }
    if ([[dateFormatter stringFromDate:[NSDate date]] isEqualToString:@"Sunday"]) {
      return 5;
    }
    return 0;
}

注意 nextSyncDate 是什么并不重要,因为我们总是首先检查它是否是星期四,然后相应地采取行动

这是我的个人意见,有人问过我这个问题,这就是我想出的。我觉得那里有一个更简单的解决方案,可以将 nextSync 准确地重置为最近的星期四,而不是 [components setDay: +[self daysUntilNearestThursday]]; 因此,无论如何,请添加 input/suggestions 和评论,以便为未来的问题寻求者提供更好的机会。我认为 Parse 很棒,但同时它也是一项业务,他们必须采取相应的行动,但是某些事情,例如 API 请求可扩展应用程序(2,000 多个用户)的所有内容是一种不好的做法,如果您打算保留开发人员。这是我为帮助我的朋友而想到的另一种选择,它需要工作,并不是说这是唯一的方法,只是我认为对他们的项目有用的方法。请不要害羞,添加编辑或更正。

在实践中,我发现固定间隔在更多情况下比日历里程碑更有意义。有了它,加上很少的 NSDate 逻辑,我可以让我的模型保证它的数据最多过时 N 秒。

为此,我让模型单例仅跟踪上次更新的日期:

// initialize this to [NSDate distantPast]
@property(nonatomic,strong) NSDate *lastUpdate;

接口还提供了异步更新的方法,如:

- (void)updateWithCompletion:(void (^)(BOOL, NSError *))completion;

我重写 lastUpdate 的合成 getter/setter 来包装持久性:

// user defaults in this case, but there are several ways to persist a date
- (NSDate *)lastUpdate {
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    return [defaults valueForKey:@"lastUpdate"];
}

- (void)setLastUpdate:(NSDate *)lastUpdate {
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setValue:lastUpdate forKey:@"lastUpdate"];
    [defaults synchronize];
}

最后,异步更新不透明地决定当前数据是否足够好,或者我们是否应该调用 parse.com api...

- (void)updateWithCompletion:(void (^)(BOOL, NSError *))completion {

    NSTimeInterval sinceLastUpdate = -[self.lastUpdate timeIntervalSinceNow];
    NSTimeInterval updatePeriod = [self updatePeriod];
    if (sinceLastUpdate < updatePeriod) {
        // our current data is new enough, so
        return completion(YES, nil);
    } else {
        // our current data is stale, so call parse.com to update...
        [...inBackgroundWithBlock:(NSArray *objects, NSError *error) {
            if (!error) {
                // finish the update here and persist the objects, then...
                self.lastUpdate = [NSDate date];
                completion(YES, nil);
            } else {
                completion(NO, error);
            }
        }];
    }
}

方法 updatePeriod 回答任何 NSTimeInterval 应用程序认为可接受的数据年龄。通常,我会以相当高的频率(比如每天)从 parse.config 获取此信息。这样,我可以调整模型更新的频率,因为我认为适合野外客户。

所以用很少的 NSDate 逻辑,我用它来保持客户端 "up-to-date-enough",甚至 "enough" 部分也可以动态决定。

编辑 - 我们仍然可以保持简洁并将我们的模型到期时间设置为日历日。我会这样做:

- (NSDate *)lastSaturday {

    NSDate *now = [NSDate date];
    NSCalendar *calendar = [NSCalendar currentCalendar];

    NSDate *startOfWeek;
    [calendar rangeOfUnit:NSCalendarUnitWeekOfMonth startDate:&startOfWeek interval:NULL forDate:now];

    NSDateComponents *components = [[NSDateComponents alloc] init];
    [components setDay:-1];
    return [calendar dateByAddingComponents:components toDate:startOfWeek options:0];
}

现在,不是在间隔到期时更新,而是在上周六(或您想要的任何工作日)之前更新...通过调整 setDay:-n 进行调整)

// change the date condition in updateWithCompletion
if (self.lastUpdate == [self.lastUpdate earlierDate:[self lastSaturday]) {
    // do the update
} else {
    // no need to update
}