iOS定时Ping小工具

背景

某天深夜,正当我想登录博客的时候,发现居然无法访问。Ping了一下,的确是不通,立即找到主机提供商,问题也很快解决。但由于我也有几天没有登录博客,我具体也不知道这个情况持续了多久,感觉依赖主机提供商的邮件很不靠谱。于是,有了一个念头,就是自己动手丰衣足食。

构想

  1. 获知网站当前网络是否畅通
  2. 可以定时自动运行
  3. 有报告机制,可以及时知道情况
  4. 最好可以随时随地使用
  5. 可以有一些更复杂的定制(例如重试次数等

上述的某些功能用脚本还是可以很好实现的,但是要及时的话,还是移动客户端有优势,而且我也舍不得24小时开着主机(心疼电费)。

技术实现

基本的Ping功能

能Ping通网站是最起码的需求,所以第一个先实现这个基本功能。找了一些资料,原来Apple就有一个SimplePing封装的wrapper,但是比较简陋。我于是找了一些SimplePingHelper来修改,以满足需求。其中我使用了这个Ping,后来增加了一些错误处理和丢失数返回。基本的Ping功能已经具备了,我在考虑以后再完善helper的功能。

后台定时运行

大家一直都认为iOS的多任务比较弱,但其实要做到应用在后台长时间运行还是可行的。最早的时候,大家都是使用后台循环播放无声音乐来实现长时间后台任务。不过,这种方法耗电还是比较厉害的,虽然是无声音乐,但是长时间下来对电量还是有所影响。 于是我还是采用了- (UIBackgroundTaskIdentifier)beginBackgroundTaskWithExpirationHandler:(void(^)(void))handler来实现后台任务,不过这个方法最多只有3分钟的运行时间,所以需要利用一个定时器来定时播放无声音乐,然后重新申请后台运行时间以达到长时间运行的目的。

#import <UIKit/UIKit.h>

@interface PINKLongTimeBackground : NSObject

+ (instancetype)shareInstance;

- (void)start;
- (void)stop;

@end

#import "PINKLongTimeBackground.h"

#import <AVFoundation/AVFoundation.h>
#import <RNTimer/RNTimer.h>

@interface PINKLongTimeBackground ()

@property (nonatomic) UIBackgroundTaskIdentifier taskId;
@property (nonatomic, strong) RNTimer *playerTimer;
@property (nonatomic, strong) AVAudioPlayer *mutePlayer;

@end

@implementation PINKLongTimeBackground

+ (instancetype)shareInstance
{
    PINKLongTimeBackground __block *_shareInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _shareInstance = [[PINKLongTimeBackground alloc] init];
    });

    return _shareInstance;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        NSString *filePath = [[NSBundle mainBundle] pathForResource:@"mute" ofType:@"wav"];
        self.mutePlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:filePath] error:nil];
        self.mutePlayer.numberOfLoops = 0;
    }

    return self;
}

- (void)start
{
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil];
    [[AVAudioSession sharedInstance] setActive:YES error:nil];

    self.taskId =
    [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
    self.playerTimer = [RNTimer repeatingTimerWithTimeInterval:60 block:^{
        if ([[UIApplication sharedApplication] backgroundTimeRemaining] < 61.0) {
            [self.mutePlayer play];
            UIBackgroundTaskIdentifier tmpID = self.taskId;
            self.taskId =
            [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
            [[UIApplication sharedApplication] endBackgroundTask:tmpID];
        }
    }];
}

- (void)stop
{
    self.playerTimer = nil;
    [[UIApplication sharedApplication] endBackgroundTask:self.taskId];
    [self.mutePlayer stop];
}

@end

关键在于用定时器每间隔60秒检查剩余的后台任务时间,不足61秒的时候就立即播放无声音乐,然后重新申请后台任务。此方法参照http://my.oschina.net/u/1386081/blog/277380,不过定时器用的是我自己修改了的RNTimer。至于,定时运行也是通过RNTimer这个东西去实现的。
上面的已经把后台定时运行的问题解决了,但是手机也会有重启的时候,每次重启以后都要重新打开应用实在太麻烦了。所以只要在Info.plistRequired background modes加上App provides Voice over IP services,以后每次开机以后,都会重新在后台启动应用了。

报告机制

暂时先使用本地推送,后续打算再加上CocoaLumberjack用作记录。

手机网络情况检测

如果手机离线或者信号不好的时候,还跑Ping去检测服务器也没有任何意义,反而会造成干扰。所以用了AFNetworkReachabilityManager来做判断,减少了做多余的无用功。

实际使用

使用了几天后,手机的电量榜上也不会出现这个小玩意,说明耗电情况不错。此外,的确能长期在后台运行,不会被系统杀掉。这次实践算是非常成功,既有趣,也解决了问题。

PS

PINKLongTimeBackground和我自己使用的Ping也有打算开源的计划,不过还需要整理一下,其中PINKLongTimeBackground打算支持pod集成。