NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"tmp.db"];
FMDatabase *db = [FMDatabase databaseWithPath:path];
if (![db open]) {
// [db release]; // uncomment this line in manual referencing code; in ARC, this is not necessary/permitted
db = nil;
return;
}
FMResultSet *s = [db executeQuery:@"SELECT * FROM myTable"];
while ([s next]) {
//retrieve values for each record
}
必须值访问查寻结果之前执行 -[FMResultSet next],及时只使用一个:
FMResultSet *s = [db executeQuery:@"SELECT COUNT(*) FROM myTable"];
if ([s next]) {
int totalCount = [s intForColumnIndex:0];
}
FMResultSet有许多合适的方法来获取数据:
intForColumn:
longForColumn:
longLongIntForColumn:
boolForColumn:
doubleForColumn:
stringForColumn:
dateForColumn:
dataForColumn:
dataNoCopyForColumn:
UTF8StringForColumnName:
objectForColumnName:
每一个方法都是该{type}ForColumnIndex:类型的变体,基于当前列获取对应类型的数据。这里并不需要手动close掉FMResultSet对象,因为当执行发生的时候,要么 result set被释放掉了,要么数据库被关闭了。 [db close];
NSString *sql = @"create table bulktest1 (id integer primary key autoincrement, x text);"
"create table bulktest2 (id integer primary key autoincrement, y text);"
"create table bulktest3 (id integer primary key autoincrement, z text);"
"insert into bulktest1 (x) values ('XXX');"
"insert into bulktest2 (y) values ('YYY');"
"insert into bulktest3 (z) values ('ZZZ');";
success = [db executeStatements:sql];
sql = @"select count(*) as count from bulktest1;"
"select count(*) as count from bulktest2;"
"select count(*) as count from bulktest3;";
success = [self.db executeStatements:sql withResultBlock:^int(NSDictionary *dictionary) {
NSInteger count = [dictionary[@"count"] integerValue];
XCTAssertEqual(count, 1, @"expected one record for dictionary %@", dictionary);
return 0;
}];
INSERT INTO myTable VALUES (?, ?, ?, ?)
?字符被认为是SQLite中插入值的默认占位符,执行方法中的所有参数变量都可以使用。如在 Objective-C使用?:
NSString *name = @"Liam O'Flaherty (\"the famous Irish author\")";
NSDate *date = [NSDate date];
NSString *comment = nil;
BOOL success = [db executeUpdate:@"INSERT INTO authors (identifier, name, date, comment) VALUES (?, ?, ?, ?)", @(identifier), name, date, comment ?: [NSNull null]];
if (!success) {
NSLog(@"error = %@", [db lastErrorMessage]);
}
注意:对于基础数据类型,向NSInteger变量identifier,应该使用NSNumber对象,使用@语法成功包裹,如上面代码所示。或者使用[NSNumber numberWithInt:identifier]方法。do {
let identifier = 42
let name = "Liam O'Flaherty (\"the famous Irish author\")"
let date = NSDate()
let comment: String? = nil
try db.executeUpdate("INSERT INTO authors (identifier, name, date, comment) VALUES (?, ?, ?, ?)", values: [identifier, name, date, comment ?? NSNull()])
} catch {
print("error = \(error)")
}
注意:在Swift中,并没有包裹基本数字类型的操作,但是可以使用comment ?? NSNull()语法进行操作,如果为空,为nil,否则使用字符串。
INSERT INTO authors (identifier, name, date, comment) VALUES (:identifier, :name, :date, :comment)
参数必须是以冒号(:)开始,SQLite它也支持其它符号,但是内部字典的key值是使用前缀冒号,字典key值并不包括冒号。
NSDictionary *arguments = @{@"identifier": @(identifier), @"name": name, @"date": date, @"comment": comment ?: [NSNull null]};
BOOL success = [db executeUpdate:@"INSERT INTO authors (identifier, name, date, comment) VALUES (:identifier, :name, :date, :comment)" withParameterDictionary:arguments];
if (!success) {
NSLog(@"error = %@", [db lastErrorMessage]);
}
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];
2:使用如下
[queue inDatabase:^(FMDatabase *db) {
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @1];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @2];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @3];
FMResultSet *rs = [db executeQuery:@"select * from foo"];
while ([rs next]) {
…
}
}];
[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @1];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @2];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @3];
if (whoopsSomethingWrongHappened) {
*rollback = YES;
return;
}
// etc…
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @4];
}];
queue.inTransaction { db, rollback in
do {
try db.executeUpdate("INSERT INTO myTable VALUES (?)", values: [1])
try db.executeUpdate("INSERT INTO myTable VALUES (?)", values: [2])
try db.executeUpdate("INSERT INTO myTable VALUES (?)", values: [3])
if whoopsSomethingWrongHappened {
rollback.memory = true
return
}
try db.executeUpdate("INSERT INTO myTable VALUES (?)", values: [4])
} catch {
rollback.memory = true
print(error)
}
}
FMDatabaseQueue将在串行列队运行block,所以如果在多线程同时调用FMDatabaseQueue的方法,他们将按顺序执行,不会造成混乱。
class DBManager: NSObject {
let field_MovieID = "movieID"
let field_MovieTitle = "title"
let field_MovieCategory = "category"
let field_MovieYear = "year"
let field_MovieURL = "movieURL"
let field_MovieCoverURL = "coverURL"
let field_MovieWatched = "watched"
let field_MovieLikes = "likes"
//创建单例对象
static let shared: DBManager = DBManager()
//数据库文件名,这并不是一定要作为属性,但是方便重用。
let databaseFileName = "database.sqlite"
//数据库文件的路径
var pathToDatabase: String!
//FMDatabase对象用于访问和操作实际的数据库
var database: FMDatabase!
override init() {
super.init()
//创建数据库文件路径
let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
pathToDatabase = documentDirectory.appending("/\(databaseFileName)")
}
//这里添加后续代码
}
这里提供了两个便利的方法来创建和打开数据库
//自定义创建数据库方法,返回布尔值,如果为true,那么数据库创建成功,否则失败
func createDatabase() -> Bool{
var created = false
//如果数据库文件不存在那么就创建,存在就不创建
if !FileManager.default.fileExists(atPath: pathToDatabase) {
database = FMDatabase(path: pathToDatabase)
if database != nil{
//数据库是否被打开
if database.open() {
//为数据库创建表,表中的相关属性都是依据MovieInfo结构体模型
let createMoviesTableQuery = "create table movies (\(field_MovieID) integer primary key autoincrement not null, \(field_MovieTitle) text not null, \(field_MovieCategory) text not null, \(field_MovieYear) integer not null, \(field_MovieURL) text, \(field_MovieCoverURL) text not null, \(field_MovieWatched) bool not null default 0, \(field_MovieLikes) integer not null)"
do{
//执行查询,将为数据库创建新的表,这里需要使用try-catch来捕获异常
try database.executeUpdate(createMoviesTableQuery, values: nil)
//表创建成功,设置created为true
created = true
}catch{
print("Could not create table.")
print(error.localizedDescription)
}
//关闭数据库
database.close()
}else{
print("Could not open the database.")
}
}
}
return created
}
//打开数据库
func openDatabase() -> Bool{
//确认database对象是否被初始化,如果为nil,那么判断路径是否存在并创建
if database == nil{
if FileManager.default.fileExists(atPath: pathToDatabase){
database = FMDatabase(path: pathToDatabase)
}
}
//如果database对象存在,打开数据库,返回真,表示打开成功,否则数据库文件不存在或者发生了其它错误
if database != nil{
if database.open(){
return true
}
}
return false
}
//数据模型
struct MovieInfo {
var movieID: Int!
var title: String!
var category: String!
var year: Int!
var movieURL: String!
var coverURL: String!
var watched: Bool!
var likes: Int!
}
//插入电影数据
func insertMovieData(){
if openDatabase(){
if let pathToMoviesFile = Bundle.main.path(forResource: "movies", ofType: "tsv"){
do{
//因为使用contentsOfFile初始化String可能出现异常,所以使用do-catch捕获异常
let moviesFileContents = try String(contentsOfFile: pathToMoviesFile)
//基于"\r\n"将字符串变成数组
let moviesData = moviesFileContents.components(separatedBy: "\r\n")
var query = ""
for movie in moviesData{
let movieParts = movie.components(separatedBy: "\t")
if movieParts.count == 5{
let movieTitle = movieParts[0]
let movieCategory = movieParts[1]
let movieYear = movieParts[2]
let movieURL = movieParts[3]
let movieCoverURL = movieParts[4]
//创建查寻语句,注意,每一个查寻语句最后使用分号(;)结束,因为我们想同时执行多条查寻语句,SQLite将基于分号来区别对应的查寻语句,而且对于values的每一个值,如果是字符串类型引用需要使用单引号括起来。最后两个值使用了默认值0
query += "insert into movies (\(field_MovieID), \(field_MovieTitle), \(field_MovieCategory), \(field_MovieYear), \(field_MovieURL), \(field_MovieCoverURL), \(field_MovieWatched), \(field_MovieLikes)) values (null, '\(movieTitle)', '\(movieCategory)', \(movieYear), '\(movieURL)', '\(movieCoverURL)', 0, 0);"
}
}
//对于FMDB,同时执行多条查寻语句是非常容易的
if !database.executeStatements(query){
//打印插入操作所遭遇的问题
print("Failed to insert initial data into the database.")
print(database.lastError(), database.lastErrorMessage())
}
}catch{
print(error.localizedDescription)
}
}
//记得最后关闭数据库
database.close()
}
}
实现加载所有数据
//Loading Data 记得每一次操作都需要打开和关闭数据库
func loadMovies() -> [MovieInfo]!{
var movies: [MovieInfo]!
if openDatabase(){
//创建SQL查寻语句,加载数据,这里是基于field_MovieYear值的升序排列
let query = "select * from movies order by \(field_MovieYear) asc"
do{
print(database)
//执行SQL语句,该方法需要两个参数,第一个是查寻的语句,第二个是数组,数组中可以包含想查寻的值,并且返回FMResultSet对象,该对象包含了获取的值
let results = try database.executeQuery(query, values: nil)
//遍历查寻结果,创建MovieInfo实例对象,并添加到数组中
while results.next() {
let movie = MovieInfo(movieID: Int(results.int(forColumn: field_MovieID)),
title: results.string(forColumn: field_MovieTitle),
category: results.string(forColumn: field_MovieCategory),
year: Int(results.int(forColumn: field_MovieYear)),
movieURL: results.string(forColumn: field_MovieURL),
coverURL: results.string(forColumn: field_MovieCoverURL),
watched: results.bool(forColumn: field_MovieWatched),
likes: Int(results.int(forColumn: field_MovieLikes))
)
if movies == nil{
movies = [MovieInfo]()
}
movies.append(movie)
}
}catch{
print(error.localizedDescription)
}
database.close()
}
return movies
}
实现更新数据
//通过电影的ID来查寻对应的电影数据,并通过闭包返回电影数据
func loadMovie(withID ID:Int, completionHandler: (_ movieInfo: MovieInfo?) -> Void){
var movieInfo: MovieInfo!
if openDatabase(){
//建立查寻语句
let query = "select * from movies where \(field_MovieID)=?"
do{
//执行查寻
let results = try database.executeQuery(query, values: [ID])
//创建对象的数据模型对象
if results.next() {
movieInfo = MovieInfo(movieID: Int(results.int(forColumn: field_MovieID)),
title: results.string(forColumn: field_MovieTitle),
category: results.string(forColumn: field_MovieCategory),
year: Int(results.int(forColumn: field_MovieYear)),
movieURL: results.string(forColumn: field_MovieURL),
coverURL: results.string(forColumn: field_MovieCoverURL),
watched: results.bool(forColumn: field_MovieWatched),
likes: Int(results.int(forColumn: field_MovieLikes))
)
}
else {
print(database.lastError())
}
}catch{
print(error.localizedDescription)
}
//关闭数据库
database.close()
}
//回调查寻的数据
completionHandler(movieInfo)
}
//使用具体的电影数据更新数据库
func updateMovie(withID ID: Int, watched: Bool, likes: Int){
if openDatabase() {
//创建更新语句 以电影的ID为准,更新数据
let query = "update movies set \(field_MovieWatched)=?, \(field_MovieLikes)=? where \(field_MovieID)=?"
do {
//执行SQL语句
try database.executeUpdate(query, values: [watched, likes, ID])
}
catch {
print(error.localizedDescription)
}
database.close()
}
}
实现删除数据
//Delete Records
func deleteMovie(withID ID: Int) -> Bool {
var deleted = false
if openDatabase() {
//更具选中电影的ID,创建查寻语句
let query = "delete from movies where \(field_MovieID)=?"
do {
//执行删除
try database.executeUpdate(query, values: [ID])
deleted = true
}
catch {
print(error.localizedDescription)
}
//关闭数据库
database.close()
}
return deleted
}
参考: