iOS开发之进阶篇(11)—— 数据存储

1. NSFileManager / NSFileHandle 文件管理

- (void)fileHandle {
    
    

    // 使用 NSFileManager 创建一个带初始内容的文件
    NSFileManager *manager = [NSFileManager defaultManager];
    // 待写入内容1
    NSString *fileStr1 = @"Hello";
    NSData *fileData1 = [fileStr1 dataUsingEncoding:NSUTF8StringEncoding];
    // 写入的文件路径
    NSString *filePath = [NSString stringWithFormat:@"%@/test.txt", NSTemporaryDirectory()];
    // 创建文件
    if(![manager fileExistsAtPath:filePath]) {
    
    
        [manager createFileAtPath:filePath      // 文件路径
                         contents:fileData1     // 初始化的内容
                       attributes:nil];         // 附加信息,一般置为nil
    }

    // 使用 NSFileHandle 继续写入内容
    // 只读权限打开
    __unused NSFileHandle *readHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
    // 只写权限打开
    __unused NSFileHandle *writeHandle = [NSFileHandle fileHandleForWritingAtPath:filePath];
    // 读写权限打开
    NSFileHandle *updateHandle = [NSFileHandle fileHandleForUpdatingAtPath:filePath];
    // 待写入内容2
    NSString *fileStr2 = @" World!";
    NSData *fileData2 = [fileStr2 dataUsingEncoding:NSUTF8StringEncoding];
    // 移动游标到末尾
    [updateHandle seekToEndOfFile];
    // 再写入一段内容
    [updateHandle writeData:fileData2];
    
    // 读取
    NSString *readStr = [[NSString alloc]initWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
    NSLog(@"readStr:%@", readStr);
}

// ---------------------------------------------
//log:
//2020-08-17 17:04:39.049061+0800 KKDataStoreDemo[2116:76170] readStr:Hello World!

2. writeToFile 写入.plist文件

- (void)writeToFile {
    
    
    
    // 待写入的字符串
    NSString *plistStr = @"Hello World!";
    // 写入的文件路径
    NSString *filePath1 = [NSString stringWithFormat:@"%@/test.plist", NSTemporaryDirectory()];
    // 写入
    [plistStr writeToFile:filePath1             // 写入的文件路径
               atomically:YES                   // 是否保证线程安全
                 encoding:NSUTF8StringEncoding  // 编码格式
                    error:nil];                 // 错误信息
    
    // 数组
    NSArray *plistArr = @[@"one",@"two",@"three"];
    NSString *filePath2 = [NSString stringWithFormat:@"%@/test2.plist", NSTemporaryDirectory()];
    [plistArr writeToFile:filePath2 atomically:YES];

    // 字典
    NSDictionary *pdic = @{
    
    @"one":@"1",@"two":@"2",@"three":@"3"};
    NSString *filePath3 = [NSString stringWithFormat:@"%@/test3.plist", NSTemporaryDirectory()];
    [pdic writeToFile:filePath3 atomically:YES];

    // 读取
    NSString *str = [[NSString alloc]initWithContentsOfFile:filePath1 encoding:NSUTF8StringEncoding error:nil];
    NSLog(@"str:%@", str);
    NSArray *arr = [[NSArray alloc] initWithContentsOfFile:filePath2];
    NSLog(@"arr:%@", arr);
    NSDictionary *dic = [[NSDictionary alloc] initWithContentsOfFile:filePath3];
    NSLog(@"dic:%@", dic);
}

// ---------------------------------------------
//log:
//2020-08-17 17:04:39.061649+0800 KKDataStoreDemo[2116:76170] str:Hello World!
//2020-08-17 17:04:39.061871+0800 KKDataStoreDemo[2116:76170] arr:(
//    one,
//    two,
//    three
//)
//2020-08-17 17:04:39.062184+0800 KKDataStoreDemo[2116:76170] dic:{
    
    
//    one = 1;
//    three = 3;
//    two = 2;
//}

3. NSUserDefaults 用户配置

- (void)userDefaults {
    
    
    
    // 写入
    [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"bKey"];
    [[NSUserDefaults standardUserDefaults] setObject:@"test" forKey:@"strKey"];
    [[NSUserDefaults standardUserDefaults] synchronize];
    
    // 读取
    BOOL bValue = [[NSUserDefaults standardUserDefaults] boolForKey:@"bKey"];
    NSLog(@"bValue:%d", (int)bValue);
    NSString *strValue = [[NSUserDefaults standardUserDefaults] objectForKey:@"strKey"];
    NSLog(@"strValue:%@", strValue);
}

// ---------------------------------------------
//log:
//2020-08-17 17:04:39.080255+0800 KKDataStoreDemo[2116:76170] bValue:1
//2020-08-17 17:04:39.080343+0800 KKDataStoreDemo[2116:76170] strValue:test

4. NSKeyedArchiver /NSKeyedUnarchiver 归解档

- (void)keyedArchiver {
    
    
    
    Person *person = [Person new];
    person.name = @"小康";
    person.age = 18;
    
    // 存储路径
    NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    NSString *filePath = [docPath stringByAppendingPathComponent:@"person1.plist"];
    
    // 归档
    if (@available(iOS 11.0, *)) {
    
    
        NSError *error = nil;
        self.archivedData = [NSKeyedArchiver archivedDataWithRootObject:person requiringSecureCoding:YES error:&error];
        if (self.archivedData == nil || error) {
    
    
            NSLog(@"归档失败:%@", error);
            return;
        }
    } else {
    
    
        [NSKeyedArchiver archiveRootObject:person toFile:filePath];
    }
    
    // 解档
    Person *pers = nil;
    if (@available(iOS 11.0, *)) {
    
    
        NSError *error = nil;
        pers = [NSKeyedUnarchiver unarchivedObjectOfClass:[Person class] fromData:self.archivedData error:&error];
        if (pers == nil || error) {
    
    
            NSLog(@"解档失败:%@", error);
            return;
        }
    } else {
    
    
        pers = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
    }
    NSLog(@"pers.name:%@, pers.age:%d", pers.name, pers.age);
}

// ---------------------------------------------
//log:
//2020-08-17 17:04:39.081435+0800 KKDataStoreDemo[2116:76170] pers.name:小康, pers.age:18
@interface Person : NSObject <NSSecureCoding>

@property (nonatomic, copy)     NSString    *name;
@property (nonatomic, assign)   int         age;
@property (nonatomic, strong)   NSData      *headData;

@end


@implementation Person

// 是否支持加密编码
+ (BOOL)supportsSecureCoding {
    
    
    
    return YES;
}

// 归档
- (void)encodeWithCoder:(NSCoder *)aCoder {
    
    
    
    [aCoder encodeObject:self.name forKey:@"name"];
    [aCoder encodeInt:self.age forKey:@"age"];
    [aCoder encodeObject:self.headData forKey:@"headData"];
}

// 解档
- (id)initWithCoder:(NSCoder *)aDecoder {
    
    
    
    self = [super init];
    if (self) {
    
    
        
        self.name = [aDecoder decodeObjectForKey:@"name"];
        self.age = [aDecoder decodeIntForKey:@"age"];
        self.headData = [aDecoder decodeObjectForKey:@"headData"];
    }
    return self;
}

@end

5. NSBundle 资源文件包

- (void)bundle {
    
    
    
    // 读取
    NSBundle *myBundle = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"MyImages" ofType:@"bundle"]];
    NSString *imagePath = [myBundle pathForResource:@"image1" ofType:@"png"];
    UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
    NSLog(@"image:%@", image);
    
    // 创建
    // 1. MacOS系统下, 新建文件夹, 修改文件名, 后缀.bundle
    // 2. 拖入工程
    // 3. 拖入资源文件至bundle下
}

6. Keychain 钥匙串

- (void)keychain {
    
    
    
    /**
     Keychain Services 是 macOS 和 iOS 都提供一种安全的存储敏感信息的工具,比如,网络密码:用户访问服务器或者网站,通用密码:用来保存应用程序或者数据库密码.与此同时,用于认证的证书,密钥,和身份信息,也可以存储在Keychain中,Keychain Services 的安全机制保证了存储这些敏感信息不会被窃取。简单说来,Keychain 就是一个安全容器。
     */
    // https://blog.csdn.net/qq_30357519/article/details/85051107
    // https://www.jianshu.com/p/6c2265a82f72
}

7. fwrite / fread C标准库输入输出

// 系统已经包含C标准库stdio.h
- (void)ioFile {
    
    
    
    // 如果还没创建目标文件, 创建之
    if (self.filePath.length == 0) {
    
    
        
        NSFileManager *manager = [NSFileManager defaultManager];

        // 文件路径
        NSString *TmpDir = NSTemporaryDirectory();
        self.filePath = [NSString stringWithFormat:@"%@%@", TmpDir, @"test/123.avi"];
        
        // 移除之前的filePath
        [[NSFileManager defaultManager] removeItemAtPath:self.filePath error:nil];
        
        // 创建文件夹
        NSString *folderPath = [self.filePath stringByDeletingLastPathComponent];    // 去除最后的组成部分(/123.avi), 剩余.../test
        if(![manager fileExistsAtPath:folderPath]) {
    
    
            BOOL success = [manager createDirectoryAtPath:folderPath    //参数1: 创建的目录路径
                              withIntermediateDirectories:YES           //参数2: 是否自动添加缺失的路径
                                               attributes:nil           //参数3: 创建文件的附带信息
                                                    error:nil];         //参数4: 错误信息
            NSLog(@"创建文件夹 success:%@, folderPath:%@", success ? @"YES" : @"NO", folderPath);
        }
        
        // 创建文件 (createFileAtPath:方法不会自动添加缺失的文件夹路径.如本例的test文件夹)
        if(![manager fileExistsAtPath:self.filePath]) {
    
    
            BOOL success = [manager createFileAtPath:self.filePath  // 文件路径
                                            contents:nil            // 初始化的内容
                                          attributes:nil];          // 附加信息
            NSLog(@"success:%@, filePath=%@", success ? @"YES" : @"NO", self.filePath);
        }
    }
    
    // 待写入的数据
    NSData *data = [NSData new];
    
    // 1.获取目标文件
    FILE *file = fopen([self.filePath UTF8String], "ab+");    // ab+ 允许读写, 后尾添加数据, 不覆盖原来数据
    // 2.写入文件
    fwrite((char *)data.bytes, data.length, 1, file);
    // 3.关闭文件
    fclose(file);
    
//    fread(<#void *restrict __ptr#>, <#size_t __size#>, <#size_t __nitems#>, <#FILE *restrict __stream#>)
}

8. SQLite3 / Core Data / FMDB 数据库

8.1 SQLite3

- (void)sqlite {
    
    
    
    Person *person1 = [Person new];
    person1.name = @"name1";
    person1.age = 1;
    NSString *headStr1 = @"不帅";
    person1.headData = [headStr1 dataUsingEncoding:NSUTF8StringEncoding];
    
    Person *person2 = [Person new];
    person2.name = @"name2";
    person2.age = 2;
    NSString *headStr2 = @"很帅";
    person2.headData = [headStr2 dataUsingEncoding:NSUTF8StringEncoding];
    
    [[DBManager sharedInstance] createDB];
    
    // 添加
    [[DBManager sharedInstance] addPerson:person2 completion:^(BOOL success) {
    
    
        NSLog(@"success:%d", (int)success);
    }];

    // 移除
//    [[DBManager sharedInstance] removePerson:person1 completion:^(BOOL success) {
    
    
//        NSLog(@"success:%d", (int)success);
//    }];
    
    // 获取
//    [[DBManager sharedInstance] getPersonWithName:@"name2" completion:^(Person * _Nullable person) {
    
    
//        NSString *str = [[NSString alloc] initWithData:person.headData encoding:NSUTF8StringEncoding];
//        NSLog(@"name:%@, age:%d, headStr:%@", person.name, person.age, str);
//    }];
    
}

DBManager.h

#import <Foundation/Foundation.h>
#import "Person.h"

NS_ASSUME_NONNULL_BEGIN


typedef void(^DBCompletion)(BOOL success);


@interface DBManager : NSObject {
    
    
    
    NSString *databasePath;
}

+ (DBManager *)sharedInstance;

- (BOOL)createDB;

/// 添加
/// @param person person
/// @param completion block
- (void)addPerson:(Person *)person completion:(DBCompletion)completion;


/// 移除
/// @param person person
/// @param completion block
- (void)removePerson:(Person *)person completion:(DBCompletion)completion;


/// 获取
/// @param name 名字
/// @param completion block
- (void)getPersonWithName:(NSString *)name completion:(void (^)(Person * _Nullable person))completion;

@end

NS_ASSUME_NONNULL_END

DBManager.m

#import "DBManager.h"
#import <sqlite3.h>

static DBManager *_singleInstance = nil;
static sqlite3 *database = nil;
static sqlite3_stmt *statement = nil;
static NSString *DB_SQLITE = @"people.db";
static NSString *DB_TABLE = @"personDetail";

@implementation DBManager


#pragma mark - 单例

+ (instancetype)sharedInstance
{
    
    
    return [[self alloc] init];
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    
    
        _singleInstance = [super allocWithZone:zone];
    });
    return _singleInstance;
}

- (instancetype)init
{
    
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    
    
        _singleInstance = [super init];
        if (_singleInstance) {
    
    
            // 在这里初始化self的属性和方法
        }
    });
    return _singleInstance;
}


#pragma mark - 

- (BOOL)createDB {
    
    
    
    BOOL isSuccess = YES;
    
    NSString *docsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
    databasePath = [[NSString alloc] initWithString:[docsDir stringByAppendingPathComponent:DB_SQLITE]];
    
    NSFileManager *manager = [NSFileManager defaultManager];
    if ([manager fileExistsAtPath:databasePath] == NO) {
    
    
        
        const char *dbpath = [databasePath UTF8String];
        if (sqlite3_open(dbpath, &database) == SQLITE_OK) {
    
    
            
            NSString *sqliteStr = [NSString stringWithFormat:@"create table if not exists %@ (idx integer primary key autoincrement, name text, age integer, headData blob)", DB_TABLE];
            const char *sqliteChar = [sqliteStr UTF8String];
            char *errMsg;
            if (sqlite3_exec(database, sqliteChar, NULL, NULL, &errMsg) != SQLITE_OK) {
    
    
                
                isSuccess = NO;
                NSLog(@"!!! Failed to create table, errMsg:%s", errMsg);
            }
            sqlite3_close(database);
            
        }else {
    
    
            
            isSuccess = NO;
            NSLog(@"!!! Failed to open/create database");
        }
    }
    
    return isSuccess;
}


/// 添加
/// @param person person
/// @param completion block
- (void)addPerson:(Person *)person completion:(DBCompletion)completion {
    
    
    
    const char *dbpath = [databasePath UTF8String];
    if (sqlite3_open(dbpath, &database) == SQLITE_OK) {
    
    
        
        NSString *sqliteStr = [NSString stringWithFormat:@"insert into %@ (name, age, headData) values (?, ?, ?);", DB_TABLE];
        const char *sqliteChar = [sqliteStr UTF8String];
        int result = sqlite3_prepare_v2(database, sqliteChar, -1, &statement, nil);
        if (result == SQLITE_OK) {
    
    
            
            sqlite3_bind_text(statement, 1, [person.name UTF8String], -1, nil);
            sqlite3_bind_int(statement, 2, person.age);
            sqlite3_bind_blob(statement, 3, person.headData.bytes, (int)person.headData.length, nil);
            
            if (sqlite3_step(statement) == SQLITE_DONE) {
    
    
                sqlite3_finalize(statement);
                if (completion) completion(YES);
                return;
            }
        }
    }
    
    sqlite3_finalize(statement);
    if (completion) completion(NO);
}


/// 移除
/// @param person person
/// @param completion block
- (void)removePerson:(Person *)person completion:(DBCompletion)completion {
    
    
    
    const char *dbpath = [databasePath UTF8String];
    if (sqlite3_open(dbpath, &database) == SQLITE_OK) {
    
    
        
        NSString *sqliteStr = [NSString stringWithFormat:@"delete from %@ where name = \"%@\"", DB_TABLE, person.name];
        const char *sqliteChar = [sqliteStr UTF8String];
        int result = sqlite3_prepare_v2(database, sqliteChar, -1, &statement, nil);
        if (result == SQLITE_OK) {
    
    
            if (sqlite3_step(statement) == SQLITE_DONE) {
    
    
                sqlite3_finalize(statement);
                if (completion) completion(YES);
                return;
            }
        }
    }
    
    sqlite3_finalize(statement);
    if (completion) completion(NO);
}


/// 获取
/// @param name 名字
/// @param completion block
- (void)getPersonWithName:(NSString *)name completion:(void (^)(Person *person))completion {
    
    
    
    const char *dbpath = [databasePath UTF8String];
    if (sqlite3_open(dbpath, &database) == SQLITE_OK) {
    
    

        NSString *sqliteStr = [NSString stringWithFormat:@"select * from %@ where name=\"%@\"", DB_TABLE, name];
        const char *sqliteChar = [sqliteStr UTF8String];
        int result = sqlite3_prepare_v2(database, sqliteChar, -1, &statement, nil);
        if (result == SQLITE_OK) {
    
    
            
            while (sqlite3_step(statement) == SQLITE_ROW) {
    
    
                Person *person = [Person new];
                person.name = [[NSString alloc] initWithUTF8String:(const char *)sqlite3_column_text(statement, 1)];
                person.age = sqlite3_column_int(statement, 2);
                const char *dataBuffer = sqlite3_column_blob(statement, 3);
                int dataSize = sqlite3_column_bytes(statement, 3);
                person.headData = [NSData dataWithBytes:(const void *)dataBuffer length:(NSUInteger)dataSize];
                if ([person.name isEqualToString:name]) {
    
    
                    sqlite3_finalize(statement);
                    if (completion) completion(person);
                    return;
                }
            }
        }
    }
    
    sqlite3_finalize(statement);
    if (completion) completion(nil);
}


@end

8.2 Core Data

新建模型

模型成品

添加实体

命名及添加属性

设置代码生成

设置代码语言

生成代码

代码成品

- (void)coreData {
    
    
    
    [[CDManager sharedInstance] create];
    [[CDManager sharedInstance] addMan];
//    [[CDManager sharedInstance] deleteMan];
//    [[CDManager sharedInstance] updateMan];
//    [[CDManager sharedInstance] readMan];
}

CDManager.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import "Man+CoreDataClass.h"
#import "Man+CoreDataProperties.h"

NS_ASSUME_NONNULL_BEGIN

@interface CDManager : NSObject  {
    
    
    
    NSManagedObjectContext *_context;
}

+ (CDManager *)sharedInstance;

- (void)create;     // 创建数据库
- (void)addMan;     // 增
- (void)deleteMan;  // 删
- (void)updateMan;  // 改
- (void)readMan;    // 查

@end

NS_ASSUME_NONNULL_END

CDManager.m

#import "CDManager.h"
#import <CoreData/CoreData.h>


static CDManager *_singleInstance = nil;

@implementation CDManager

#pragma mark - 单例

+ (instancetype)sharedInstance
{
    
    
    return [[self alloc] init];
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    
    
        _singleInstance = [super allocWithZone:zone];
    });
    return _singleInstance;
}

- (instancetype)init
{
    
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    
    
        _singleInstance = [super init];
        if (_singleInstance) {
    
    
            // 在这里初始化self的属性和方法
        }
    });
    return _singleInstance;
}


#pragma mark -

// 创建数据库
- (void)create {
    
    

    // 获取模型路径
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"ManModel" withExtension:@"momd"];
    // 根据模型文件创建模型对象
    NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    // 利用模型对象创建助理对象
    NSPersistentStoreCoordinator *store = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
    // 数据库的名称和路径
    NSString *docStr = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    NSString *sqlPath = [docStr stringByAppendingPathComponent:@"coreData.sqlite"];
    NSURL *sqlUrl = [NSURL fileURLWithPath:sqlPath];
    // 创建
    NSError *error = nil;
    [store addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:sqlUrl options:nil error:&error];
    if (error) {
    
    
        NSLog(@"创建数据库失败:%@",error);
    } else {
    
    
        NSLog(@"创建数据库成功");
    }

    // 创建上下文
    _context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    // 关联持久化助理
    _context.persistentStoreCoordinator = store;
}


- (void)addMan {
    
    

    // new man
    Man *man = [NSEntityDescription insertNewObjectForEntityForName:@"Man" inManagedObjectContext:_context];
    man.name = @"name1";
    man.age = 11;
    NSString *headStr = @"不帅";
    man.headData = [headStr dataUsingEncoding:NSUTF8StringEncoding];

    // 保存
    NSError *error = nil;
    BOOL result = [_context save:&error];
    if (result == YES) {
    
    
        NSLog(@"添加成功");
    }else{
    
    
        NSLog(@"!!! 添加失败, error:%@", error);
    }
}


- (void)deleteMan {
    
    
   
    // 创建删除请求
    NSFetchRequest *deleRequest = [NSFetchRequest fetchRequestWithEntityName:@"Man"];
    // 删除条件
    deleRequest.predicate = [NSPredicate predicateWithFormat:@"age < %d", 10];
   
    // 发送请求, 返回需要删除的对象数组
    NSArray *deleArray = [_context executeFetchRequest:deleRequest error:nil];
    
    // 从数据库中删除
    for (Man *man in deleArray) {
    
    
        [_context deleteObject:man];
    }
   
    // 保存
    NSError *error = nil;
    BOOL result = [_context save:&error];
    if (result == YES) {
    
    
        NSLog(@"删除成功");
    }else{
    
    
        NSLog(@"!!! 删除失败, error:%@", error);
    }
}


- (void)updateMan {
    
    
    
    // 创建查询请求
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Man"];
    request.predicate = [NSPredicate predicateWithFormat:@"name = %@", @"name1"];
    
    // 发送请求
    NSArray *resArray = [_context executeFetchRequest:request error:nil];
    
    // 修改
    for (Man *man in resArray) {
    
    
        man.age = 28;
    }
  
    // 保存
    NSError *error = nil;
    BOOL result = [_context save:&error];
    if (result == YES) {
    
    
        NSLog(@"更改成功");
    }else{
    
    
        NSLog(@"!!! 更改失败, error:%@", error);
    }
}


- (void)readMan {
    
    
    
    // 创建查询请求
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Man"];
    request.predicate = [NSPredicate predicateWithFormat:@"name = %@", @"name1"];

    // 发送请求
    NSArray *resArray = [_context executeFetchRequest:request error:nil];
    
    // 读取
    for (Man *man in resArray) {
    
    
        NSLog(@"name:%@, age:%d, headData:%@", man.name, (int)man.age, [[NSString alloc] initWithData:man.headData encoding:NSUTF8StringEncoding]);
    }
}


@end

8.3 FMDB

https://github.com/ccgus/fmdb

KKDeviceManager.h

#import <Foundation/Foundation.h>
#import "KKDevice.h"

NS_ASSUME_NONNULL_BEGIN

@interface KKDeviceManager : NSObject


+ (instancetype)sharedInstance;


/**
 添加单个设备 (已存在设备则先删除后插入)

 @param device 设备
 @param block YES:成功 NO:失败
 */
- (void)addDevice:(KKDevice *)device finishBlock:(nullable void (^) (BOOL success))block;


/**
 移除单个设备

 @param device 设备
 @param block YES:成功 NO:失败
 */
- (void)removeDevice:(KKDevice *)device finishBlock:(nullable void (^) (BOOL success))block;


/**
 移除所有设备

 @param block YES:成功 NO:失败
 */
- (void)removeAllDeviceFinishBlock:(nullable void (^) (BOOL success))block;


/**
 更新设备信息 (有设备才会更新)

 @param device 设备
 @param block YES:成功 NO:失败
 */
- (void)updateDevice:(KKDevice *)device finishBlock:(nullable void (^) (BOOL success))block;


/// 获取设备列表
/// @param block 设备列表
- (void)getDeviceListFinishBlock:(nullable void (^) (NSMutableArray<KKDevice *> *deviceList))block;


@end

NS_ASSUME_NONNULL_END

KKDeviceManager.m

#import "KKDeviceManager.h"
#import "FMDB.h"

static NSString *DEVICE_LIST_SQLITE_NAME = @"deviceList.sqlite";
static KKDeviceManager *_singleInstance = nil;

@implementation KKDeviceManager {
    
    
    
    FMDatabaseQueue* queue;
}


#pragma mark - 单例

+ (instancetype)sharedInstance
{
    
    
    return [[self alloc] init];
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    
    
        _singleInstance = [super allocWithZone:zone];
    });
    return _singleInstance;
}

- (instancetype)init
{
    
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    
    
        _singleInstance = [super init];
        if (_singleInstance) {
    
    
            // 在这里初始化self的属性和方法
            [self createTable];
        }
    });
    return _singleInstance;
}

- (void)createTable {
    
    
    
    // 获取文件路径
    NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
    NSString *dbFilePath = [documentsPath stringByAppendingPathComponent:DEVICE_LIST_SQLITE_NAME];
    queue = [FMDatabaseQueue databaseQueueWithPath:dbFilePath];
    // 创建表格
    [queue inDatabase:^(FMDatabase *db) {
    
    
        BOOL result = [db executeUpdate:@"CREATE TABLE IF NOT EXISTS deviceTable ('number' integer primary key autoincrement not null, deviceID varchar(255), deviceName varchar(255), password varchar(255), securityCode varchar(255), imageFlip INTEGER, imageMirror INTEGER, infrared INTEGER, indicator INTEGER, beShared INTEGER, serverExist INTEGER, previewData blob, localIDsData blob);"];
        if (result) {
    
    
//                NSLog(@"~~~KK~~~ 创建数据库表格成功");
        }else{
    
    
            NSLog(@"~~~KK~~~ !!! 创建设备列表数据库表格失败");
        }
    }];
}


#pragma mark -


// 添加单个设备 (如果已有设备, 则先删除后插入)
- (void)addDevice:(KKDevice *)device finishBlock:(nullable void (^) (BOOL success))block {
    
    
        
    [queue inDatabase:^(FMDatabase * _Nonnull db) {
    
    
        
        BOOL success = NO;
        
        [db open];
        
        /* --- 1. 删除 --- */
        // 遍历查询所有设备,如果数据库中已含有此deviceID,则删除
        FMResultSet *res = [db executeQuery:@"SELECT * FROM deviceTable"];
        while ([res next]) {
    
    
            if ([device.did isEqualToString:[res stringForColumn:@"deviceID"]]) {
    
    
                NSString *sqlite = [NSString stringWithFormat:@"delete from deviceTable where deviceID = '%@'", device.did];
                success = [db executeUpdate:sqlite];
                break;
            }
        }
        
        /* --- 2.插入 --- */
        // NSArray转NSData
        NSData *localIDsData = [NSKeyedArchiver archivedDataWithRootObject:device.localIDs];
        // 准备sqlite语句
        NSString *sqlite = [NSString stringWithFormat:@"insert into deviceTable(deviceID, deviceName, password, securityCode, imageFlip, imageMirror, infrared, indicator, beShared, serverExist, previewData, localIDsData) values ('%@', '%@', '%@', '%@', '%d', '%d', '%d', '%d','%d', '%d', ?, ?)", device.did, device.name, device.pwd, device.securityCode, (int)device.imageFlip, (int)device.imageMirror, (int)device.infraredOn, (int)device.indicatorOn, (int)device.beShared, (int)device.serverExist];
        // 添加数据
        success = [db executeUpdate:sqlite, device.previewData, localIDsData];
        
        [db close];
        
        if (block) {
    
    
            block(success);
        }
    }];
}


// 移除单个设备
- (void)removeDevice:(KKDevice *)device finishBlock:(nullable void (^) (BOOL success))block {
    
    
        
    [queue inDatabase:^(FMDatabase *db) {
    
    
        
        [db open];
        
        // 准备sqlite语句
        NSString *sqlite = [NSString stringWithFormat:@"delete from deviceTable where deviceID = '%@'", device.did];
        // 执行sqlite语句
        BOOL success =  [db executeUpdate:sqlite];
        
        [db close];

        if (block) {
    
    
            block(success);
        }
    }];
}


// 移除所有设备
- (void)removeAllDeviceFinishBlock:(nullable void (^) (BOOL success))block {
    
    
    
    
    [queue inDatabase:^(FMDatabase *db) {
    
    
        
        [db open];
        
        NSString *sqlstr = @"delete from deviceTable";
        BOOL success =  [db executeUpdate:sqlstr];
        
        [db close];
                
        if (block) {
    
    
            block(success);
        }
    }];
}


// 更新设备信息,顺便更新deviceList
- (void)updateDevice:(KKDevice *)device finishBlock:(nullable void (^) (BOOL success))block {
    
    
    
    
    [queue inDatabase:^(FMDatabase * _Nonnull db) {
    
    
        
        BOOL success = NO;
        
        [db open];
        
        // NSArray转NSData
        NSData *localIDsData = [NSKeyedArchiver archivedDataWithRootObject:device.localIDs];
        // 准备sqlite语句
        NSString *sqlite = [NSString stringWithFormat:@"update deviceTable set deviceName = '%@', password = '%@', securityCode = '%@', imageFlip = '%d', imageMirror = '%d', infrared = '%d', indicator = '%d', beShared = '%d', serverExist = '%d', previewData = ?, localIDsData = ? where deviceID = '%@'", device.name, device.pwd, device.securityCode, (int)device.imageFlip, (int)device.imageMirror, (int)device.infraredOn, (int)device.indicatorOn, (int)device.beShared, (int)device.serverExist, device.did];
        // 添加数据
        success = [db executeUpdate:sqlite, device.previewData, localIDsData];
        
        [db close];
        
        if (block) {
    
    
            block(success);
        }
    }];
}


// 获取设备列表
- (void)getDeviceListFinishBlock:(nullable void (^) (NSMutableArray<KKDevice *> *deviceList))block {
    
    
    
    [queue inDatabase:^(FMDatabase *db) {
    
    
        
        [db open];
        
        NSMutableArray *deviceList = [[NSMutableArray<KKDevice *> alloc] init];
        // 1.准备sqlite语句
        NSString *sqlite = [NSString stringWithFormat:@"select * from deviceTable"];
        // 2.执行查询语句
        FMResultSet *resultSet = [db executeQuery:sqlite];
        // 3.遍历结果
        while ([resultSet next]) {
    
    
                        
            KKDevice *device = [[KKDevice alloc] init];
            
            if (device) {
    
    
                
                device.name = [resultSet stringForColumn:@"deviceName"];
                device.pwd = [resultSet stringForColumn:@"password"];
                device.securityCode = [resultSet stringForColumn:@"securityCode"];
                device.imageFlip = [resultSet boolForColumn:@"imageFlip"];
                device.imageMirror = [resultSet boolForColumn:@"imageMirror"];
                device.infraredOn = [resultSet intForColumn:@"infrared"];
                device.indicatorOn = [resultSet boolForColumn:@"indicator"];
                device.beShared = [resultSet boolForColumn:@"beShared"];
                device.serverExist = [resultSet boolForColumn:@"serverExist"];
                device.previewData = [resultSet dataForColumn:@"previewData"];
                NSData *localIDsData = [resultSet dataForColumn:@"localIDsData"];
                device.localIDs = [NSKeyedUnarchiver unarchiveObjectWithData:localIDsData];
                
                [deviceList addObject:device];
            }
            
        }
        
        [db close];

        if (block) {
    
    
            block(deviceList);
        }
    }];
}


@end

KKDevice.h

#import <Foundation/Foundation.h>


NS_ASSUME_NONNULL_BEGIN


// 设备连接状态
typedef NS_ENUM(NSInteger, KKDeviceConnectStatus) {
    
    
    KKDeviceConnectStatus_Offline,              // 断线
    KKDeviceConnectStatus_Connecting,           // 连线中
    KKDeviceConnectStatus_Connected,            // 连线成功
    KKDeviceConnectStatus_Online,               // 在线 (登录成功)
    KKDeviceConnectStatus_InvalidPassword,      // 无效密码
    KKDeviceConnectStatus_InvalidSecurityCode,  // 无效安全码 (设备已重置)
};

// 设备分辨率
typedef NS_ENUM(NSInteger, KKDeviceResolution) {
    
    
    KKDeviceResolution_Low,
    KKDeviceResolution_Mid,
    KKDeviceResolution_High
};

// 数据流类型
typedef NS_ENUM(NSInteger, KKStreamType) {
    
    
    KKStreamType_live,      // 直播
    KKStreamType_playback,  // 回放
};

// 红外灯状态
typedef NS_ENUM(NSInteger, KKDeviceInfraredStatus) {
    
    
    KKDeviceInfraredStatus_Open,
    KKDeviceInfraredStatus_Close,
    KKDeviceInfraredStatus_Auto,
};

// SD卡状态
typedef NS_ENUM(NSInteger, KKSDStatus) {
    
    
    KKSDStatus_No,          // 无卡
    KKSDStatus_Valid,       // 有效
    KKSDStatus_Nonsupport,  // 格式不支持, 需格式化
};


@interface KKDevice : NSObject

@property (nonatomic, copy)     NSString    *did;           // 设备ID
@property (nonatomic, copy)     NSString    *name;          // 名称
@property (nonatomic, copy)     NSString    *pwd;           // 密码
@property (nonatomic, copy)     NSString    *securityCode;  // 设备安全码 (设备每复位后刷新)
@property (nonatomic, assign)   BOOL        imageFlip;      // 图像翻转
@property (nonatomic, assign)   BOOL        imageMirror;    // 图像镜像
@property (nonatomic, assign)   BOOL        infraredOn;     // 红外开关 (废弃, 使用infraredStatus)
@property (nonatomic, assign)   BOOL        indicatorOn;    // 指示灯
@property (nonatomic, assign)   BOOL        beShared;       // 是否被分享
@property (nonatomic, assign)   BOOL        serverExist;    // 服务器是否存在
@property (nonatomic, strong)   NSData      *previewData;   // 预览图
@property (nonatomic, strong)   NSArray     *localIDs;      // 保存到相册的图片/视频id

@end


NS_ASSUME_NONNULL_END

PS

check

download

show

Demo

猜你喜欢

转载自blog.csdn.net/u012078168/article/details/108060570