在 iOS 上使用和访问现有的 SQLite 数据库
Posted
技术标签:
【中文标题】在 iOS 上使用和访问现有的 SQLite 数据库【英文标题】:Use and Access Existing SQLite Database on iOS 【发布时间】:2013-06-09 10:08:20 【问题描述】:我有一个完全填充的 SQLite 数据库,我想在我的新应用中使用它。它相当大,所以我想尽可能避免将其更改为另一种格式。如何以我的应用附带的方式使用此数据库?
编辑:例如,如果我只是将文件放入支持的文件目录中,我该如何访问它?如何引用?
【问题讨论】:
问题不清楚。您可以将数据库文件添加到您的包中。 您可以将 sqlite 添加到新应用的资源包中。您是否担心这样做的数据库大小? 我是 ios 开发新手,所以你的意思是把它放在文件(类、支持文件等)中吗?如果是这样,我该如何引用它? (编辑我的问题以包括这个) 查看这些 sqlite 教程:1, 2 【参考方案1】:使用FMDB Framework
可以使SQLite 数据库交互变得简单而干净。 FMDB 是 SQLite C 接口的 Objective-C 包装器。
参考值得一读:
FMDB Framework Docs
Sample Project With Storyboard
初始设置
像应用程序包中的任何其他文件一样添加SQLite DB
,然后使用以下代码将数据库复制到文档目录,然后使用文档目录中的数据库
-
首先下载FMDB framework
现在解压框架,从
src/fmdb
文件夹(不是src/sample
或src/extra
文件夹)复制所有文件。
在 Xcode 的左栏中单击您的项目。
点击中间栏中的主要目标。
点击“构建阶段”选项卡。
展开“将二进制文件与库链接”旁边的箭头。
点击“+”按钮。
搜索 libsqlite3.0.dylib 并双击它。
将您的 existing database
复制到 app's document
中的 didFinishLaunchingWithOptions:
并在整个应用程序中维护数据库路径。
在您的 AppDelegate 中添加以下代码。
AppDelegate.m
#import "AppDelegate.h"
@implementation AppDelegate
// Application Start
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
// Function called to create a copy of the database if needed.
[self createCopyOfDatabaseIfNeeded];
return YES;
#pragma mark - Defined Functions
// Function to Create a writable copy of the bundled default database in the application Documents directory.
- (void)createCopyOfDatabaseIfNeeded
// First, test for existence.
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
// Database filename can have extension db/sqlite.
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *appDBPath = [documentsDirectory stringByAppendingPathComponent:@"database-name.sqlite"];
success = [fileManager fileExistsAtPath:appDBPath];
if (success)
return;
// The writable database does not exist, so copy the default to the appropriate location.
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"database-name.sqlite"];
success = [fileManager copyItemAtPath:defaultDBPath toPath:appDBPath error:&error];
NSAssert(success, @"Failed to create writable database file with message '%@'.", [error localizedDescription]);
YourViewController.m
选择查询
#import "FMDatabase.h"
- (void)getAllData
// Getting the database path.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsPath = [paths objectAtIndex:0];
NSString *dbPath = [docsPath stringByAppendingPathComponent:@"database-name.sqlite"];
FMDatabase *database = [FMDatabase databaseWithPath:dbPath];
[database open];
NSString *sqlSelectQuery = @"SELECT * FROM tablename";
// Query result
FMResultSet *resultsWithNameLocation = [database executeQuery:sqlSelectQuery];
while([resultsWithNameLocation next])
NSString *strID = [NSString stringWithFormat:@"%d",[resultsWithNameLocation intForColumn:@"ID"]];
NSString *strName = [NSString stringWithFormat:@"%@",[resultsWithNameLocation stringForColumn:@"Name"]];
NSString *strLoc = [NSString stringWithFormat:@"%@",[resultsWithNameLocation stringForColumn:@"Location"]];
// loading your data into the array, dictionaries.
NSLog(@"ID = %d, Name = %@, Location = %@",strID, strName, strLoc);
[database close];
插入查询
#import "FMDatabase.h"
- (void)insertData
// Getting the database path.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsPath = [paths objectAtIndex:0];
NSString *dbPath = [docsPath stringByAppendingPathComponent:@"database-name.sqlite"];
FMDatabase *database = [FMDatabase databaseWithPath:dbPath];
[database open];
NSString *insertQuery = [NSString stringWithFormat:@"INSERT INTO user VALUES ('%@', %d)", @"Jobin Kurian", 25];
[database executeUpdate:insertQuery];
[database close];
更新查询
- (void)updateDate
// Getting the database path.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsPath = [paths objectAtIndex:0];
NSString *dbPath = [docsPath stringByAppendingPathComponent:@"fmdb-sample.sqlite"];
FMDatabase *database = [FMDatabase databaseWithPath:dbPath];
[database open];
NSString *insertQuery = [NSString stringWithFormat:@"UPDATE users SET age = '%@' WHERE username = '%@'", @"23", @"colin" ];
[database executeUpdate:insertQuery];
[database close];
删除查询
#import "FMDatabase.h"
- (void)deleteData
// Getting the database path.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsPath = [paths objectAtIndex:0];
NSString *dbPath = [docsPath stringByAppendingPathComponent:@"database-name.sqlite"];
FMDatabase *database = [FMDatabase databaseWithPath:dbPath];
[database open];
NSString *deleteQuery = @"DELETE FROM user WHERE age = 25";
[database executeUpdate:deleteQuery];
[database close];
添加功能
获取行数
确保包含FMDatabaseAdditions.h
文件以使用intForQuery:
。
#import "FMDatabase.h"
#import "FMDatabaseAdditions.h"
- (void)gettingRowCount
// Getting the database path.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsPath = [paths objectAtIndex:0];
NSString *dbPath = [docsPath stringByAppendingPathComponent:@"database-name.sqlite"];
FMDatabase *database = [FMDatabase databaseWithPath:dbPath];
[database open];
NSUInteger count = [database intForQuery:@"SELECT COUNT(field_name) FROM table_name"];
[database close];
【讨论】:
抱歉打扰了。我们是否必须将 SQLite3 数据库放在特定文件夹中?将它放在“支持文件”文件夹中就足够了吗? 另外,文件扩展名可以是.sql或.db而不是.sqlite吗? @Razgriz 可以将 SQLite3 放在“支持文件”中。您可以使用这些扩展,首选 .db 和 .sqlite +1 但是,顺便说一句,在这个例子中使用stringWithFormat
来构建 SQL 是一个真的坏主意。例如,如果 updateDate
方法中的人名是 O'Brian
,这将失败。您也容易受到 SQL 注入攻击。您应该始终在 SQL 中使用 ?
占位符,然后在 executeQuery
或 executeUpdate
方法中添加参数,而不是。我还建议将数字保存为数字(例如 @23
而不是 @"23"
),因为这可能会改变行为,例如结果排序。
@Rob 感谢您的提醒,我同意您对 stringWithFormat
问题的看法,很快就会更新。【参考方案2】:
像添加应用程序包中的任何其他文件一样添加 Sqlite DB
通过代码将其复制到documents目录并使用它。这样做的目的是更新sqlite中的内容只能在Documents目录中进行
-(void) checkAndCreateDatabase
// Check if the SQL database has already been saved to the users phone, if not then copy it over
BOOL success;
// Create a FileManager object, we will use this to check the status
// of the database and to copy it over if required
NSFileManager *fileManager = [NSFileManager defaultManager];
// Check if the database has already been created in the users filesystem
success = [fileManager fileExistsAtPath:_databasePath];
// If the database already exists then return without doing anything
if(success) return;
// If not then proceed to copy the database from the application to the users filesystem
// Get the path to the database in the application package
NSString *databasePathFromApp = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:_databaseName];
// Copy the database from the package to the users filesystem
[fileManager copyItemAtPath:databasePathFromApp toPath:_databasePath error:nil];
- (id)init
if ((self = [super init]))
_databaseName = DB_NAME;
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
_databasePath = [documentsDir stringByAppendingPathComponent:_databaseName];
if (sqlite3_open([[self dbPath] UTF8String], &_database) != SQLITE_OK)
[[[UIAlertView alloc]initWithTitle:@"Missing"
message:@"Database file not found"
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil, nil]show];
return self;
【讨论】:
我首先使用了这个,但 icodebuster 建议使用 FMDB 最终成为使用 SQLite 的一种更简洁的方式。不过,这仍然是一个有效的答案。 如果数据库不需要修改,是否可以留在bundle中? @Lithu T.V 我有旧项目的 sqlite db,我希望它可以在我的新应用程序中重用。你能告诉我如何使用它吗?我尝试了你的建议,但我无法进入 insdie 这个循环 if(sqlite3_prepare_v2(database, sqlStatement, -1, &compiledStatement, NULL) == SQLITE_OK) @Imran 检查所有函数中抛出的错误..refer myiostricks.blogspot.in/2013/07/another-great-day.html【参考方案3】:以下方法将帮助您处理数据库
如果文档目录不存在则复制数据库的方法
-(void)copyDatabase
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *insPath = [NSString stringWithFormat:@"Instamontage.sqlite"];
destPath = [documentsDirectory stringByAppendingPathComponent:insPath];
NSString *srcPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:insPath];
// NSLog(@"\n src %@ \n dest %@", srcPath, destPath);
if (![[NSFileManager defaultManager] fileExistsAtPath:destPath])
NSError *error;
NSLog(@"not exist");
[[NSFileManager defaultManager] copyItemAtPath:srcPath toPath:destPath error:&error];
else
NSLog(@"exist");
插入/删除/更新表的方法
-(BOOL)dataManipulation: (NSString *)query
BOOL result=NO;
if (sqlite3_open([destPath UTF8String], &connectDatabase)==SQLITE_OK)
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(connectDatabase, [query UTF8String], -1, &stmt, NULL)==SQLITE_OK)
sqlite3_step(stmt);
result=YES;
sqlite3_finalize(stmt);
sqlite3_close(connectDatabase);
return result;
从表中获取行的方法
-(NSMutableArray *)getData: (NSString *)query
NSMutableArray *arrData=[[NSMutableArray alloc]init];
if (sqlite3_open([destPath UTF8String],&connectDatabase)==SQLITE_OK)
sqlite3_stmt *stmt;
const char *query_stmt = [query UTF8String];
if (sqlite3_prepare_v2(connectDatabase,query_stmt, -1, &stmt, NULL)==SQLITE_OK)
while (sqlite3_step(stmt)==SQLITE_ROW)
NSMutableDictionary *dictResult=[[NSMutableDictionary alloc] init];
for (int i=0;i<sqlite3_column_count(stmt);i++)
NSString *str;
if (sqlite3_column_text(stmt,i)!=NULL)
str = [NSString stringWithUTF8String:(char *)sqlite3_column_text(stmt,i)];
else
str=@"";
[dictResult setValue:str forKey:[NSString stringWithUTF8String:(char *)sqlite3_column_name(stmt,i)]];
[arrData addObject:dictResult];
sqlite3_finalize(stmt);
sqlite3_close(connectDatabase);
return arrData;
swift中的上述方法将如下所示
如果文档目录不存在则复制数据库的方法
func copyDatabaseToDocumentDirectory()
let directoryList = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
var documentDirectory = directoryList.first
documentDirectory?.append("/DatabasePract1.sqlite")
print(documentDirectory!)
if !FileManager.default.fileExists(atPath: documentDirectory!)
let databaseBundlePath = Bundle.main.path(forResource: "DatabasePract1", ofType: "sqlite")
do
try FileManager.default.copyItem(atPath: databaseBundlePath!, toPath: documentDirectory!)
self.databasePath = documentDirectory
catch
print("Unable to copy database.")
else
print("database exist")
self.databasePath = documentDirectory
插入/删除/更新表的方法
func dataManipulation(query: String) -> Bool
var database: OpaquePointer?
var result = false
if (sqlite3_open(databasePath, &database) == SQLITE_OK)
var queryStatement: OpaquePointer?
if (sqlite3_prepare_v2(database, query, -1, &queryStatement, nil) == SQLITE_OK)
sqlite3_step(queryStatement)
result = true
else
let errmsg = String(cString: sqlite3_errmsg(database)!)
print("error preparing insert: \(errmsg)")
sqlite3_finalize(queryStatement)
sqlite3_close(database)
return result
从表中获取行的方法
func fetchData(_ query: String) -> [[String:Any]]
var database: OpaquePointer?
var arrData: [[String:Any]] = []
if (sqlite3_open(databasePath, &database) == SQLITE_OK)
var stmt:OpaquePointer?
if sqlite3_prepare(database, query, -1, &stmt, nil) != SQLITE_OK
let errmsg = String(cString: sqlite3_errmsg(database)!)
print("error preparing insert: \(errmsg)")
return arrData
while(sqlite3_step(stmt) == SQLITE_ROW)
var dictData: [String: Any] = [:]
for i in 0..<sqlite3_column_count(stmt)
var strValue = ""
if (sqlite3_column_text(stmt, i) != nil)
strValue = String(cString: sqlite3_column_text(stmt, i))
let keyName = String(cString: sqlite3_column_name(stmt, i), encoding: .utf8)
dictData[keyName!] = strValue
arrData.append(dictData)
sqlite3_close(database)
return arrData
【讨论】:
【参考方案4】:使用 swift、单例类和 FMDB。你可以使用下面的代码很容易地实现它。
Download example
import Foundation
class LocalDatabase: NSObject
//sharedInstance
static let sharedInstance = LocalDatabase()
func methodToCreateDatabase() -> NSURL?
let fileManager = NSFileManager.defaultManager()
let urls = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
if let documentDirectory:NSURL = urls.first // No use of as? NSURL because let urls returns array of NSURL
// exclude cloud backup
do
try documentDirectory.setResourceValue(true, forKey: NSURLIsExcludedFromBackupKey)
catch _
print("Failed to exclude backup")
// This is where the database should be in the documents directory
let finalDatabaseURL = documentDirectory.URLByAppendingPathComponent("contact.db")
if finalDatabaseURL.checkResourceIsReachableAndReturnError(nil)
// The file already exists, so just return the URL
return finalDatabaseURL
else
// Copy the initial file from the application bundle to the documents directory
if let bundleURL = NSBundle.mainBundle().URLForResource("contact", withExtension: "db")
do
try fileManager.copyItemAtURL(bundleURL, toURL: finalDatabaseURL)
catch _
print("Couldn't copy file to final location!")
else
print("Couldn't find initial database in the bundle!")
else
print("Couldn't get documents directory!")
return nil
func methodToInsertUpdateDeleteData(strQuery : String) -> Bool
// print("%@",String(methodToCreateDatabase()!.absoluteString))
let contactDB = FMDatabase(path: String(methodToCreateDatabase()!.absoluteString) )
if contactDB.open()
let insertSQL = strQuery
let result = contactDB.executeUpdate(insertSQL,
withArgumentsInArray: nil)
if !result
print("Failed to add contact")
print("Error: \(contactDB.lastErrorMessage())")
return false
else
print("Contact Added")
return true
else
print("Error: \(contactDB.lastErrorMessage())")
return false
func methodToSelectData(strQuery : String) -> NSMutableArray
let arryToReturn : NSMutableArray = []
print("%@",String(methodToCreateDatabase()!.absoluteString))
let contactDB = FMDatabase(path: String(methodToCreateDatabase()!.absoluteString) )
if contactDB.open()
let querySQL = strQuery
let results:FMResultSet? = contactDB.executeQuery(querySQL,
withArgumentsInArray: nil)
while results?.next() == true
arryToReturn.addObject(results!.resultDictionary())
// NSLog("%@", arryToReturn)
if arryToReturn.count == 0
print("Record Not Found")
else
print("Record Found")
contactDB.close()
else
print("Error: \(contactDB.lastErrorMessage())")
return arryToReturn
【讨论】:
【参考方案5】:复制.sqlite 文件到目录...
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
// Database filename can have extension db/sqlite.
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *databasePath = [documentsDirectory stringByAppendingPathComponent:@"MapView.sqlite"];
success = [fileManager fileExistsAtPath:databasePath];
// if (success)
// return;
//
// The writable database does not exist, so copy the default to the appropriate location.
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"MapView.sqlite"];
success = [fileManager copyItemAtPath:defaultDBPath toPath:databasePath error:&error];
if (!success)
//NSAssert1(0, @"Failed to create writable database file with message '%@'.", [error localizedDescription]);
else
NSLog(@"Database created successfully");
从数据库中选择数据...
const char *dbpath = [databasePath UTF8String];
sqlite3_stmt *statement;
if (sqlite3_open(dbpath, &mapDB) == SQLITE_OK)
NSString *querySQL = [NSString stringWithFormat: @"SELECT * FROM maplatlong"];
const char *query_stmt = [querySQL UTF8String];
if (sqlite3_prepare_v2(mapDB, query_stmt, -1, &statement, NULL) == SQLITE_OK)
while(sqlite3_step(statement) == SQLITE_ROW)
NSString *cityN = [[NSString alloc] initWithUTF8String:(const char *) sqlite3_column_text(statement, 0)];
NSString *lat = [[NSString alloc] initWithUTF8String:(const char *) sqlite3_column_text(statement, 1)];
NSString *longi = [[NSString alloc] initWithUTF8String:(const char *) sqlite3_column_text(statement, 2)];
[cityName addObject:cityN];
[latitude addObject:lat];
[longitude addObject:longi];
sqlite3_finalize(statement);
sqlite3_close(mapDB);
【讨论】:
以上是关于在 iOS 上使用和访问现有的 SQLite 数据库的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Qt 在 Android 中读取现有的 SQLite 数据库?