2015-07-21 19:05:35 billhepeng 阅读数 4662
操作数据库
以一个实例展示输入名称和年龄保存到数据库,然后在另外一个tab里面显示,围绕一个TableView 来完成一个对数据的增、删、改、查的操作 。
具体实现代码:

UsingData.zip 

1:创建工程的时候需要选择Use Core Data 使用数据库是Sqlit。
4790.png 
2:导入 CoreData
283066.png 
3:定义对象读取数据库: NSManagedObjectContext
706651.png 
4:创建表:创建表的时候首写字母一定是大写不然会提示出错
775849.png
5:获取数据连接,相当于创建一个连接池connection,用这个对象就可以保存,查询数据。
635744.png 

  context = ( UIApplication.sharedApplication().delegate asAppDelegate ).managedObjectContext

6:创建访问表的变量。

584225.png
7:设置字段值:

var row:AnyObject  =  NSEntityDescription.insertNewObjectForEntityForName("USERS", inManagedObjectContext: context!)

        if(name.text.isEmpty){

            UIAlertView(title: "提示", message: "姓名不能为空", delegate: nil, cancelButtonTitle: "确认").show()

            name.becomeFirstResponder()  //得到焦点

            return

        }

        row.setValue(name.text, forKey: "name")

        if  let _age  = age.text.toInt() {   //可以根据此种方法判断是否是int类型

            var _age:Int  = age.text.toInt()!

            if(_age < 0){

                UIAlertView(title: "提示", message: "年龄必须为正数", delegate: nil, cancelButtonTitle: "确认").show()

                age.becomeFirstResponder()

                return

            }

            row.setValue(_age, forKey: "age")

        }else{

            UIAlertView(title: "提示", message: "年龄必须为正数", delegate: nil, cancelButtonTitle: "确认").show()

            age.becomeFirstResponder()

            return

        }



527986.png
8:保存操作,保存数据

context?.save(nil)

646799.png 

9:读取数据:将数据读取到NString

  context = ( UIApplication.sharedApplication().delegate asAppDelegate ).managedObjectContext!

         dataarry = context.executeFetchRequestNSFetchRequest(entityName: "USERS"), error: nil)!

336201.png

10:打开表可以看到我们保存的数据:(大家可以去网上下载Sqlit工具,我用的Navict 感觉很不错的支持mysql、orcal、sqlit 等)
457294.png


11:更新数据:

    @IBAction func update(sender: AnyObject) {

        data.setValue(_uname.text, forKey: "name")

        data.setValue(_uage.text.toInt(), forKey: "age")

        data.managedObjectContext?.save(nil)


        dismissViewControllerAnimated(true, completion: nil)


12:删除

/ Override to support editing the table view.

override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {

        if editingStyle == .Delete {

            // Delete the row from the data source

            //tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)

            context.deleteObject(dataarry[indexPath.rowasNSManagedObject)

            context.save(nil);

            refreshData()

        } else if editingStyle == .Insert {

            // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view

        }    


    }


import UIKit

import CoreData

class ViewController: UIViewController {


    @IBOutlet weak var age: UITextField!

    @IBOutlet weak var name: UITextField!

    var  context: NSManagedObjectContext!

    override func viewDidLoad() {

        super.viewDidLoad()

        // Do any additional setup after loading the view, typically from a nib.

         context = ( UIApplication.sharedApplication().delegate asAppDelegate ).managedObjectContext

    }


    override func didReceiveMemoryWarning() {

        super.didReceiveMemoryWarning()

        // Dispose of any resources that can be recreated.

    }



    @IBAction func saveAction(sender: AnyObject) {

        var row:AnyObject  =  NSEntityDescription.insertNewObjectForEntityForName("USERS", inManagedObjectContext: context!)

        if(name.text.isEmpty){

            UIAlertView(title: "提示", message: "姓名不能为空", delegate: nil, cancelButtonTitle: "确认").show()

            name.becomeFirstResponder()  //得到焦点

            return

        }

        row.setValue(name.text, forKey: "name")

        if  let _age  = age.text.toInt() {   //可以根据此种方法判断是否是int类型

            var _age:Int  = age.text.toInt()!

            if(_age < 0){

                UIAlertView(title: "提示", message: "年龄必须为正数", delegate: nil, cancelButtonTitle: "确认").show()

                age.becomeFirstResponder()

                return

            }

            row.setValue(_age, forKey: "age")

        }else{

            UIAlertView(title: "提示", message: "年龄必须为正数", delegate: nil, cancelButtonTitle: "确认").show()

            age.becomeFirstResponder()

            return

        }

        context?.save(nil)

        age.resignFirstResponder()  //第一响应事件取消,消除键盘操作

    }

}


2016-05-23 16:23:46 Batac_Lee 阅读数 414

第一种查询方式:

// 1. sql语句
        let sql = "select * from t_stu"

        // 2. 执行语句
        // 参数1: 一个已经打开的数据库
        // 参数2: sql语句
        // 参数3: 回调代码(*) 当查询到结果之后, 会调用这个代码块, 把结果返回给我们
            // 参数1: 参数4
            // 参数2: 列的个数
            // 参数3: 值组成的数组
            // 参数4: 列名组成的数组
            // 返回值: 如果返回的是 0 继续查询, 非0 代表停止查询
        // 参数4: 参数3里面的参数1
        // 参数5: 错误信息

        let db = SQLiteTool.shareInstance.db
        sqlite3_exec(db, sql, { (firsPara, columnCount, values, columnNames) -> Int32 in

            // 操作的是一行, 一行记录
            // 包含了很多个列
            // 遍历这些列(列名称, 列对应的值)
            let count = Int(columnCount)
            for i in 0..<count {
                // 代表每一列
                // (列名称, 列对应的值)
                // UnsafeMutablePointer<Int8>
                let columnName = columnNames[i]
                let columnNameStr = String(CString: columnName, encoding: NSUTF8StringEncoding)


                // UnsafeMutablePointer<Int8>
                let value = values[i]
                //该处注意:如果value为空,程序会崩溃
                let valueStr = String(CString: value, encoding: NSUTF8StringEncoding)
                //改进为:
                //var valueStr : String = ""
                //if value == nil{
                  //  valueStr = "isNil"
                //}else{
                  // valueStr = String(value)
                //}


                print(columnNameStr, valueStr)


            }


            return 2
            }, nil, nil)

第二种查询方式:

这种方式是使用的参数绑定,其中参数绑定和重置两步是不需要的;
//1.创建准备语句
        //创建的sql语句
        let sql = "select * from t_person"
        //调用数据库工具类单例的同时,打开了一个数据库db
        let db = SQLiteTool.shareinstance.db
        var tempDB : COpaquePointer = nil
        //第三个参数为-1,代表自动计算长度
        if sqlite3_prepare_v2(db, sql, -1, &tempDB, nil) != SQLITE_OK{
            print("预处理失败")
            return
        }
        //2.绑定参数(请求数据的时候,可以省略)
        //3.执行
        while sqlite3_step(tempDB) == SQLITE_ROW{
            //1.确定有多少列
            let count = sqlite3_column_count(tempDB)
            print(count)
            //遍历每一列
            for i in 0..<count{
                //获取每一列的名字
                let columnName = sqlite3_column_name(tempDB, i)
                //对每列的名字进行编码处理
                let columnNameStr = String(CString: columnName, encoding: NSUTF8StringEncoding)

                print(columnNameStr!)
                //获取每一列的类型
                let type = sqlite3_column_type(tempDB, i)
                //根据不同的类型,进行对应的取值
                if type == SQLITE_TEXT {
                    let name = UnsafePointer<CChar>(sqlite3_column_text(tempDB, i))
                    var nameStr : String?
                    if name == nil {
                        nameStr = "isNil"
                    }else{
                        nameStr = String(CString: name, encoding: NSUTF8StringEncoding)
                    }
                    print(nameStr!)
                }
                if type == SQLITE_INTEGER {
                    let age = sqlite3_column_int(tempDB, i)
                    print(age)
                }
                if type == SQLITE_FLOAT {
                    let score = sqlite3_column_double(tempDB, i)
                    print(score)
                }

            }
注意:如果有两列是相同的标注类型:则需要另外处理;
2015-09-28 19:30:30 baixiaozhe 阅读数 1589

不知道csdn博客怎么加附件,原帖有工程文件

http://www.hangge.com/blog/cache/detail_645.html

使用方法:

ViewController.swift代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import UIKit
 
class ViewControllerUIViewController {
     
    var db:SQLiteDB!
     
    @IBOutlet var txtUname: UITextField!
    @IBOutlet var txtMobile: UITextField!
     
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
         
        //获取数据库实例
        db = SQLiteDB.sharedInstance()
        //如果表还不存在则创建表
        db.execute(
         "create table if not exists t_user(uid integer primary key,uname varchar(20),mobile varchar(20))"
        )
        //如果有数据则加载
        initUser()
    }
 
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
     
    //点击保存
    @IBAction func saveClicked(sender: AnyObject) {
        saveUser()
    }
     
    //从SQLite加载数据
    func initUser() {
        let data = db.query("select * from t_user")
        if data.count > 0 {
            //获取最后一行数据显示
            let user = data[data.count - 1] as SQLRow
            txtUname.text = user["uname"]?.asString()
            txtMobile.text = user["mobile"]?.asString()
        }
    }
     
    //保存数据到SQLite
    func saveUser() {
        let uname = self.txtUname.text!
        let mobile = self.txtMobile.text!
        //插入数据库,这里用到了esc字符编码函数,其实是调用bridge.m实现的
        let sql = "insert into t_user(uname,mobile) values('\(uname)','\(mobile)')"
        println("sql: \(sql)")
        //通过封装的方法执行sql
        let result = db.execute(sql)
    }
}//

操作类SQLiteDb.swift:

//  TasksGalore
//
//  Created by Fahim Farook on 12/6/14.
//  Copyright (c) 2014 RookSoft Pte. Ltd. All rights reserved.
//

import Foundation
#if os(iOS)
import UIKit
#else
import AppKit
#endif

let SQLITE_DATE = SQLITE_NULL + 1

//private let SQLITE_STATIC = sqlite3_destructor_type(COpaquePointer(bitPattern:0))
//private let SQLITE_TRANSIENT = sqlite3_destructor_type(COpaquePointer(bitPattern:-1))

internal let SQLITE_STATIC = unsafeBitCast(0, sqlite3_destructor_type.self)
internal let SQLITE_TRANSIENT = unsafeBitCast(-1, sqlite3_destructor_type.self)

// MARK:- SQLColumn Class - Column Definition
class SQLColumn {
	var value:AnyObject? = nil
	var type:CInt = -1
	
	init(value:AnyObject, type:CInt) {
//		println("SQLiteDB - Initialize column with type: \(type), value: \(value)")
		self.value = value
		self.type = type
	}
	
	// New conversion functions
	func asString()->String {
		switch (type) {
			case SQLITE_INTEGER, SQLITE_FLOAT:
				return "\(value!)"
				
			case SQLITE_TEXT:
				return value as! String
				
			case SQLITE_BLOB:
				if let str = NSString(data:value as! NSData, encoding:NSUTF8StringEncoding) {
					return str as String
				} else {
					return ""
				}
			
			case SQLITE_NULL:
				return ""
				
			case SQLITE_DATE:
				let fmt = NSDateFormatter()
				fmt.dateFormat = "yyyy-MM-dd HH:mm:ss"
				return fmt.stringFromDate(value as! NSDate)
				
			default:
				return ""
		}
	}
	
	func asInt()->Int {
		switch (type) {
			case SQLITE_INTEGER, SQLITE_FLOAT:
				return value as! Int
				
			case SQLITE_TEXT:
				let str = value as! NSString
				return str.integerValue
				
			case SQLITE_BLOB:
				if let str = NSString(data:value as! NSData, encoding:NSUTF8StringEncoding) {
					return str.integerValue
				} else {
					return 0
				}
				
			case SQLITE_NULL:
				return 0
				
			case SQLITE_DATE:
				return Int((value as! NSDate).timeIntervalSince1970)
				
			default:
				return 0
		}
	}
	
	func asDouble()->Double {
		switch (type) {
			case SQLITE_INTEGER, SQLITE_FLOAT:
				return value as! Double
			
			case SQLITE_TEXT:
				let str = value as! NSString
				return str.doubleValue
			
			case SQLITE_BLOB:
				if let str = NSString(data:value as! NSData, encoding:NSUTF8StringEncoding) {
					return str.doubleValue
				} else {
					return 0.0
				}
			
			case SQLITE_NULL:
				return 0.0
			
			case SQLITE_DATE:
				return (value as! NSDate).timeIntervalSince1970
			
			default:
				return 0.0
		}
	}
	
	func asData()->NSData? {
		switch (type) {
			case SQLITE_INTEGER, SQLITE_FLOAT:
				let str = "\(value)" as NSString
				return str.dataUsingEncoding(NSUTF8StringEncoding)
			
			case SQLITE_TEXT:
				let str = value as! NSString
				return str.dataUsingEncoding(NSUTF8StringEncoding)
			
			case SQLITE_BLOB:
				return value as? NSData
			
			case SQLITE_NULL:
				return nil
			
			case SQLITE_DATE:
				let fmt = NSDateFormatter()
				fmt.dateFormat = "yyyy-MM-dd HH:mm:ss"
				let str = fmt.stringFromDate(value as! NSDate)
				return str.dataUsingEncoding(NSUTF8StringEncoding)
			
			default:
				return nil
		}
	}
	
	func asDate()->NSDate? {
		switch (type) {
			case SQLITE_INTEGER, SQLITE_FLOAT:
				let tm = value as! Double
				return NSDate(timeIntervalSince1970:tm)
			
			case SQLITE_TEXT:
				let fmt = NSDateFormatter()
				fmt.dateFormat = "yyyy-MM-dd HH:mm:ss"
				return fmt.dateFromString(value as! String)
			
			case SQLITE_BLOB:
				if let str = NSString(data:value as! NSData, encoding:NSUTF8StringEncoding) {
					let fmt = NSDateFormatter()
					fmt.dateFormat = "yyyy-MM-dd HH:mm:ss"
					return fmt.dateFromString(str as String)
				} else {
					return nil
				}
			
			case SQLITE_NULL:
				return nil
			
			case SQLITE_DATE:
				return value as? NSDate
			
			default:
				return nil
		}
	}
	
	func description()->String {
		return "Type: \(type), Value: \(value)"
	}
}

// MARK:- SQLRow Class - Row Definition
class SQLRow {
	var data = Dictionary<String, SQLColumn>()
	
	subscript(key: String) -> SQLColumn? {
		get {
			return data[key]
		}
		
		set(newVal) {
			data[key] = newVal
		}
	}
	
	func description()->String {
		return data.description
	}
}

// MARK:- SQLiteDB Class - Does all the work
class SQLiteDB {
	let DB_NAME = "data.db"
	let QUEUE_LABLE = "SQLiteDB"
	private var db:COpaquePointer = nil
	private var queue:dispatch_queue_t
	private var fmt = NSDateFormatter()
	private var GROUP = ""
	
	struct Static {
		static var instance:SQLiteDB? = nil
		static var token:dispatch_once_t = 0
	}
	
	class func sharedInstance() -> SQLiteDB! {
		dispatch_once(&Static.token) {
			Static.instance = self.init(gid:"")
		}
		return Static.instance!
	}
	
	class func sharedInstance(gid:String) -> SQLiteDB! {
		dispatch_once(&Static.token) {
			Static.instance = self.init(gid:gid)
		}
		return Static.instance!
	}
 
	required init(gid:String) {
		assert(Static.instance == nil, "Singleton already initialized!")
		GROUP = gid
		// Set queue
		queue = dispatch_queue_create(QUEUE_LABLE, nil)
		// Set up for file operations
		let fm = NSFileManager.defaultManager()
		let dbName:String = String.fromCString(DB_NAME)!
		var docDir = ""
		// Is this for an app group?
		if GROUP.isEmpty {
			// Get path to DB in Documents directory
			docDir = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] 
		} else {
			// Get path to shared group folder
			if let url = fm.containerURLForSecurityApplicationGroupIdentifier(GROUP) {
				docDir = url.path!
			} else {
				assert(false, "Error getting container URL for group: \(GROUP)")
			}
		}
		let path = (docDir as NSString).stringByAppendingPathComponent(dbName)
		print("Database path: \(path)")
		// Check if copy of DB is there in Documents directory
		if !(fm.fileExistsAtPath(path)) {
			// The database does not exist, so copy to Documents directory
            let resourcePath = NSBundle.mainBundle().resourcePath
			if resourcePath != nil {
                let from = (resourcePath! as NSString).stringByAppendingPathComponent(dbName)
				var error:NSError?
				do {
					try fm.copyItemAtPath(from, toPath: path)
				} catch let error1 as NSError {
					error = error1
					print("SQLiteDB - failed to copy writable version of DB!")
					print("Error - \(error!.localizedDescription)")
					return
				}
			}
		}
		// Open the DB
		let cpath = path.cStringUsingEncoding(NSUTF8StringEncoding)
		let error = sqlite3_open(cpath!, &db)
		if error != SQLITE_OK {
			// Open failed, close DB and fail
			print("SQLiteDB - failed to open DB!")
			sqlite3_close(db)
		}
		fmt.dateFormat = "YYYY-MM-dd HH:mm:ss"
	}
	
	deinit {
		closeDatabase()
	}
 
	private func closeDatabase() {
		if db != nil {
			// Get launch count value
			let ud = NSUserDefaults.standardUserDefaults()
			var launchCount = ud.integerForKey("LaunchCount")
			launchCount--
			print("SQLiteDB - Launch count \(launchCount)")
			var clean = false
			if launchCount < 0 {
				clean = true
				launchCount = 500
			}
			ud.setInteger(launchCount, forKey: "LaunchCount")
			ud.synchronize()
			// Do we clean DB?
			if !clean {
				sqlite3_close(db)
				return
			}
			// Clean DB
			print("SQLiteDB - Optimize DB")
			let sql = "VACUUM; ANALYZE"
			if execute(sql) != SQLITE_OK {
				print("SQLiteDB - Error cleaning DB")
			}
			sqlite3_close(db)
		}
	}
	
	// Execute SQL with parameters and return result code
	func execute(sql:String, parameters:[AnyObject]?=nil)->CInt {
		var result:CInt = 0
		dispatch_sync(queue) {
			let stmt = self.prepare(sql, params:parameters)
			if stmt != nil {
				result = self.execute(stmt, sql:sql)
			}
		}
		return result
	}
	
	// Run SQL query with parameters
	func query(sql:String, parameters:[AnyObject]?=nil)->[SQLRow] {
		var rows = [SQLRow]()
		dispatch_sync(queue) {
			let stmt = self.prepare(sql, params:parameters)
			if stmt != nil {
				rows = self.query(stmt, sql:sql)
			}
		}
		return rows
	}
	
	// Show alert with either supplied message or last error
	func alert(msg:String) {
		dispatch_async(dispatch_get_main_queue()) {
#if os(iOS)
			let alert = UIAlertView(title: "SQLiteDB", message:msg, delegate: nil, cancelButtonTitle: "OK")
			alert.show()
#else
			let alert = NSAlert()
			alert.addButtonWithTitle("OK")
			alert.messageText = "SQLiteDB"
			alert.informativeText = msg
			alert.alertStyle = NSAlertStyle.WarningAlertStyle
			alert.runModal()
#endif
		}
	}
	
	// Private method which prepares the SQL
	private func prepare(sql:String, params:[AnyObject]?)->COpaquePointer {
		var stmt:COpaquePointer = nil
		let cSql = sql.cStringUsingEncoding(NSUTF8StringEncoding)
		// Prepare
		let result = sqlite3_prepare_v2(self.db, cSql!, -1, &stmt, nil)
		if result != SQLITE_OK {
			sqlite3_finalize(stmt)
			if let error = String.fromCString(sqlite3_errmsg(self.db)) {
				let msg = "SQLiteDB - failed to prepare SQL: \(sql), Error: \(error)"
				print(msg)
			}
			return nil
		}
		// Bind parameters, if any
		if params != nil {
			// Validate parameters
			let cntParams = sqlite3_bind_parameter_count(stmt)
			let cnt = CInt(params!.count)
			if cntParams != cnt {
				let msg = "SQLiteDB - failed to bind parameters, counts did not match. SQL: \(sql), Parameters: \(params)"
				print(msg)
				return nil
			}
			var flag:CInt = 0
			// Text & BLOB values passed to a C-API do not work correctly if they are not marked as transient.
			for ndx in 1...cnt {
//				println("Binding: \(params![ndx-1]) at Index: \(ndx)")
				// Check for data types
				if let txt = params![ndx-1] as? String {
					flag = sqlite3_bind_text(stmt, CInt(ndx), txt, -1, SQLITE_TRANSIENT)
				} else if let data = params![ndx-1] as? NSData {
					flag = sqlite3_bind_blob(stmt, CInt(ndx), data.bytes, CInt(data.length), SQLITE_TRANSIENT)
				} else if let date = params![ndx-1] as? NSDate {
					let txt = fmt.stringFromDate(date)
					flag = sqlite3_bind_text(stmt, CInt(ndx), txt, -1, SQLITE_TRANSIENT)
				} else if let val = params![ndx-1] as? Double {
					flag = sqlite3_bind_double(stmt, CInt(ndx), CDouble(val))
				} else if let val = params![ndx-1] as? Int {
					flag = sqlite3_bind_int(stmt, CInt(ndx), CInt(val))
				} else {
					flag = sqlite3_bind_null(stmt, CInt(ndx))
				}
				// Check for errors
				if flag != SQLITE_OK {
					sqlite3_finalize(stmt)
					if let error = String.fromCString(sqlite3_errmsg(self.db)) {
						let msg = "SQLiteDB - failed to bind for SQL: \(sql), Parameters: \(params), Index: \(ndx) Error: \(error)"
						print(msg)
					}
					return nil
				}
			}
		}
		return stmt
	}
	
	// Private method which handles the actual execution of an SQL statement
	private func execute(stmt:COpaquePointer, sql:String)->CInt {
		// Step
		var result = sqlite3_step(stmt)
		if result != SQLITE_OK && result != SQLITE_DONE {
			sqlite3_finalize(stmt)
			if let err = String.fromCString(sqlite3_errmsg(self.db)) {
				let msg = "SQLiteDB - failed to execute SQL: \(sql), Error: \(err)"
				print(msg)
			}
			return 0
		}
		// Is this an insert
		let upp = sql.uppercaseString
		if upp.hasPrefix("INSERT ") {
			// Known limitations: http://www.sqlite.org/c3ref/last_insert_rowid.html
			let rid = sqlite3_last_insert_rowid(self.db)
			result = CInt(rid)
		} else if upp.hasPrefix("DELETE") || upp.hasPrefix("UPDATE") {
			var cnt = sqlite3_changes(self.db)
			if cnt == 0 {
				cnt++
			}
			result = CInt(cnt)
		} else {
			result = 1
		}
		// Finalize
		sqlite3_finalize(stmt)
		return result
	}
	
	// Private method which handles the actual execution of an SQL query
	private func query(stmt:COpaquePointer, sql:String)->[SQLRow] {
		var rows = [SQLRow]()
		var fetchColumnInfo = true
		var columnCount:CInt = 0
		var columnNames = [String]()
		var columnTypes = [CInt]()
		var result = sqlite3_step(stmt)
		while result == SQLITE_ROW {
			// Should we get column info?
			if fetchColumnInfo {
				columnCount = sqlite3_column_count(stmt)
				for index in 0..<columnCount {
					// Get column name
					let name = sqlite3_column_name(stmt, index)
					columnNames.append(String.fromCString(name)!)
					// Get column type
					columnTypes.append(self.getColumnType(index, stmt:stmt))
				}
				fetchColumnInfo = false
			}
			// Get row data for each column
			let row = SQLRow()
			for index in 0..<columnCount {
				let key = columnNames[Int(index)]
				let type = columnTypes[Int(index)]
				if let val:AnyObject = self.getColumnValue(index, type:type, stmt:stmt) {
//						println("Column type:\(type) with value:\(val)")
					let col = SQLColumn(value: val, type: type)
					row[key] = col
				}
			}
			rows.append(row)
			// Next row
			result = sqlite3_step(stmt)
		}
		sqlite3_finalize(stmt)
		return rows
	}
	
	// Get column type
	private func getColumnType(index:CInt, stmt:COpaquePointer)->CInt {
		var type:CInt = 0
		// Column types - http://www.sqlite.org/datatype3.html (section 2.2 table column 1)
		let blobTypes = ["BINARY", "BLOB", "VARBINARY"]
		let charTypes = ["CHAR", "CHARACTER", "CLOB", "NATIONAL VARYING CHARACTER", "NATIVE CHARACTER", "NCHAR", "NVARCHAR", "TEXT", "VARCHAR", "VARIANT", "VARYING CHARACTER"]
		let dateTypes = ["DATE", "DATETIME", "TIME", "TIMESTAMP"]
		let intTypes  = ["BIGINT", "BIT", "BOOL", "BOOLEAN", "INT", "INT2", "INT8", "INTEGER", "MEDIUMINT", "SMALLINT", "TINYINT"]
		let nullTypes = ["NULL"]
		let realTypes = ["DECIMAL", "DOUBLE", "DOUBLE PRECISION", "FLOAT", "NUMERIC", "REAL"]
		// Determine type of column - http://www.sqlite.org/c3ref/c_blob.html
		let buf = sqlite3_column_decltype(stmt, index)
//		println("SQLiteDB - Got column type: \(buf)")
		if buf != nil {
			var tmp = String.fromCString(buf)!.uppercaseString
			// Remove brackets
			let pos = tmp.positionOf("(")
			if pos > 0 {
				tmp = tmp.subStringTo(pos)
			}
			// Remove unsigned?
			// Remove spaces
			// Is the data type in any of the pre-set values?
//			println("SQLiteDB - Cleaned up column type: \(tmp)")
			if intTypes.contains(tmp) {
				return SQLITE_INTEGER
			}
			if realTypes.contains(tmp) {
				return SQLITE_FLOAT
			}
			if charTypes.contains(tmp) {
				return SQLITE_TEXT
			}
			if blobTypes.contains(tmp) {
				return SQLITE_BLOB
			}
			if nullTypes.contains(tmp) {
				return SQLITE_NULL
			}
			if dateTypes.contains(tmp) {
				return SQLITE_DATE
			}
			return SQLITE_TEXT
		} else {
			// For expressions and sub-queries
			type = sqlite3_column_type(stmt, index)
		}
		return type
	}
	
	// Get column value
	private func getColumnValue(index:CInt, type:CInt, stmt:COpaquePointer)->AnyObject? {
		// Integer
		if type == SQLITE_INTEGER {
			let val = sqlite3_column_int(stmt, index)
			return Int(val)
		}
		// Float
		if type == SQLITE_FLOAT {
			let val = sqlite3_column_double(stmt, index)
			return Double(val)
		}
		// Text - handled by default handler at end
		// Blob
		if type == SQLITE_BLOB {
			let data = sqlite3_column_blob(stmt, index)
			let size = sqlite3_column_bytes(stmt, index)
			let val = NSData(bytes:data, length: Int(size))
			return val
		}
		// Null
		if type == SQLITE_NULL {
			return nil
		}
		// Date
		if type == SQLITE_DATE {
			// Is this a text date
			let txt = UnsafePointer<Int8>(sqlite3_column_text(stmt, index))
			if txt != nil {
				if let buf = NSString(CString:txt, encoding:NSUTF8StringEncoding) {
					let set = NSCharacterSet(charactersInString: "-:")
					let range = buf.rangeOfCharacterFromSet(set)
					if range.location != NSNotFound {
						// Convert to time
						var time:tm = tm(tm_sec: 0, tm_min: 0, tm_hour: 0, tm_mday: 0, tm_mon: 0, tm_year: 0, tm_wday: 0, tm_yday: 0, tm_isdst: 0, tm_gmtoff: 0, tm_zone:nil)
						strptime(txt, "%Y-%m-%d %H:%M:%S", &time)
						time.tm_isdst = -1
						let diff = NSTimeZone.localTimeZone().secondsFromGMT
						let t = mktime(&time) + diff
						let ti = NSTimeInterval(t)
						let val = NSDate(timeIntervalSince1970:ti)
						return val
					}
				}
			}
			// If not a text date, then it's a time interval
			let val = sqlite3_column_double(stmt, index)
			let dt = NSDate(timeIntervalSince1970: val)
			return dt
		}
		// If nothing works, return a string representation
		let buf = UnsafePointer<Int8>(sqlite3_column_text(stmt, index))
		let val = String.fromCString(buf)
//		println("SQLiteDB - Got value: \(val)")
		return val
	}
}


2017-10-23 20:05:10 BUG_delete 阅读数 2138

1,什么是Realm
Realm 于2014 年7月发布,是一个跨平台的移动数据库引擎,专门为移动应用的数据持久化而生。其目的是要取代 Core Data  SQLite

2,关于Realm,你要知道下面几点:
(1)使用简单,大部分常用的功能(比如插入、查询等)都可以用一行简单的代码轻松完成,学习成本低。
(2)Realm 不是基于 Core Data,也不是基于 SQLite 封装构建的。它有自己的数据库存储引擎。
(3)Realm 具有良好的跨平台特性,可以在 iOS  Android 平台上共同使用。代码可以使用 Swift  Objective-C 以及 Java 语言来编写。
(4)Realm 还提供了一个轻量级的数据库查看工具(Realm Browser)。你也可以用它进行一些简单的编辑操作(比如插入和删除操作) 

3,支持的类型
(1)Realm 支持以下的属性类型:BoolInt8Int16Int32Int64DoubleFloatStringDate(精度到秒)以及Data.
(2)也可以使用 List<object>  Object 来建立诸如一对多、一对一之类的关系模型,此外 Object 的子类也支持此功能。

4,Realm的安装配置 
(1)先去 Realm 的官网去下载最新框架:http://static.realm.io/downloads/swift/latest。(或者使用cocoapods下载RealmSwift第三方。最新的使用方法案例官网地址:https://realm.io/cn/docs/swift/latest/
(2)拖拽 RealmSwift.framework  Realm.framework 文件到”Embedded Binaries”选项中。选中 Copy items if needed 并点击 Finish
原文:Swift - Realm数据库的使用详解(附样例)


5,将数据插入到数据库中
下面代码判断默认数据库中是否有数据,如果没有的话将几个自定义对像插入到数据库中。
(1)这里以个人消费记录为例,我们先定义消费类别类,和具体消费记录类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import Foundation
import RealmSwift
 
//消费类型
class ConsumeType:Object {
    //类型名
    @objc dynamic var name = ""
}
 
//消费条目
class ConsumeItem:Object {
    //条目名
    @objc dynamic var name = ""
    //金额
    @objc dynamic var cost = 0.00
    //时间
    @objc dynamic var date = Date()
    //所属消费类别
    @objc dynamic var type:ConsumeType?
}

(2)判断数据库记录是否为空,空的话则插入数据库(这里以默认数据库为例)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import UIKit
import RealmSwift
 
class ViewController: UIViewController {
     
    override func viewDidLoad() {
        super.viewDidLoad()
         
        //使用默认的数据库
        let realm = try! Realm()
        //查询所有的消费记录
        let items = realm.objects(ConsumeItem.self)
        //已经有记录的话就不插入了
        if items.count>0 {
            return
        }
         
        //创建两个消费类型
        let type1 = ConsumeType()
        type1.name = "购物"
        let type2 = ConsumeType()
        type2.name = "娱乐"
         
        //创建三个消费记录
        let item1 = ConsumeItem(value: ["买一台电脑",5999.00,Date(),type1]) //可使用数组创建
         
        let item2 = ConsumeItem()
        item2.name = "看一场电影"
        item2.cost = 30.00
        item2.date = Date(timeIntervalSinceNow: -36000)
        item2.type = type2
         
        let item3 = ConsumeItem()
        item3.name = "买一包泡面"
        item3.cost = 2.50
        item3.date = Date(timeIntervalSinceNow: -72000)
        item3.type = type1
         
        // 数据持久化操作(类型记录也会自动添加的)
        try! realm.write {
            realm.add(item1)
            realm.add(item2)
            realm.add(item3)
        }
         
        //打印出数据库地址
        print(realm.configuration.fileURL ?? "")
    }
     
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

6,Data类型数据的存取
参考另一篇文章:Swift - Realm数据库中图片的插入、读取(Data类型数据的存储) 

7,使用Realm Browser查看数据库  
(1)默认数据库是应用的 Documents 文件夹下的一个名为“default.realm”。
1
2
//打印出数据库地址
print(realm.configuration.fileURL ?? "")
(2)使用 Realm Browser 工具可以很方便的对.realm数据库进行读取和编辑(在 App Store 中搜索 Realm Browser 即可下载)。
可以看到,上面的几个对象已经成功的插入到数据库中来。

原文:Swift - Realm数据库的使用详解(附样例)


原文:Swift - Realm数据库的使用详解(附样例)


8,从数据库中读取记录并显示到表格中来
(1)通过查询操作,Realm 将会返回包含 Object 集合的 Results 实例。Results 的表现和 Array 十分相似,并且包含在 Results 中的对象能够通过索引下标进行访问。 
(2)所有的查询(包括查询和属性访问)在 Realm 中都是延迟加载的,只有当属性被访问时,才能够读取相应的数据。 
(3)查询结果并不是数据的拷贝:修改查询结果(在写入事务中)会直接修改硬盘上的数据。

下面我们把库里的数据加载出来,并通过表格显示出来。
效果图如下:
原文:Swift - Realm数据库的使用详解(附样例)


代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import UIKit
import RealmSwift
 
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource  {
     
    @IBOutlet weak var tableView: UITableView!
     
    var dformatter = DateFormatter()
     
    //保存从数据库中查询出来的结果集
    var consumeItems:Results<ConsumeItem>?
     
    override func viewDidLoad() {
        super.viewDidLoad()
         
        self.dformatter.dateFormat = "MM月dd日 HH:mm"
         
        self.tableView!.delegate = self
        self.tableView!.dataSource = self
        //创建一个重用的单元格
        self.tableView!.register(UITableViewCell.self, forCellReuseIdentifier: "MyCell")
         
        //使用默认的数据库
        let realm = try! Realm()
        //查询所有的消费记录
        consumeItems = realm.objects(ConsumeItem.self)
    }
     
    //在本例中,只有一个分区
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1;
    }
     
    //返回表格行数(也就是返回控件数)
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.consumeItems!.count
    }
     
    //创建各单元显示内容(创建参数indexPath指定的单元)
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
        -> UITableViewCell {
        //同一形式的单元格重复使用,在声明时已注册
        let cell = UITableViewCell(style: .value1, reuseIdentifier: "MyCell")
        let item = self.consumeItems![indexPath.row]
        cell.textLabel?.text = item.name + " ¥" + String(format: "%.1f", item.cost)
        cell.detailTextLabel?.text = self.dformatter.string(from: item.date)
        return cell
    }
     
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

9,查询前N条数据
Realm无法直接限制查询数量。所以我们如果想要查出部分数据(比如前5条记录),也是全部查出来后在结果集中捞取。
1
2
3
4
5
6
//查询并取出前5条数据
let dogs = try! Realm().objects(Dog.self)
for i in 0..<5 {
    let dog = dogs[i]
    // ...
}
Realm为何无法限制查询数量?
通常查询数据库数据时,我们可以在sql语句中添加一些限制语句(比如rownumlimittop等)来限制返回的结果集的行数。
但我们使用Realm会发现,它没有这种分页功能,感觉不管查什么都是把所有的结果都捞出来。比如我们只要User表的前10条数据,那么做法是先查询出所有的User数据,再从结果集中取出前10条数据。
有人可能会担心,如果数据库中数据非常多,那每次都这么查不会影响性能吗?
其实大可放心,由于Realm都是延迟加载的,只有当属性被访问时,才能够读取相应的数据。不像通常数据库,查询后,查询结果是从数据库拷贝一份出来放在内存中的。而Realm的查询结果应该说是数据库数据的引用,就算你查出来,如果不用也不会占用什么内存。

10,支持断言查询(Predicate),这样可以通过条件查询特定数据
同时可以使用链式查询数据。
1
2
3
4
5
6
7
8
9
//查询花费超过10元的消费记录(使用断言字符串查询)
consumeItems = realm.objects(ConsumeItem.self).filter("cost > 10")
 
//查询花费超过10元的购物记录(使用 NSPredicate 查询)
let predicate = NSPredicate(format: "type.name = '购物' AND cost > 10")
consumeItems = realm.objects(ConsumeItem.self).filter(predicate)
 
//使用链式查询
consumeItems = realm.objects(ConsumeItem.self).filter("cost > 10").filter("type.name = '购物'")
支持的断言:
  • 比较操作数(comparison operand)可以是属性名称或者某个常量,但至少有一个操作数必须是属性名称;
  • 比较操作符 ==、<=、<、>=、>、!=, 以及 BETWEEN 支持 int、long、long long、float、double 以及 NSDate 属性类型的比较,比如说 age == 45;
  • 相等比较 ==以及!=,比如说Results<Employee>().filter("company == %@", company)
  • 比较操作符 == and != 支持布尔属性;
  • 对于 NSString 和 NSData 属性来说,我们支持 ==、!=、BEGINSWITH、CONTAINS 以及 ENDSWITH 操作符,比如说 name CONTAINS ‘Ja’;
  • 字符串支持忽略大小写的比较方式,比如说 name CONTAINS[c] ‘Ja’ ,注意到其中字符的大小写将被忽略;
  • Realm 支持以下复合操作符:“AND”、“OR” 以及 “NOT”。比如说 name BEGINSWITH ‘J’ AND age >= 32;
  • 包含操作符 IN,比如说 name IN {‘Lisa’, ‘Spike’, ‘Hachi’};
  • ==、!=支持与 nil 比较,比如说 Results<Company>().filter("ceo == nil")。注意到这只适用于有关系的对象,这里 ceo 是 Company 模型的一个属性。
  • ANY 比较,比如说 ANY student.age < 21
  • 注意,虽然我们不支持复合表达式类型(aggregate expression type),但是我们支持对对象的值使用 BETWEEN 操作符类型。比如说,Results<Person>.filter("age BETWEEN %@", [42, 43]])。

11,查询结果的排序
1
2
//查询花费超过10元的消费记录,并按升序排列
consumeItems = realm.objects(ConsumeItem.self).filter("cost > 10").sorted(byKeyPath: "cost")

12,使用List实现一对多关系
List 中可以包含简单类型的 Object,表面上和可变的 Array 非常类似。
注意:List 只能够包含 Object 类型,不能包含诸如String之类的基础类型。 
如果打算给我们的 Person 数据模型添加一个“dogs”属性,以便能够和多个“dogs”建立关系,也就是表明一个 Person 可以有多个 Dog,那么我们可以声明一个List类型的属性:
1
2
3
4
5
6
7
8
9
10
class Person: Object {
    ... // 其余的属性声明
    let dogs = List<Dog>()
}
 
// 这里我们就可以使用已存在的狗狗对象来完成初始化
let aPerson = Person(value: ["李四", 30, [aDog, anotherDog]])
 
// 还可以使用多重嵌套
let aPerson = Person(value: ["李四", 30, [["小黑", 5], ["旺财", 6]]])
可以和之前一样,对 List 属性进行访问和赋值:
1
2
3
let someDogs = realm.objects(Dog.self).filter("name contains '小白'")
ZhangSan.dogs.append(objectsIn: someDogs)
ZhangSan.dogs.append(dahuang)

反向关系(Inverse Relationship)
通过反向关系(也被称为反向链接(backlink)),您可以通过一个特定的属性获取和给定对象有关系的所有对象。 Realm 提供了“链接对象 (linking objects)” 属性来表示这些反向关系。借助链接对象属性,您可以通过指定的属性来获取所有链接到指定对象的对象。
例如,一个 Dog 对象可以拥有一个名为 owners 的链接对象属性,这个属性中包含了某些 Person 对象,而这些 Person 对象在其 dogs 属性中包含了这一个确定的 Dog 对象。您可以将 owners 属性设置为 LinkingObjects 类型,然后指定其关系,说明其当中包含了 Person 对象。
1
2
3
4
5
6
7
8
class Dog: Object {
    @objc dynamic var name = ""
    @objc dynamic var age = 0
     
    // Realm 并不会存储这个属性,因为这个属性只定义了 getter
    // 定义“owners”,和 Person.dogs 建立反向关系
    let owners = LinkingObjects(fromType: Person.self, property: "dogs")
}

13,添加主键(Primary Keys) 
重写 Object.primaryKey() 可以设置模型的主键。
声明主键之后,对象将被允许查询,更新速度更加高效,并且要求每个对象保持唯一性。
一旦带有主键的对象被添加到 Realm 之后,该对象的主键将不可修改。
1
2
3
4
5
6
7
8
class Person: Object {
    @objc dynamic var id = 0
    @objc dynamic var name = ""
     
    override static func primaryKey() -> String? {
        return "id"
    }
}

14,添加索引属性(Indexed Properties)
重写 Object.indexedProperties() 方法可以为数据模型中需要添加索引的属性建立索引:
1
2
3
4
5
6
7
8
class Book: Object {
    @objc dynamic var price = 0
    @objc dynamic var title = ""
     
    override static func indexedProperties() -> [String] {
        return ["title"]
    }
}

15,设置忽略属性(Ignored Properties)
重写 Object.ignoredProperties() 可以防止 Realm 存储数据模型的某个属性。Realm 将不会干涉这些属性的常规操作,它们将由成员变量(var)提供支持,并且您能够轻易重写它们的 setter 和 getter。
1
2
3
4
5
6
7
8
9
10
11
12
class Person: Object {
    @objc dynamic var tmpID = 0
    var name: String { // 计算属性将被自动忽略
        return "\(firstName) \(lastName)"
    }
    @objc dynamic var firstName = ""
    @objc dynamic var lastName = ""
     
    override static func ignoredProperties() -> [String] {
        return ["tmpID"]
    }
}

16,修改更新数据 
(1)直接更新内容
1
2
3
4
// 在一个事务中更新对象
try! realm.write {
  consumeItem.name = "去北京旅行"
}
(2)通过主键更新
如果您的数据模型中设置了主键的话,那么您可以使用 Realm().add(_:update:) 来更新对象(当对象不存在时也会自动插入新的对象。)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/****** 方式1 ***/
// 创建一个带有主键的“书籍”对象,作为事先存储的书籍
let cheeseBook = Book()
cheeseBook.title = "奶酪食谱"
cheeseBook.price = 9000
cheeseBook.id = 1
 
// 通过 id = 1 更新该书籍
try! realm.write {
    realm.add(cheeseBook, update: true)
}
 
/****** 方式2 ***/
// 假设带有主键值 `1` 的“书籍”对象已经存在
try! realm.write {
    realm.create(Book.self, value: ["id": 1, "price": 22], update: true)
    // 这本书的`title`属性不会被改变
}
(3)键值编码 
这个是在运行时才能决定哪个属性需要更新的时候,这个对于大量更新的对象极为有用。
1
2
3
4
5
6
7
let persons = realm.objects(Person.self)
try! realm.write {
    // 更新第一个
    persons.first?.setValue(true, forKeyPath: "isFirst")
    // 将每个人的 planet 属性设置为“地球”
    persons.setValue("地球", forKeyPath: "planet")
}

17,删除数据
1
2
3
4
5
6
let cheeseBook = ... // 存储在 Realm 中的 Book 对象
 
// 在事务中删除一个对象
try! realm.write {
  realm.delete(cheeseBook)
}
也能够删除数据库中的所有数据
1
2
3
4
// 从 Realm 中删除所有数据
try! realm.write {
  realm.deleteAll()
}

18,Realm数据库配置 
(1)修改默认的的数据库
通过调用 Realm() 来初始化以及访问我们的 realm 变量。其指向的是应用的 Documents 文件夹下的一个名为“default.realm”的文件。
通过对默认配置进行更改,我们可以使用不同的数据库。比如给每个用户帐号创建一个特有的 Realm 文件,通过切换配置,就可以直接使用默认的 Realm 数据库来直接访问各自数据库:
1
2
3
4
5
6
7
8
9
10
func setDefaultRealmForUser(username: String) {
    var config = Realm.Configuration()
     
    // 使用默认的目录,但是使用用户名来替换默认的文件名
    config.fileURL = config.fileURL!.deletingLastPathComponent()
        .appendingPathComponent("\(username).realm")