Objective-C实现iOS APP定时检网及离线SQLite数据自动上传可行性问询
iOS Objective-C 网络监听与离线表单自动上传方案
1. 如何实现每隔一段时间自动检查网络可用性
在iOS中,推荐使用Apple的Network框架(iOS 12+)来监听网络状态,搭配定时器就能实现定时检查的需求。下面是完整的实现步骤和代码:
实现思路
- 用
NWPathMonitor实时监听网络状态变化(无需轮询也能获取状态更新) - 搭配
NSTimer实现主动定时轮询(可选,适合需要主动确认状态的场景) - 所有UI或业务逻辑处理必须回到主线程,避免子线程操作异常
代码实现
首先在视图控制器中声明所需属性:
#import <Network/Network.h> @interface ViewController () @property (nonatomic, strong) NWPathMonitor *networkMonitor; @property (nonatomic, strong) dispatch_queue_t networkQueue; @property (nonatomic, strong) NSTimer *networkCheckTimer; @end
然后在viewDidLoad中初始化监听和定时器:
- (void)viewDidLoad { [super viewDidLoad]; // 创建专门用于网络监听的子队列(不能用主队列) self.networkQueue = dispatch_queue_create("com.yourapp.network.monitor", DISPATCH_QUEUE_CONCURRENT); // 初始化NWPathMonitor,默认监听所有网络类型(WiFi/蜂窝/有线等) self.networkMonitor = [[NWPathMonitor alloc] init]; __weak typeof(self) weakSelf = self; self.networkMonitor.pathUpdateHandler = ^(NWPath *path) { BOOL isReachable = path.status == NWPathStatusSatisfied; NSString *networkType = @"未知"; if (path.usesInterfaceType(NWInterfaceTypeWiFi)) { networkType = @"WiFi"; } else if (path.usesInterfaceType(NWInterfaceTypeCellular)) { networkType = @"蜂窝网络"; } // 切回主线程处理业务逻辑 dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"网络状态更新:%@,可用:%@", networkType, isReachable ? @"是" : @"否"); [weakSelf handleNetworkStatusChange:isReachable]; }); }; // 启动网络监听 [self.networkMonitor startMonitoring]; // 启动定时检查(比如每30秒一次) self.networkCheckTimer = [NSTimer scheduledTimerWithTimeInterval:30.0 target:self selector:@selector(manualNetworkCheck) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:self.networkCheckTimer forMode:NSRunLoopCommonModes]; } // 手动检查网络状态的方法 - (void)manualNetworkCheck { BOOL isReachable = self.networkMonitor.currentPath.status == NWPathStatusSatisfied; NSLog(@"定时检查网络:可用状态:%@", isReachable ? @"是" : @"否"); [self handleNetworkStatusChange:isReachable]; } // 处理网络状态变化的业务逻辑 - (void)handleNetworkStatusChange:(BOOL)isReachable { if (isReachable) { // 网络可用时的操作,比如触发离线数据上传 NSLog(@"网络已恢复,可执行相关业务操作"); } else { // 网络不可用时的操作,比如提示用户 NSLog(@"网络不可用,请检查连接"); } } // 销毁时停止监听和定时器,避免内存泄漏 - (void)dealloc { [self.networkMonitor stopMonitoring]; [self.networkCheckTimer invalidate]; self.networkCheckTimer = nil; }
注意事项
- 如果需要兼容iOS 12以下版本,可以使用
SystemConfiguration框架的Reachability类(Apple官方提供的示例实现),但目前iOS 12以下用户占比极低,优先推荐NWPathMonitor - 定时器在应用进入后台后会暂停,如果需要后台持续检查,需要申请后台权限并使用后台任务,但一般前台场景下定时轮询足够
2. 网络恢复后自动上传SQLite中存储的表单数据
完全可以实现!这是典型的离线缓存上传场景,核心思路是本地存储待上传数据 + 网络状态监听触发上传。下面是完整的实现方案:
实现思路
- 网络不可用时,将表单数据持久化到SQLite(推荐用
FMDB简化SQLite操作,避免原生API的繁琐) - 监听网络状态变化,当网络恢复时,从SQLite读取未上传的数据
- 调用API上传数据,成功后标记为已上传或删除,失败则保留数据等待下次重试
代码实现
第一步:定义表单数据模型
// EnrollmentForm.h @interface EnrollmentForm : NSObject @property (nonatomic, copy) NSString *formID; // 表单唯一标识,用于去重和标记 @property (nonatomic, copy) NSString *name; @property (nonatomic, copy) NSString *studentID; // 其他表单字段根据你的需求添加... @property (nonatomic, assign) BOOL isUploaded; // 标记是否已上传 - (NSDictionary *)toDictionary; // 转换为字典用于存储和上传 + (EnrollmentForm *)fromDictionary:(NSDictionary *)dict; // 从字典解析模型 @end // EnrollmentForm.m @implementation EnrollmentForm - (NSDictionary *)toDictionary { return @{@"formID": self.formID ?: @"", @"name": self.name ?: @"", @"studentID": self.studentID ?: @"", @"isUploaded": @(self.isUploaded)}; } + (EnrollmentForm *)fromDictionary:(NSDictionary *)dict { EnrollmentForm *form = [[EnrollmentForm alloc] init]; form.formID = dict[@"formID"]; form.name = dict[@"name"]; form.studentID = dict[@"studentID"]; form.isUploaded = [dict[@"isUploaded"] boolValue]; return form; } @end
第二步:SQLite数据库操作类(用FMDB保证线程安全)
// DBManager.h #import <Foundation/Foundation.h> #import "EnrollmentForm.h" @interface DBManager : NSObject + (instancetype)sharedManager; // 单例模式,全局共享 - (BOOL)saveEnrollmentForm:(EnrollmentForm *)form; // 保存表单到本地 - (NSArray<EnrollmentForm *> *)getUnuploadedForms; // 获取所有未上传的表单 - (BOOL)markFormAsUploaded:(NSString *)formID; // 标记表单为已上传 @end // DBManager.m #import "DBManager.h" #import <FMDB/FMDB.h> @interface DBManager () @property (nonatomic, strong) FMDatabaseQueue *dbQueue; // 线程安全的数据库队列 @end @implementation DBManager + (instancetype)sharedManager { static DBManager *sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[DBManager alloc] init]; [sharedInstance setupDatabase]; }); return sharedInstance; } - (void)setupDatabase { NSString *docsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; NSString *dbPath = [docsDir stringByAppendingPathComponent:@"EnrollmentDB.sqlite"]; self.dbQueue = [[FMDatabaseQueue alloc] initWithPath:dbPath]; // 创建表单表 [self.dbQueue inDatabase:^(FMDatabase *db) { NSString *createSQL = @"CREATE TABLE IF NOT EXISTS EnrollmentForms (" "formID TEXT PRIMARY KEY, " "name TEXT, " "studentID TEXT, " "isUploaded INTEGER)"; BOOL success = [db executeUpdate:createSQL]; if (!success) { NSLog(@"创建表单表失败:%@", [db lastError]); } }]; } - (BOOL)saveEnrollmentForm:(EnrollmentForm *)form { __block BOOL success = NO; [self.dbQueue inDatabase:^(FMDatabase *db) { NSString *insertSQL = @"INSERT OR REPLACE INTO EnrollmentForms " "(formID, name, studentID, isUploaded) VALUES (?, ?, ?, ?)"; success = [db executeUpdate:insertSQL, form.formID, form.name, form.studentID, @(form.isUploaded)]; if (!success) { NSLog(@"保存表单失败:%@", [db lastError]); } }]; return success; } - (NSArray<EnrollmentForm *> *)getUnuploadedForms { __block NSMutableArray *forms = [NSMutableArray array]; [self.dbQueue inDatabase:^(FMDatabase *db) { NSString *querySQL = @"SELECT * FROM EnrollmentForms WHERE isUploaded = 0"; FMResultSet *rs = [db executeQuery:querySQL]; while ([rs next]) { NSDictionary *dict = @{@"formID": [rs stringForColumn:@"formID"], @"name": [rs stringForColumn:@"name"], @"studentID": [rs stringForColumn:@"studentID"], @"isUploaded": @([rs intForColumn:@"isUploaded"])}; EnrollmentForm *form = [EnrollmentForm fromDictionary:dict]; [forms addObject:form]; } [rs close]; }]; return forms; } - (BOOL)markFormAsUploaded:(NSString *)formID { __block BOOL success = NO; [self.dbQueue inDatabase:^(FMDatabase *db) { NSString *updateSQL = @"UPDATE EnrollmentForms SET isUploaded = 1 WHERE formID = ?"; success = [db executeUpdate:updateSQL, formID]; if (!success) { NSLog(@"标记表单为已上传失败:%@", [db lastError]); } }]; return success; } @end
第三步:网络恢复时触发上传
回到第一个问题的handleNetworkStatusChange方法,修改为:
- (void)handleNetworkStatusChange:(BOOL)isReachable { if (isReachable) { NSLog(@"网络已恢复,开始上传未提交的表单"); [self uploadUnsubmittedForms]; } else { NSLog(@"网络不可用,表单将存储到本地"); } } // 上传所有未提交的表单 - (void)uploadUnsubmittedForms { NSArray<EnrollmentForm *> *unuploadedForms = [[DBManager sharedManager] getUnuploadedForms]; if (unuploadedForms.count == 0) { NSLog(@"没有待上传的表单"); return; } // 逐个上传(可根据API支持改为批量上传) for (EnrollmentForm *form in unuploadedForms) { [self uploadForm:form completion:^(BOOL success, NSError *error) { if (success) { NSLog(@"表单 %@ 上传成功", form.formID); [[DBManager sharedManager] markFormAsUploaded:form.formID]; } else { NSLog(@"表单 %@ 上传失败:%@", form.formID, error.localizedDescription); // 保留数据,等待下次网络恢复时重试 } }]; } } // 单个表单上传的API调用 - (void)uploadForm:(EnrollmentForm *)form completion:(void(^)(BOOL success, NSError *error))completion { NSURL *url = [NSURL URLWithString:@"https://your-server.com/api/enroll"]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; request.HTTPMethod = @"POST"; request.HTTPBody = [NSJSONSerialization dataWithJSONObject:form.toDictionary options:0 error:nil]; [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { dispatch_async(dispatch_get_main_queue(), ^{ if (error) { completion(NO, error); return; } NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; if (httpResponse.statusCode >= 200 && httpResponse.statusCode < 300) { completion(YES, nil); } else { NSError *statusError = [NSError errorWithDomain:@"UploadError" code:httpResponse.statusCode userInfo:@{NSLocalizedDescriptionKey: @"服务器返回错误"}]; completion(NO, statusError); } }); }]; [task resume]; }
注意事项
- 用户提交表单时,先检查网络状态:如果不可用,调用
[DBManager sharedManager] saveEnrollmentForm:form保存到本地;如果可用,直接上传并可选择保存到本地作为备份 - 上传时可添加并发控制,比如用
NSOperationQueue限制同时上传的数量,避免请求过多导致服务器压力过大 - 可添加重试机制,比如上传失败后,设置重试次数,超过次数后提示用户手动重试
内容的提问来源于stack exchange,提问作者sathyapriya
相关产品推荐
相关产品推荐

