代码改变世界

iOS-数据库+上传队列+runtime

2018-03-16 14:45  菜鸟Alex  阅读(1522)  评论(0编辑  收藏  举报
  • 需求: 把要上传的数据,文字或者图片之类存入数据库创建上传队列从数据库取出待上传数据进行上传,上传完成删除此条数据,失败进行重试.重试两次仍然失败停止上传
  • 创建数据库类HBDatabase,用于创建数据库, 表, 表的列名,存取或者删除数据.
  • 通过runtime获取模型属性,并将属性名字作为数据库表中的列名来创建表, 实现数据库增,删,查功能.
  • 数据库用的FMDB

整体流程

  • 创建数据库相关类HBDatabase, 根据传入的model,通过runtime创建数据库的表,以及表中列的名.
  • 存入model时根据runtime获取model的属性,得到表中的列名, 再通过runtime获得model属性对应的value值,插入表
  • 上传成功时删除此条数据,我根据的图片url来删除,可以根据任何一个在表中唯一的key来删除此条数据.
  • 创建上传管理类HBUploadManager,供外界使用.调用顺序,首先创建数据库,-(void)hb_setDatabaseModel:(id)upModel根据传入的model要让HBDatabase知道创建什么样的表.
  • 将数据存入数据库.直接存模型,解析在HBDatabase中,-(void)hb_saveDataToDatabaseWithModel:(id)model;
  • 调用上传方法-(void)start1即可, 剩下的操作交给HBDatabase就可以.

HBDatabase.h


#import <Foundation/Foundation.h>

@interface HBDatabase : NSObject
// 创建单利
+(instancetype)sharedHBDatabase;

/**
 设置数据库表列模型默认全部为text类型,传入类型id 任何模型都可以, 通过runtime获得属性,作为表中列名.

 @param model 表存储模型
 @param tableName 表名字
 */
-(void)setDatabaseModel:(id)model andTableName:(NSString *)tableName;


/**
 插入表中一条数据

 @param model 要插入的模型
 @param callback 插入是否成功回调(根据需求还可以增加回调的内容)
 */
-(void)insertToDatabaseWithModel:(id)model andCallback:(void(^)(BOOL isSuccess))callback;



/**
 删除一条数据, 我存入的有图片有文字,但删除此条记录是由于iOS本地手机中图片对应的路径url唯一性,则根据url来删除一条数据.

 @param strImgUniqueID 根据图片id删除一条数据
 @param callback 删除成功与否的回调
 */
-(void)deleteDataFromDatabaseWithImgUniqueID:(NSString *)strImgUniqueID andCallback:(void(^)(BOOL isSuccess))callback;


/**
 获取一条数据

 @param callback 返回一个字典 & 一个图片id 用来上传完成删除这条记录用(可以根据需求返回需要的信息)
 */
-(void)selectOneDataFromDatabaseCallbackWithDictionary:(void(^)(NSDictionary *dict, int imgUniqueID))callback;

-(void)selectAllDataCallbackArray:(void(^)(NSMutableArray *arrM, NSMutableArray * arrMImgID))callback;


/// 清空全部数据
-(void)deleteAllData;

@end

HBDatabase.m文件

  • 实现对应方法
  • KLog(@"")是自定义的打印类,可以打印出打印所在文件,方法名,行数快速定位打印位置
  • KImageTableName是定义的一个宏, 一个字符串作为表的名字.

#import "HBDatabase.h"
#import <objc/runtime.h>
#import <FMDB.h>
// 数据库名字
#define KDB_NAME  @"hb.db"

@interface HBDatabase ()
/// model 属性列表
@property(nonatomic, strong) NSArray * arrAttr;

@end

@implementation HBDatabase
// 创建单利
+(instancetype)sharedHBDatabase{
    static dispatch_once_t onceToken;
    static HBDatabase * instance = nil;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}

// 得到创建好的数据库
-(FMDatabase *)getDB{
    NSString* docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
    NSString* pathDb = [docsPath stringByAppendingPathComponent:KDB_NAME];
    KLog(@"数据库位置 : %@", pathDb);
    FMDatabase * db = [FMDatabase databaseWithPath:pathDb];
    return db;
}

#pragma mark - 查一条数据默认从第一条开始查
-(void)selectOneDataFromDatabaseCallbackWithDictionary:(void (^)(NSDictionary *dict, int imgUniqueID))callbackk{
    FMDatabase * db = [self getDB];
    if (![db open]) {
        KLog(@"数据库打开失败");
        return;
    }else{
        NSString * sql = [NSString stringWithFormat:@"select * from %@ where rownum = 1", KImageTableName];
        [db beginTransaction];
        FMResultSet * set = [db executeQuery:sql];
        NSMutableDictionary * dictM = @{}.mutableCopy;
        int img_unique_id = 0;
        while ([set next]) {
            for (int i = 0; i<self.arrAttr.count; i++) {
                NSString * nameAttr = self.arrAttr[i];
                NSString * attrValue = [set stringForColumn:nameAttr];
                dictM[nameAttr] = attrValue;
            }
            img_unique_id  = [set intForColumn:@"img_unique_id"];
        }
        callbackk(dictM, img_unique_id);
        [db commit];
        [db close];
    }
}

// 暂时无用
-(void)selectAllDataCallbackArray:(void(^)(NSMutableArray *arrM, NSMutableArray * arrMImgID))callback{
    FMDatabase * db = [self getDB];
    if (![db open]) {
        KLog(@"数据库打开失败");
        return;
    }else{
        NSString * sql = [NSString stringWithFormat:@"select * from %@", KImageTableName];
        [db beginTransaction];
        FMResultSet * set = [db executeQuery:sql];
        
        NSMutableArray * arrM = @[].mutableCopy;
        NSMutableArray * imgIDM = @[].mutableCopy;
        int img_unique_id = 0;
        while ([set next]) {
            NSMutableDictionary * dictM = @{}.mutableCopy;
            for (int i = 0; i<self.arrAttr.count; i++) {
                NSString * nameAttr = self.arrAttr[i];
                NSString * attrValue = [set stringForColumn:nameAttr];
                dictM[nameAttr] = attrValue;
            }
            img_unique_id  = [set intForColumn:@"img_unique_id"];
            
            [imgIDM addObject:[NSNumber numberWithInt:img_unique_id]];
            [arrM addObject:dictM];
        }
        callback(arrM, imgIDM);
        [db commit];
        [db close];
    }
}



#pragma mark - 删一条数据,根据图片的url.
-(void)deleteDataFromDatabaseWithImgUniqueID:(NSString *)strImgUniqueID andCallback:(void(^)(BOOL isSuccess))callback{
    FMDatabase * db = [self getDB];
    if (![db open]) {
        KLog(@"删除失败");
        callback(NO);
        return;
    }else{
        NSString * sqlDelete = [NSString stringWithFormat:@"delete from %@ where img_unique_id = %zd", KImageTableName, [strImgUniqueID intValue]];
        [db beginTransaction];
        BOOL isSuccess = [db executeUpdate:sqlDelete];
        if (isSuccess) {
            KLog(@"删除成功");
            callback(YES);
        }else{
            KLog(@"删除失败");
            callback(NO);
        }
        [db commit];
        [db close];
    }
}

#pragma mark - 增
-(void)insertToDatabaseWithModel:(id)model andCallback:(void(^)(BOOL isSuccess))callback{
    FMDatabase * db = [self getDB];
    NSMutableArray * arrProList = [self hb_getProListWithModel:model];
    NSString * strNames = @"";
    NSString * strValues = @"";
// 1.通过runtime获取模型的属性列表,遍历得到每一个属性名字colName,将属性名字拼接成sql需要的格式
// 2.同样通过runtime获得属性对应的value值,拼接成sql语句需要的格式插入
// 3.拼接完成插入数据库.
    for (int i = 0; i<arrProList.count; i++) {
        NSString * colName = arrProList[i];
        NSString * colValue = @"";
        const char * proName = [[NSString stringWithFormat:@"_%@",colName] UTF8String];
        Ivar ivar = class_getInstanceVariable([model class], proName);
        colValue = object_getIvar(model, ivar);
        
        if (i < arrProList.count - 1) {
            strNames = [strNames stringByAppendingString:[NSString stringWithFormat:@"'%@',", colName]];
            strValues = [strValues stringByAppendingString:[NSString stringWithFormat:@"'%@',", colValue]];
        }else if (i == arrProList.count - 1){
            strNames = [strNames stringByAppendingString:[NSString stringWithFormat:@"'%@'", colName]];
            strValues = [strValues stringByAppendingString:[NSString stringWithFormat:@"'%@'", colValue]];
        }
        
    }
    NSString * sqlInsert = [NSString stringWithFormat:@"insert into '%@' (%@) values (%@)", KImageTableName, strNames, strValues];
    if (![db open]) {
        KLog(@"数据库打开失败");
        callback(NO);
    }else{
        [db beginTransaction];
        BOOL isSuccess = [db executeUpdate:sqlInsert];
        if (isSuccess) {
            KLog(@"插入成功");
            [db commit];
            [db close];
            callback(YES);
        }else{
            KLog(@"插入失败");
            [db commit];
            [db close];
            callback(NO);
        }
        
    }
}

#pragma mark - init database
// 创建数据库
-(void)setDatabaseModel:(id)model andTableName:(NSString *)tableName{
// 获取要存入的模型的属性列表.将属性作为表列名创建表
    NSMutableArray * arrM = [self hb_getProListWithModel:model];
    [HBDatabase sharedHBDatabase].arrAttr = arrM;
    [self hb_initDatabaseWithProArr:arrM andTableName:tableName];
}

-(void)hb_initDatabaseWithProArr:(NSMutableArray *)arrM andTableName:(NSString *)tableName{
    NSString * creatTable = [NSString stringWithFormat:@"CREATE TABLE IF NOT EXISTS '%@' (img_unique_id INTEGER PRIMARY KEY AUTOINCREMENT)", tableName];
    NSString* docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
    NSString* pathDb = [docsPath stringByAppendingPathComponent:KDB_NAME];
    KLog(@"数据库位置 : %@", pathDb);
    FMDatabase * db = [FMDatabase databaseWithPath:pathDb];
    if (![db open]) {
        db = nil;
        KLog(@"数据库打开失败");
        return;
    }else{
        [db beginTransaction];
        BOOL isSuccess = [db executeUpdate:creatTable];
        if (isSuccess) {
            KLog(@"建表成功");
        }else{
            KLog(@"建表失败");
        }
        // 动态插入列
        for (int i = 0; i<arrM.count; i++) {
            NSString * colName = arrM[i];
            if (![db columnExists:colName inTableWithName:tableName]) {
                NSString * alerColStr = [NSString stringWithFormat:@"AlTER TABLE %@ ADD %@ TEXT", tableName, colName];
                BOOL isSuccess = [db executeUpdate:alerColStr];
                if (isSuccess) {
                    KLog(@"插入新key成功 = %@", colName);
                }else{
                    KLog(@"插入新key失败 = %@", colName);
                }
            }
        }
        [db commit];
        [db close];
    }
    
}

// 获取模型的属性,通过runtime, 并返回属性列表
-(NSMutableArray *)hb_getProListWithModel:(id)model{
    u_int count = 0;
    objc_property_t *properties = class_copyPropertyList([model class], &count);
    NSMutableArray * arrM = @[].mutableCopy;
    for (int i = 0; i < count; i++) {
        const char *propertyName = property_getName(properties[i]);
        NSString *str = [NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding];
        [arrM addObject:str];
    }
    KLog(@"模型属性 = %@", arrM);
    return arrM;
}

// 删除全部数据
-(void)deleteAllData{
    FMDatabase * db = [self getDB];
    if (![db open]) {
        KLog(@"清空数据库时打开失败");
        return;
    }else{
        NSString * sqlDelete = [NSString stringWithFormat:@"delete from %@", KImageTableName];
        [db beginTransaction];
        BOOL isSuccess = [db executeUpdate:sqlDelete];
        if (isSuccess) {
            KLog(@"清空成功");
        }else{
            KLog(@"清空失败");
        }
        [db commit];
        [db close];
    }
}

@end

创建上传管理类HBUploadManager

  • 直接暴露给外部,供使用
  • HBUploadManager.h


#import <Foundation/Foundation.h>

@interface HBUploadManager : NSObject
/// 存储数据条数
@property(nonatomic,assign)int countSave;


/// 数据库取的数据
@property(nonatomic, strong) NSDictionary * dict;
/// 默认2次重试
@property(nonatomic,assign)int retryTimes;

/// 当前获取到的数据库第一条数据
@property(nonatomic,assign)int img_unique_id;

/// operation的block
@property(nonatomic, copy) void(^operationBlock)();

/// 数据库取一条数据的回调
@property(nonatomic, copy) void(^selectOneDataCallback)(NSDictionary * dict);

+(instancetype)sharedUploadManager;

#pragma mark --------------------------- 上传队列 ----------------------------
-(void)start;
-(void)start1;
#pragma mark --------------------------- 数据库操作 --------------------------

/**
 初始化数据库

 @param upModel 数据表的模型
 */
-(void)hb_setDatabaseModel:(id)upModel;


/**
 存储数据模型到database

 @param model 要存储的模型
 */
-(void)hb_saveDataToDatabaseWithModel:(id)model;


/**
 删除数据

 @param imgUniqueID 根据图片id删除数据
 */
-(void)hb_deleteDataFromDatabaseWithImgUniqueID:(NSString *)imgUniqueID;


/**
 数据库取出第一条数据
 */
-(void)hb_selectOneDataFromDatabase;


/**
 清空全部数据
 */
-(void)hb_clearAllData;

@end


  • HBUploadManager.m文件实现对应方法

#import "HBUploadManager.h"
#import "HBDatabase.h"

// 外部头文件 这是请求网络上传需要的文件
#import "D_UploadServiceImg_UP.h"

@interface HBUploadManager ()
@property(nonatomic, strong) NSBlockOperation * operation; // 上传的操作
@property(nonatomic, strong) NSOperationQueue * queue; //上传队列,将操作加入队列,操作会自动执行

@property(nonatomic, strong) NSMutableArray * arrM;
@property(nonatomic, strong) NSMutableArray * arrImgIDs;
@end

@implementation HBUploadManager

+(instancetype)sharedUploadManager{
    static dispatch_once_t onceToken;
    static HBUploadManager * instance = nil;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
        instance.retryTimes = 2;
    });
    return instance;
}

// 废弃的方法
-(void)start{
    // 查数据
    [[HBDatabase sharedHBDatabase] selectOneDataFromDatabaseCallbackWithDictionary:^(NSDictionary *dict, int imgUniqueID) {
        if (dict != nil && dict.count != 0) {
            
            if ([self.dict isEqual:dict]) {
                if (self.retryTimes > 0) {
                    self.retryTimes --;
                }else{
                    // 重试两次仍然失败 停止上传
                    return;
                }
                
            }
            self.dict = dict;
            NSOperationQueue * queue = [[NSOperationQueue alloc] init];
            self.queue = queue;
            WEAKSELF
            NSBlockOperation * operation = [NSBlockOperation blockOperationWithBlock:^{
                // 此处是 上传的操作, 根据需要写自己的上传操作.
                [weakSelf uploadWithDictionary:dict witImgID:imgUniqueID];
            }];
            self.operation = operation;
            // 将操作加入队列自动执行操作
            [queue addOperation:operation];
        }else{
            return;
        }
    }];
}

// 目前使用的外部调用上传方法
// 从数据库取出所有的数据,存入属性中.并执行上传,上传完成则删除一条.
-(void)start1{
    [[HBDatabase sharedHBDatabase] selectAllDataCallbackArray:^(NSMutableArray *arrM, NSMutableArray *arrMImgID) {
        self.arrM = arrM;
        self.arrImgIDs = arrMImgID;
        NSOperationQueue * queue = [[NSOperationQueue alloc] init];
        queue.maxConcurrentOperationCount = 2;
        NSBlockOperation * operation = [NSBlockOperation blockOperationWithBlock:^{
            [self upAll];
        }];
        [queue addOperation:operation];
    }];
}

// 开始上传的方法,根据需要写自己的
-(void)upAll{
    KLog(@"开始上传");
    if (self.arrM.count == 0) {
        return;
    }
    if (self.retryTimes == 0) {
        return;
    }
    
    NSDictionary * dict = self.arrM[0];
    self.dict = dict;
    int imgID = [self.arrImgIDs[0] intValue];
    
    D_UploadServiceImg_UP * model = [D_UploadServiceImg_UP yy_modelWithDictionary:dict];
    DataMgerSuperLS * mana = [[DataMgerSuperLS alloc] init];
    mana.param = model;
    mana.dataMgerSuccessBlock = ^(NSDictionary *responseData) {
        KLog(@"上传图片res = %@", responseData);
        if ([responseData[@"status"][@"code"] intValue] != 200) {
            KLog(@"上传图片失败");
            self.retryTimes --;
            [self upAll];
        }
        // 上传完成
        // 上传成功
        self.retryTimes = 2;
        [self.arrM removeObjectAtIndex:0];
        [self.arrImgIDs removeObjectAtIndex:0];
        self.operation = nil;
        self.queue = nil;
        // 删除上传成功的数据
        [[HBDatabase sharedHBDatabase] deleteDataFromDatabaseWithImgUniqueID:[NSString stringWithFormat:@"%zd", imgID] andCallback:^(BOOL isSuccess) {
            if (isSuccess) {
                KLog(@"上传成功,数据库删除成功");
                
                // 继续上传
                [self upAll];
            }else{
                KLog(@"上传成功,数据库删除失败");
                // 停止上传
            }
        }];
    };
    mana.dataMgerFailBlock = ^{
        KLog(@"上传图片failed");
        self.retryTimes--;
        // 上传失败 重试2次
        [self upAll];
    };
    mana.dataMgerOtherFailBlock = ^(NSString *statusCode) {
        KLog(@"上传图片iled = %@", statusCode);
        self.retryTimes --;
        // 上传失败 重试2次
        [self upAll];
    };
    [mana getData];
}

// 我们自己上传请求网络的方法
-(void)uploadWithDictionary:(NSDictionary *)dict witImgID:(int) imgID{
    KLog(@"开始上传");
    D_UploadServiceImg_UP * model = [D_UploadServiceImg_UP yy_modelWithDictionary:dict];
    DataMgerSuperLS * mana = [[DataMgerSuperLS alloc] init];
    mana.param = model;
    mana.dataMgerSuccessBlock = ^(NSDictionary *responseData) {
        KLog(@"上传图片res = %@", responseData);
        if ([responseData[@"status"][@"code"] intValue] != 200) {
            KLog(@"上传图片失败");
            [self start];
        }
        // 上传完成
        // 上传成功
        self.operation = nil;
        self.queue = nil;
        // 删除上传成功的数据
        [[HBDatabase sharedHBDatabase] deleteDataFromDatabaseWithImgUniqueID:[NSString stringWithFormat:@"%zd", imgID] andCallback:^(BOOL isSuccess) {
            if (isSuccess) {
                KLog(@"上传成功,数据库删除成功");
                self.retryTimes = 2;
                // 继续上传
                [self start];
            }else{
                KLog(@"上传成功,数据库删除失败");
                // 停止上传
            }
        }];
    };
    mana.dataMgerFailBlock = ^{
        KLog(@"上传图片failed");
        // 上传失败 重试2次
        [self start];
    };
    mana.dataMgerOtherFailBlock = ^(NSString *statusCode) {
        KLog(@"上传图片iled = %@", statusCode);
        // 上传失败 重试2次
        [self start];
    };
    [mana getData];

}


// 取出一条数据从数据库, 目前没有使用
-(void)hb_selectOneDataFromDatabase{
    dispatch_async(dispatch_get_main_queue(), ^{
        [[HBDatabase sharedHBDatabase] selectOneDataFromDatabaseCallbackWithDictionary:^(NSDictionary *dict, int img_unique_id) {
            KLog(@"取出数据返回内容是: %@", dict);
            KLog(@"img_unique_id = %zd", img_unique_id);
        }];
    });
}

// 从数据库删除一条数据目前没有使用
-(void)hb_deleteDataFromDatabaseWithImgUniqueID:(NSString *)imgUniqueID{
    
    dispatch_async(dispatch_get_main_queue(), ^{
        [[HBDatabase sharedHBDatabase] deleteDataFromDatabaseWithImgUniqueID:imgUniqueID andCallback:^(BOOL isSuccess) {
            if (isSuccess) {
                KLog(@"删除成功");
            }else{
                KLog(@"删除失败");
            }
        }];
    });
    
}

// 存入要上传的模型
-(void)hb_saveDataToDatabaseWithModel:(id)model{
    
    dispatch_async(dispatch_get_main_queue(), ^{
        [[HBDatabase sharedHBDatabase] insertToDatabaseWithModel:model andCallback:^(BOOL isSuccess) {
            if (isSuccess) {
                KLog(@"模型存入数据库成功");
                self.countSave --;
                if (self.countSave > 0) {
                    
                }else if (self.countSave == 0){
                    // 全部存储完毕
                    // 加入队列开始上传
//                    [self start1];
                    [[HBDatabase sharedHBDatabase] selectAllDataCallbackArray:^(NSMutableArray *arrM, NSMutableArray *arrMImgID) {
                        self.arrM = arrM;
                        self.arrImgIDs = arrMImgID;
                        NSOperationQueue * queue = [[NSOperationQueue alloc] init];
                        queue.maxConcurrentOperationCount = 2;
                        NSBlockOperation * operation = [NSBlockOperation blockOperationWithBlock:^{
                            [self upAll];
                        }];
                        [queue addOperation:operation];
                    }];
                }
                
            }else{
                KLog(@"模型存入数据库失败");
            }
        }];
    });
}

// 根据要存储的model来初始化数据库,表
-(void)hb_setDatabaseModel:(id)upModel{
    [[HBDatabase sharedHBDatabase] setDatabaseModel:upModel andTableName:KImageTableName]; 
//    dispatch_async(dispatch_get_main_queue(), ^{
//        [[HBDatabase sharedHBDatabase] setDatabaseModel:upModel andTableName:KImageTableName];
//    });
}

// 清除所有数据, 比如切换账号时.
-(void)hb_clearAllData{
    [[HBDatabase sharedHBDatabase] deleteAllData];
}

@end


欢迎随时讨论