您的位置:首页 > 数据库

iOS开发-------Sqlite3实现本地存储简易通讯录

2015-10-15 16:26 288 查看
最近几天整了下Sqlite3,也就是iOS的另外一种储存方式,那么coreData是有什么不足么,不是,一般数据比较简易的时候是不会用coreData的,反而会用自身的sqlte3来实现本地的存储,这就需要用到了点SQL语句了,一般都会用第三方FMDB(第三方库)来简化使用,但第三方的应用是下一次博客的事情了,这次用自带的sqlite3来实现一个简易的通讯录,能够实现保存,并完成增删改查即可,毕竟是最基础的嘛,用第三方虽然简单,但是底层的基础还是要回的。首先看一下如何导入自身的sqlite3的库吧



如果细看这个图,那么也就知道,这个demo是按照MVC模式写的,既然是MVC模式写的,那么必然就会出现Model(模型类),View(视图类)以及Controller(控制器类)

这次的页面没有进行细作,只是为了能够了解Sqlite3的用法,首先就是模型类,看看数据库结构



上面是一个第三方工具,虽然能够快速建立表,但是作为程序员,不建议用它直接建表,建议用sql语句,毕竟还能回顾以及锻炼自己的SQL语句嘛,岂不是一举两得,一般工具是用来查看数据库的。

这里的布局非常简单,只是为了介绍sqlite3的用法,如果想要比较好的视觉可以去之前的博客iOS学习-------简单通讯录(UITableView和CoreData的应用),其他的都是一样的,只是存储的方法变了而已

Model(模型)

这个Model的作用就是存储数据库中表的信息即可,属性如下
//
//  Humen.h
//  Sqilte3
//
//  Created by YueWen on 15/10/6.
//  Copyright (c) 2015年 YueWen. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface Humen : NSObject

@property(nonatomic,strong)NSString * name;//姓名

@property(nonatomic,assign)NSInteger age;//年龄

@property(nonatomic,strong)NSString * tele;//电话

@property(nonatomic,strong)NSString * address;//地址

@property(nonatomic,assign)NSInteger humenId;//id

/**
*  便利初始化方法
*
*  @param name    初始name
*  @param age     初始age
*  @param tele    初始tele
*  @param address 初始地址
*
*  @return 初始化好的Humen对象
*/
-(instancetype)initWithName:(NSString *)name Age:(NSInteger)age Tele:(NSString *)tele Address:(NSString *)address;


实现便利初始化方法
-(instancetype)initWithName:(NSString *)name Age:(NSInteger)age Tele:(NSString *)tele Address:(NSString *)address
{
if (self = [super init])
{
self.name = name;//初始化name
self.age = age;//初始化age
self.tele = tele;//初始化tele
self.address = address;//初始化address
}

return self;
}


接下来就是最重点的Sqlite3Manager(数据库管理员),首先他要有增删改查的功能,所以定义四个方法
/**
*  增加人
*
*  @param humen 需要增加的Humen模型
*/
-(void)addHumenToSqlite:(Humen *)humen;

/**
*  更新数据
*
*  @param humen 需要更新的Humen模型
*/
-(void)updateHumenFromSqlite:(Humen *)humen withIndex:(NSInteger)index;

/**
*  根据下标即id删除
*
*  @param index 当前的id
*/
-(void)deleteHumenFromSqlite:(NSInteger)index;

/**
*  根据姓名查询名字
*
*  @param name 查询的名字
*/
-(void)selectHumenFromWithName:(NSString *)name;


其次,Manager一般都是单例,这次也不例外
/**
*  单例方法
*
*  @return 返回单例
*/
+(instancetype)shareSqlite3Manager;


此外,还应该有一个方法能够加载所有的数据,即刷新类似功能的时候,从新从数据库中加载一般数据

/**
*  加载数据库中所有的数据
*/
-(void)loadMHumen;


如果外界想获得所有的数据,那么必然会有一个对外开放的属性,但是外界是不能更改的,所以是readOnly的属性

/**
*  存储数据库中的数据数组
*/
@property(nonatomic,strong,readonly)NSArray * humen;


然后是最麻烦的实现方法,先从最简单的东西开始,不要忘记导入自带的sqlite3的头文件

#import <sqlite3.h>


延展中的属性,其中之一必须有导入sqlite3的对象,第二个就是可变数组,用来存储处理好的对象,也就是存储将字典处理好Humen对象,延展如下

@interface Sqlite3Manager ()

@property(nonatomic)sqlite3 * humendate;//不要加strong

@property(nonatomic,strong)NSMutableArray * mHumen;

@end


接着实现重写init方法,以及单例方法,这个相信是再熟悉也不过了

- (instancetype)init
{
self = [super init];
if (self) {

//初始化数据数组
self.mHumen = [NSMutableArray array];

//打开数据库
[self openSqlite_db];

//创建表
[self creatSqlite_db];

//加载数据
[self loadMHumen];
}
return self;
}

//单例方法
+(instancetype)shareSqlite3Manager
{
static Sqlite3Manager * sqliteManager = nil;

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{

sqliteManager = [[Sqlite3Manager alloc]init];

});

return sqliteManager;
}


再用这个苹果打包好的Sqlite3的时候,实时要注意objc对象和C语言类型的转换,因为SQL是用C语言进行操作的,不认NSString等对象,打开数据库如下

/**
*  打开数据库
*/
-(void)openSqlite_db
{
//获取沙盒目录
NSString * path1 = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];

//追加数据库名称
NSString * path = [path1 stringByAppendingPathComponent:@"humen.db"];

//打开数据库
sqlite3_open([path UTF8String],&self->_humendate);//导入的包中的API,因为底层是C语言的东西,所以path需要转成C语言的字符串,后面传一个sqlite3指针的地址

}


接下来就是创建本地的数据库了,用SQL的create table 语句,记得还是C语言的字符串

/**
*  创建本地的sqlite文件数据库,格式为db
*/
-(void)creatSqlite_db
{
//创建表的SQL语句,用C语言的字符串,如果用了NSString,记得转成C语言字符串即可
char * createTableSQL = "create table if not exists t_humen (id integer primary key,name varchar(30),age integer,tele varchar(100),address varchar(100))";

//执行,第一个参数是sqlite3对象指针,第二个是C语言类型的SQL语句,后面3个参数填NULL即可
sqlite3_exec(self->_humendate, createTableSQL, NULL, NULL, NULL);

}


加载数据是什么,就是select * table 语句,将所有的数据库的中的数据全部加载出来,并进行模型的转化

/**
*  加载数据
*/
-(void)loadMHumen
{
//清空之前的所有的数据
[self.mHumen removeAllObjects];

//访问数据库获取数据,根据id,默认为升序
char * selectSQL = "select * from t_humen order by id";

//创建sqlite3_stmt对象
sqlite3_stmt * stmt;

//准备加载
/**
*  参数的意义:
*  1:sqlite3的属性指针
*  2:执行的sql语句
*  3: 这里填的是搜说的字节数,-1表示全部
*  4: 表示填入一个sqlite3_stmt的对象
*/
sqlite3_prepare_v2(self->_humendate, selectSQL, -1, &stmt, NULL);

//循环加载
while (sqlite3_step(stmt) ==SQLITE_ROW)
{
//初始化一个Humen对象
Humen * humen = [[Humen alloc] initWithName:[NSString stringWithUTF8String:(char *)(sqlite3_column_text(stmt, 1))]//第一列是名字
Age:sqlite3_column_int(stmt, 2)//第二列是年龄
Tele:[NSString stringWithUTF8String:(char *)(sqlite3_column_text(stmt, 3))]//第三列是电话
Address:[NSString stringWithUTF8String:(char *)(sqlite3_column_text(stmt, 4))]];//第四列是地址

humen.humenId = sqlite3_column_int(stmt, 0);
//添加一个数组
[self.mHumen addObject:humen];
}

sqlite3_finalize(stmt);//表示完成,回馈一下
}


如何完成增删改查呢,这里需要了解一个机制叫做预编译,如果不用预编译,那么输入的某些SQL语句中的特殊字符,比如#等,会造成一些不必要而且还很难解决的问题,最麻烦的就是数据库的数据的安全会很危险,所以下面的方法都是用的自带的预编译语句进行操作

首先是增加人,如何增加,直接传入一个Humen对象,其他的操作在方法里操作即可,用SQL语句中的insert into

/**
*  增加人
*
*  @param humen 需要增加的Humen模型
*/
-(void)addHumenToSqlite:(Humen *)humen
{
//为数组添加数据
[self.mHumen addObject:humen];

//预编译语句
char * insertSQL = "insert into t_humen values(NULL,?,?,?,?)";

sqlite3_stmt * stmt;

//准备
sqlite3_prepare_v2(self->_humendate, insertSQL, -1, &stmt, NULL);

//为stmt预编译语句绑定数据,从1开始
sqlite3_bind_text(stmt, 1, [humen.name UTF8String], -1, NULL);//为name绑定
sqlite3_bind_int(stmt, 2, (int)humen.age);//为age绑定
sqlite3_bind_text(stmt, 3, [humen.tele UTF8String], -1, NULL);//为tele绑定
sqlite3_bind_text(stmt, 4, [humen.address UTF8String], -1, NULL);//为address绑定

//单步调试
int rst = sqlite3_step(stmt);

if (rst == SQLITE_DONE)
{
NSLog(@"插入成功!");
}else
{
NSLog(@"失败!编号为:%d",rst);
}

sqlite3_finalize(stmt);
}


删除对象,即需要知道数据库中属性的主键,在创建表的时候,primary key 表示主键,即 不能重复,就像人的身份证一样,唯一标识符,所以必须传入一个主键的数字,这个数字就是对象中的humen_id属性,因为加载的顺序就是按照主键顺序的

/**
*  删除指定id的人对象
*
*  @param index 下标 + 1
*/
-(void)deleteHumenFromSqlite:(NSInteger)index
{
//获取当前的对象id
NSInteger ID = ((Humen *)self.mHumen[index - 1]).humenId;

[self.mHumen removeObjectAtIndex:index - 1];

//从数据库中删除
//预编译语句
char * deleteSQL = "delete from t_humen where id=?";

sqlite3_stmt * stmt;

//准备
sqlite3_prepare_v2(self.humendate, deleteSQL, -1, &stmt, NULL);

//绑定数据
sqlite3_bind_int(stmt, 1, (int)ID);

//分布调试
int rst = sqlite3_step(stmt);

//如果完成
if (rst == SQLITE_DONE)
{
NSLog(@"删除成功!");
}

else
{
NSLog(@"删除失败!");
}

sqlite3_finalize(stmt);
}


更新对象,需要知道一个修改后的对象以及修改对象的id方便在数据库中进行修改,用update table语句即可

/**
*  更新数据
*
*  @param humen 需要更新的Humen模型
*/
-(void)updateHumenFromSqlite:(Humen *)humen withIndex:(NSInteger)index
{
//获取当前的对象的ID
NSInteger ID = ((Humen *)self.humen[index - 1]).humenId;

//数组替换
[self.mHumen replaceObjectAtIndex:index - 1 withObject:humen];

//预编译语句
char * updateSQL = "update t_humen set name=?,age=?,tele=?,address=? where id=?";

sqlite3_stmt * stmt;

//准备
sqlite3_prepare_v2(self.humendate, updateSQL, -1, &stmt, NULL);

//绑定参数数据
sqlite3_bind_text(stmt, 1, [humen.name UTF8String], -1, NULL);
sqlite3_bind_int(stmt, 2, (int)humen.age);
sqlite3_bind_text(stmt, 3, [humen.tele UTF8String], -1, NULL);
sqlite3_bind_text(stmt, 4, [humen.address UTF8String], -1, NULL);
sqlite3_bind_int(stmt, 5, (int)ID);

//分部调试
int rst = sqlite3_step(stmt);

//如果更新完成
if (rst == SQLITE_DONE)
{
NSLog(@"更新成功!");
}else
{
NSLog(@"失败!%d",rst);
}

sqlite3_finalize(stmt);
}


最后一个功能就是查,查的话在这个方案里是不需要再对数据库进行操作的,因为增删改的时候对本地的数组进行了操作,所以本地的数组存的数据就是实时的数据库里存的对象,整个工程只需要在初始化的时候对数据库进行一次加载即可,查询如下

/**
*  根据姓名查询名字
*
*  @param name 查询的名字
*/
-(void)selectHumenFromWithName:(NSString *)name
{
//可变数组存储符合条件的对象
NSMutableArray * mutableHumen = [NSMutableArray array];

//只需在数组中查询即可,不需要在执行数据库
for (Humen * humen in self.mHumen)
{
if ([humen.name isEqualToString:name])
{
//添加到数组
[mutableHumen addObject:humen];
}
}

//改变当前的数组
self.mHumen = mutableHumen;
}


既然打开了数据库,那么必然存在关闭数据库,这个demo的思路是在创建的时候打开数据库,直到程序结束的时候才会关闭,所以必然有一个关闭数据库的方法

/**
*  关闭数据库
*/
-(void)closeSqlite_db
{
sqlite3_close(self->_humendate);
}


重写dealloc中的方法,说到dealloc中的方法,在ARC环境下,是不需要写[super dealloc]的,写上也会报错

/**
*  消除对象的时候关闭数据库
*/
-(void)dealloc
{
//关闭数据库
[self closeSqlite_db];
}


由此,Sqlite3Manager就完成了

View(视图)

页面的布局依旧选择了storyboard,简单布局如下,root视图控制器为一个导航控制器,连接着能够显示的cell,后面的页面负责添加以及更新



Controller(控制器)

首先创建一个显示信息的主页面,为一个tableViewController,取名为:HumenTableViewController
延展中只需定义一个sqlite3Manager即可
@interface HumenTableViewController ()

@property(nonatomic,strong)Sqlite3Manager * sqliteManager;

@end


加载viewDidLoad
- (void)viewDidLoad {
[super viewDidLoad];

//初始化单例
self.sqliteManager = [Sqlite3Manager shareSqlite3Manager];

//添加导航栏的右按钮,回调方法为 toAddViewController
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(toAddViewController)];

//添加导航栏的左按钮,回调方法为 toSearchViewController
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemSearch target:self action:@selector(toSearchViewController)];
}


这里需要一个方法,叫做viewWillApear,比如在当前页面即将展现的时候触发的,需要刷新一下列表,比如更新,添加页面,调回的时候,需要刷新一下列表
//页面将要跳回动画结束时
-(void)viewWillAppear:(BOOL)animated
{
//重新加载数据库中的所有数据
[self.sqliteManager loadMHumen];

//刷新列表
[self.tableView reloadData];
}


为添加按钮设置回调方法,只负责跳转一个页面即可
#pragma mark - barButton target action
-(void)toAddViewController
{
//获取storyboard
UIStoryboard * storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]];

//根据storyboard创建控制对象
UIViewController * viewController = [storyboard instantiateViewControllerWithIdentifier:@"AddViewController"];

//跳转界面
[self.navigationController pushViewController:viewController animated:YES];
}


为搜索按钮设置一个回调方法,这里不再进行页面跳转,只需弹出一个alertController即可,详细可以去之前的博客iOS学习-------UIAlertController(弹出视图控制器)
-(void)toSearchViewController
{
//准备alertController
UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"搜索" message:@"请填入搜索的名字" preferredStyle:UIAlertControllerStyleAlert];

//添加文本框
[alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
}];

//添加搜索的动作按钮
[alert addAction:[UIAlertAction actionWithTitle:@"搜索" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {

//获取搜索的名字
NSString * name = alert.textFields[0].text;

//在数据库对象中进行搜索
[self.sqliteManager selectHumenFromWithName:name];

//刷新列表
[self.tableView reloadData];

//跳回
[self dismissViewControllerAnimated:YES completion:nil];

}]];

//添加取消的动作按钮
[alert addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {

//跳回
[self dismissViewControllerAnimated:YES completion:nil];
}]];

//跳出
[self presentViewController:alert animated:YES completion:nil];

}


效果如下:



接下来是最为熟悉的三个数据源代理方法,不做赘余
#pragma mark - Table view data source
/**
*  返回组数,分组的情况下,因为不分组,所以返回1
*
*  @param tableView 当前的tableView
*
*  @return 组数
*/
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {

return 1;
}

/**
*  组的行数
*
*  @param tableView 当前的tableView
*  @param section   组的个数
*
*  @return 返回每部分的行数
*/
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

return self.sqliteManager.humen.count;

}

/**
*  cell显示的内容
*
*  @param tableView 当前的tableView
*  @param indexPath 当前的位置
*
*  @return 打包好的cell
*/
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

//获取模型
Humen * humen = self.sqliteManager.humen[indexPath.row];

//创建表格cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];

//简单赋值
cell.textLabel.text = humen.name;

return cell;
}


因为修改界面是通过点击当前的行来跳入的,因为需要实现如下代理方法
/**
*  点击行的时候
*
*  @param tableView 当前的tableView
*  @param indexPath 当前的位置
*/
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//获取当前的模型
Humen * humen = self.sqliteManager.humen[indexPath.row];

//获得当前的storyboard
UIStoryboard * storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]];

//获得跳转的ViewController
UIViewController * vc = [storyboard instantiateViewControllerWithIdentifier:@"AddViewController"];

//KVC赋值
[vc setValue:humen forKey:@"humen"];
[vc setValue:indexPath forKey:@"index"];

//跳转
[self.navigationController pushViewController:vc animated:YES];

}


增删改查已经完成了增,改,查的页面,删除如何呢,想点击按住一拖进行删除来完成,那么需要完成两个代理方法

首先将可以编辑设置为YES,默认为NO
/**
*  列表视图能够编辑
*
*  @param tableView 当前的tableView
*  @param indexPath 当前的位置
*
*  @return YES表示可以编辑  NO表示不可以编辑
*/
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {

return YES;
}


接着实现编辑模式下的不同编辑风格进行的操作,因为只完成删除,因此只完成了删除操作
/**
*  当前的编辑模式进行相应的操作
*
*  @param tableView    当前的tableView
*  @param editingStyle 编辑的风格
*  @param indexPath    当前的位置
*/
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {

//从数据库中删除当前的对象
[self.sqliteManager deleteHumenFromSqlite:indexPath.row + 1];

[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
} else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}


删除展示图如下



接着完成添加用户以及更新用户页面,因为页面相似,所以不想再做两个相同页面,只需一个属性,即Humen,判断依据是,只有更新的时候他才会有值,添加的时候是nil,延展如下
#import "AddViewController.h"
#import "Sqlite3Manager.h"
#import "Humen.h"

@interface AddViewController ()

@property (strong, nonatomic) IBOutlet UITextField *nameText;
@property (strong, nonatomic) IBOutlet UITextField *ageText;
@property (strong, nonatomic) IBOutlet UITextField *teleText;
@property (strong, nonatomic) IBOutlet UITextField *addressText;

@property (nonatomic,strong) Sqlite3Manager * sqliteManager;

//更新时专用
@property(strong,nonatomic)Humen * humen;
@property(nonatomic,strong)NSIndexPath * index;

@end


实现viewDidLoad,根据属性中的human的有无来判断进入何种页面
- (void)viewDidLoad {
[super viewDidLoad];

//实例化单例属性
self.sqliteManager = [Sqlite3Manager shareSqlite3Manager];

//设置导航的右上角按钮
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(addHumenToSqliteFromAddViewController)];

//表示代表更新视图
if (self.humen)
{
self.navigationItem.title = @"更新视图";
//赋值
self.nameText.text = self.humen.name;
self.ageText.text = [NSString stringWithFormat:@"%ld",self.humen.age];
self.teleText.text = self.humen.tele;
self.addressText.text = self.humen.address;
}

else
{
self.navigationItem.title = @"添加视图";
}
}


点击完成的时候,也是需要判断是添加还是更新的,毕竟SQL语句是不一样的,因此,回调方法如下
-(void)addHumenToSqliteFromAddViewController
{

//初始化humen实例
Humen * humen = [[Humen alloc]initWithName:self.nameText.text Age:[self.ageText.text intValue] Tele:self.teleText.text Address:self.addressText.text];

//表示更新
if (self.humen)
{
//更新到数据库
[self.sqliteManager updateHumenFromSqlite:humen withIndex:self.index.row + 1];
}

//表示添加
else
{
//添加到数据库sqlite
[self.sqliteManager addHumenToSqlite:humen];
}

//调回原页面
[self.navigationController popViewControllerAnimated:YES];

}


这样基本的增删改查就已经完成了,看一下效果吧

增加效果:



更新效果:



查找效果:



删除效果:



再次打开模拟器,还是可以保存的,如下

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: