You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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中存储的表单数据

完全可以实现!这是典型的离线缓存上传场景,核心思路是本地存储待上传数据 + 网络状态监听触发上传。下面是完整的实现方案:

实现思路

  1. 网络不可用时,将表单数据持久化到SQLite(推荐用FMDB简化SQLite操作,避免原生API的繁琐)
  2. 监听网络状态变化,当网络恢复时,从SQLite读取未上传的数据
  3. 调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:49:37