在 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/samplesrc/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 中使用 ? 占位符,然后在 executeQueryexecuteUpdate 方法中添加参数,而不是。我还建议将数字保存为数字(例如 @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 数据库的主要内容,如果未能解决你的问题,请参考以下文章

iOS 下载 sqlite 数据库并替换现有的

如何使用 Qt 在 Android 中读取现有的 SQLite 数据库?

如何在 swift 3 中使用现有的 SQLite? (FMDB)

SpecialFolder.个人现有的sqlite数据库

如何拆分 iOS sqlite 数据库?

在 iPhone 上更新 SQLite 数据库