JHHK

欢迎来到我的个人网站
行者常至 为者常成

课程6 iOS基础进阶班(第六部分)-大神推荐

参考:iOS基础进阶班合辑-大神推荐
本文是上面课程的摘要,只用于自己快速浏览.

目录

29 数据库

【录播】1.(了解)数据库简介(13分钟)
    iOS中数据存取的方式
        Plist(NSArray\NSDictionary)
        Preference(偏好设置\NSUserDefaults)
        NSCoding(NSKeyedArchiver\NSkeyedUnarchiver)
            必须整存整取,比如数组内存放三条微博数据,归档后写入文件
            当存放第四条时,需要将前三条取出解档,添加第四条再归档

        SQLite3
            嵌入式轻量化数据库

        Core Data
            本质是对SQLite3的包装,使用更面向对象化

    什么是数据库
        数据库(Database)是按照数据结构来组织、存储和管理数据的仓库
        数据库可以分为2大种类
            关系型数据库(主流)
            对象型数据库

    常用关系型数据库
        PC端:Oracle、MySQL、SQL Server、Access、DB2、Sybase
        嵌入式\移动客户端:SQLite



【录播】2.(了解)Navicat安装(6分钟)
    Navicat是一款著名的数据库管理软件,支持大部分主流数据库(包括SQLite)
    利用Navicat建立数据库连接



【录播】3.(掌握)Navicat基本使用(11分钟)

    数据库文件:xxx.sqlite
        表1
            主键key(自增)   字段1   字段2   字段3
            val             val   val     val

        表2:t_Student
            id  name    age score
            0   mj      18  99
            1   lc      19  100

        表3

    SQLite将数据划分为以下几种存储类型:
        integer : 整型值
        real : 浮点值
        text : 文本字符串
        blob : 二进制数据(比如文件)
        null : 空类型


        

【录播】4.(熟悉)SQL语言简介(8分钟)
    什么是SQL
        SQL(structured query language):结构化查询语言
        SQL是一种对关系型数据库中的数据进行定义和操作的语言
        SQL语言简洁,语法简单,好学好用

    什么是SQL语句
        使用SQL语言编写出来的句子\代码,就是SQL语句
        在程序运行过程中,要想操作(增删改查,CRUD)数据库中的数据,必须使用SQL语句

    SQL语句的特点
        不区分大小写(比如数据库认为user和UsEr是一样的)
        每条语句都必须以分号 ; 结尾


    SQL语句的种类
        数据定义语句(DDL:Data Definition Language)
            包括create和drop等操作
            在数据库中创建新表或删除表(create table或 drop table)

        数据操作语句(DML:Data Manipulation Language)
            包括insert、update、delete等操作
            上面的3种操作分别用于添加、修改、删除表中的数据

        数据查询语句(DQL:Data Query Language)
            可以用于查询获得表中的数据
            关键字select是DQL(也是所有SQL)用得最多的操作
            其他DQL常用的关键字有where,order by,group by和having


【录播】5.(掌握)DDL语句(11分钟)
    创建表
        create table 表名 (字段名1 字段类型1, 字段名2 字段类型2, …) ;
        create table if not exists 表名 (字段名1 字段类型1, 字段名2 字段类型2, …) ;
        create table t_student (id integer, name text, age inetger, score real) ;
        create table t_student(name, age);

    删除表
        drop table 表名 ;
        drop table if exists 表名 ;
        drop table t_student ;

    修改表名
        ALTER TABLE 旧表名 RENAME TO 新表名
        alter t_student rename to t_stu

    新增属性
        ALTER TABLE 表名 ADD COLUMN 列名 数据类型 限定符
        alter t_student add column birthday



【录播】6.(掌握)DML语句(14分钟)
    不能为空
        not null :规定字段的值不能为null
    不能重复
        unique :规定字段的值必须唯一
    默认值
        default :指定字段的默认值

    主键
        保证数据库数据的唯一
        create table t_student (id integer, name text not null unique, age integer not null default 1) ;
        create table t_student (id integer primary key autoincrement, name text, age integer) ;
        primary key 默认包含了 not null unique



【录播】7.(掌握)DML语句(7分钟)
    insert into t_student (name, age) values (‘sz’, 10) ;
    update t_student set name = ‘wex’, age = 20 ; 
    delete from t_student ;

30 数据库 2

【录播】8.(掌握)条件语句(10分钟)
    update t_student set age = 5 where age > 10 and name != ‘wex’ ;
    delete from t_student where age <= 10 or age > 30 ;
    update t_student set score = age where name = ‘wex’ ;



【录播】9.(掌握)DQL-查询相关语句(17分钟)
    查询
        select name, age from t_student ;
        select * from t_student ;
        select * from t_student where age > 10 ;  //  条件查询

    统计
        select count(*) from t_student
        select count(age) from t_student
        select avg(score) from t_student
        avg(X)
        sum(X)
        min(X)
        max(X)

    排序
        select * from t_student order by age ;
        select * from t_student order by age desc ;  //降序
        select * from t_student order by age asc ;   // 升序(默认)
        select * from t_student order by age asc, height desc ;


    limit分页
        select * from t_student limit 4, 8 ;

        select * from t_student limit 7 ;
            相当于select * from t_student limit 0, 7 ;

        分页
            第1页:limit 0, 5
            第2页:limit 5, 5
            第3页:limit 10, 5
            第n页:limit 5*(n-1), 5




【录播】10.(掌握)多表查询(13分钟)
    多表查询
        select 字段1, 字段2, … from 表名1, 表名2 ;

    多表连接查询
        select 字段1, 字段2, … from 表名1, 表名2  where 表名1.关联id = 表名2.关联id;



【录播】11.(掌握)代码实现SQLite-DDL(18分钟)

    创建并打开数据库文件
            var db: OpaquePointer? = nil
            override func viewDidLoad() {
                super.viewDidLoad()
                let docPath  = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory,
                                                                FileManager.SearchPathDomainMask.userDomainMask,
                                                                true)[0]
                print("path = ",docPath)
                let fileName = docPath + "/demo.sqlite"
                /**
                *  sqlite3_open 使用这个函数打开一个数据库
                *  参数一: 需要打开的数据库文件路径
                *  参数二: 一个指向SQlite3数据结构的指针, 到时候操作数据库都需要使用这个对象
                *  功能作用: 如果需要打开数据库文件路径不存在, 就会创建该文件;如果存在, 就直接打开; 可通过返回值, 查看是否打开成功
                */
                if sqlite3_open(fileName, &db) != SQLITE_OK {
                    print("打开失败")
                }else{
                    print("打开成功")
                }
            }


    创建表
        func createTable() {
            // 创建SQL语句
            let sql = "CREATE TABLE IF NOT EXISTS t_student (name TEXT, age INTEGER, score text, id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT)"

            // 执行SQL语句
            // 参数一: 数据库
            // 参数二: 需要执行的SQL语句
            // 参数三: 回调结果, 执行完毕之后的回调函数, 如果不需要置为NULL
            // 参数四: 参数三的第一个参数, 刻意通过这个传值给回调函数 如果不需要置为NULL
            // 参数五: 错误信息, 通过传递一个地址, 赋值给外界, 如果不需要置为NULL
            if sqlite3_exec(db, sql, nil, nil, nil) != SQLITE_OK{
                print("创建表失败")
            }else{
                print("创建表成功")
            }
        }

    
    删除表
        func dropTable() {
            let sql = "DROP TABLE IF EXISTS t_student"
            if sqlite3_exec(db, sql, nil, nil, nil) != SQLITE_OK{
                print("删除表失败")
            }else{
                print("删除表成功")
            }
        }


【录播】12.(掌握)代码实现SQLite-DDL-封装(8分钟)
    封装成一个单例对象
        SQLTool.shareInstance
【录播】13.(掌握)代码实现DML语句-Insert(7分钟)

        func insert(tableName: String, columnNameArray: [String], valueArray: [String]) -> Bool {
            // 1. 创建SQL语句
            let tempColumnNameArray = columnNameArray as NSArray
            let columnNames = tempColumnNameArray.componentsJoined(by: ",")
            let tempValueArray = valueArray as NSArray
            let values = "\'" + tempValueArray.componentsJoined(by: "\',\'") + "\'"
            let sql = "INSERT INTO \(tableName)(\(columnNames)) values (\(values))"
            print("sql = ",sql)

            // 2. 执行SQL语句
            return (sqlite3_exec(db, sql, nil, nil, nil) == SQLITE_OK)
        }
【录播】14(了解)代码实现DML语句-Insert绑定参数(22分钟)

    extension SQLTool {
        /// 预处理语句,绑定参数(未进行拆分,效率低)
        /// - Parameters:
        ///   - tableName: 表名称
        ///   - columnNameArray: key 数组
        ///   - values: values 数组
        /// - Returns: 返回是否执行成功
        
        @discardableResult
        func insertBind(tableName: String, columnNameArray: [String], values: [Any]) -> Bool  {

            let tempColumnNameArray = columnNameArray as NSArray
            let columnNames = tempColumnNameArray.componentsJoined(by: ",")

            // 1. 创建需要预编译的SQL语句
            var tempValues: Array = [String]()
            
            // 创建 ? 的占位个数
            for _ in 0 ..< values.count{
                tempValues.append("?")
            }
            
            let valuesStr = (tempValues as NSArray).componentsJoined(by: ",")
            let prepareSql = "INSERT INTO \(tableName)(\(columnNames)) values (\(valuesStr))"
            //print("prepareSql = ",prepareSql)
            
            
            
        
            //预处理SQL语句, 并生成 "语句句柄" , 后续会使用这样的语句句柄绑定数值, 并执行
            var stmt: OpaquePointer? = nil
            if sqlite3_prepare_v2(db, prepareSql, -1, &stmt, nil) != SQLITE_OK{
                print("预处理失败")
                // 释放语句资源
                sqlite3_finalize(stmt)
                return false
            }

            
            
            var index: Int32 = 1
            for obj in values{

                if obj is Int {
                    let temp: sqlite_int64 = obj as! sqlite_int64
                    sqlite3_bind_int64(stmt, index, temp)
                    
                } else if obj is Double {
                    sqlite3_bind_double(stmt, index, obj as! Double)
                    
                } else if obj is String {
                    /**
                    第5个参数
                    此参数有两个常数,SQLITE_STATIC告诉sqlite3_bind_text函数字符串为常量,可以放心使用;
                    而SQLITE_TRANSIENT会使得sqlite3_bind_text函数对字符串做一份拷贝。
                    一般使用这两个常量参数来调用sqlite3_bind_text。
                    */
                    sqlite3_bind_text(stmt, index, obj as! String, -1, SQLITE_TRANSIENT)

                } else {
                    continue
                }

                index+=1
            }

            
            var result: Bool = false
            if sqlite3_step(stmt) == SQLITE_DONE {
                //print("插入成功")
                result = true
                
            } else {
                //print("插入失败")
                result = false

            }

            // 将语句复位
            if sqlite3_reset(stmt) != SQLITE_OK {
                //print("复位失败")
                result = false
            }
            
            // 释放语句
            sqlite3_finalize(stmt)

            return result
        }
    }

31 数据库 3

【录播】15.(掌握代码实现)DML语句-Insert数据优化(20分钟)

    extension SQLTool {
        
        /// 获取预处理语句,预处理语句不需要每次都创建,获取一次可以多次绑定值
        /// - Parameters:
        ///   - tableName: 表名
        ///   - columnNameArray: key 数组
        /// - Returns: 预处理语句
        func getPrepareStmt(tableName: String, columnNameArray: [String]) -> OpaquePointer! {
            let tempColumnNameArray = columnNameArray as NSArray
            let columnNames = tempColumnNameArray.componentsJoined(by: ",")

            // 1. 创建需要预编译的SQL语句
            var tempValues: Array = [String]()
            // 创建 ? 的占位个数
            for _ in 0 ..< columnNameArray.count{
                tempValues.append("?")
            }

            let valuesStr = (tempValues as NSArray).componentsJoined(by: ",")
            let prepareSql = "INSERT INTO \(tableName)(\(columnNames)) values (\(valuesStr))"

            var stmt: OpaquePointer? = nil

            // 预处理SQL语句, 并生成 "语句句柄" , 后续会使用这样的语句句柄绑定数值, 并执行
            if sqlite3_prepare_v2(db, prepareSql, -1, &stmt, nil) != SQLITE_OK {
                print("预处理失败")
                // 释放语句资源
                sqlite3_finalize(stmt)
                return nil
            }
            return stmt
        }
        
        
        /// 绑定值到预处理语句,并执行
        /// - Parameters:
        ///   - stmt: 预处理语句
        ///   - values: values 数组
        /// - Returns: true执行成功,false执行失败
        @discardableResult
        func insertBind(stmt: OpaquePointer!, values: [Any]) -> Bool  {

            //一 绑定值
            var index: Int32 = 1
            for obj in values{
                if obj is Int{
                    let temp: sqlite_int64 = obj as! sqlite_int64
                    sqlite3_bind_int64(stmt, index, temp)
                    
                } else if obj is Double {
                    sqlite3_bind_double(stmt, index, obj as! Double)
                    
                }else if obj is String {
                    /**
                    第5个参数
                    此参数有两个常数,SQLITE_STATIC告诉sqlite3_bind_text函数字符串为常量,可以放心使用;
                    而SQLITE_TRANSIENT会使得sqlite3_bind_text函数对字符串做一份拷贝。
                    一般使用这两个常量参数来调用sqlite3_bind_text。
                    */
                    sqlite3_bind_text(stmt, index, obj as! String, -1, SQLITE_TRANSIENT)
                }else {
                    
                    continue
                }
                index+=1
            }

            
            //二 执行
            var result: Bool = false
            if sqlite3_step(stmt) == SQLITE_DONE{
    //            print("插入成功")
                result = true
            }else{
                //print("插入失败")
                result = false
            }
            return result
        }
        
        
        /// 重置预处理语句
        /// - Parameter stmt: 预处理语句
        /// - Returns: 是否成功
        @discardableResult
        func resetStmt(stmt: OpaquePointer!) -> Bool {
            // 将语句复位
            return sqlite3_reset(stmt) == SQLITE_OK

        }
        
        
        /// 释放预处理语句
        /// - Parameter stmt: 预处理语句
        func releaseStmt(stmt: OpaquePointer!) {
            // 释放语句
            sqlite3_finalize(stmt)
        }
    }
【录播】16.(掌握)代码实现-事务(19分钟)

XMGSQLTool.shareInstance.beginTransaction()

let result1 = Student.updateStudent("score = score - 10", condition: "name = 'zs'")
let result2 = Student.updateStudent("score1 = score + 10", condition: "name = 'wex'")

// 如果都执行成功再提交, 如果都不成功, 那就回滚
if result1 && result2 {
    XMGSQLTool.shareInstance.commitTransaction()
} else {
    XMGSQLTool.shareInstance.rollBackTransaction()
}
【录播】17代码实现DQL语句-方式1- sqlite3_exec(13分钟)

let block = { (parameter: UnsafeMutablePointer<Void>,
                       columnCount: Int32,
                       values: UnsafeMutablePointer<UnsafeMutablePointer<Int8>>,
                       columnNames: UnsafeMutablePointer<UnsafeMutablePointer<Int8>>) -> Int32 in
            
            let count = Int(columnCount)
            for i: Int in 0..<count
            {
                let column = columnNames[i]
                let columnStr = String(CString: column, encoding: NSUTF8StringEncoding)
                
                let value = values[i]
                let valueStr = String(CString: value, encoding: NSUTF8StringEncoding)
                print(columnStr! + "= " + valueStr!)
            }
            
            return 0
        }
        
        sqlite3_exec(db, selectSql,block , nil, nil)

【录播】18.(掌握)代码实现DQL语句-准备语句(18分钟)

方式1: sqlite3_exec

    作用: 可以通过回调来获取结果, 步骤相对来说简单, 结果数据类型没有特定类型(id)

    let selectSql = "select * from t_student"
    // 方式1:
    // 参数1: 一个打开的数据库
    // 参数2: 需要执行的SQL语句
    // 参数3: 查询结果回调(执行0次或多次)
        // 参数1: 参数4的值
        // 参数2: 列的个数
        // 参数3: 结果值的数组
        // 参数4: 所有列的名称数组
        // 返回值: 0代表继续执行一致到结束, 1代表执行一次
    // 参数4: 回调函数的第一个值
    // 参数5: 错误信息
    sqlite3_exec(db, selectSql, { (parameter: UnsafeMutablePointer<Void>, columnCount: Int32, values: UnsafeMutablePointer<UnsafeMutablePointer<Int8>>, columnNames: UnsafeMutablePointer<UnsafeMutablePointer<Int8>>) -> Int32 in

            let count = Int(columnCount)

            for i: Int in 0..<count {
                let column = columnNames[i]
                let columnStr = String(CString: column, encoding: NSUTF8StringEncoding)

                let value = values[i]
                let valueStr = String(CString: value, encoding: NSUTF8StringEncoding)
                print(columnStr! + "= " + valueStr!)
            }

            return 0
        }, nil, nil)


方式2: 通过"准备语句"

    作用: 可以处理不同特定类型, 步骤相对来说复杂

    var stmt: COpaquePointer = nil

    if sqlite3_prepare(db, selectSql, -1, &stmt, nil) != SQLITE_OK {
        print("预处理失败")
        return
    }

    // 因为查询语句中没有占位?, 所以, 可以省略"绑定步骤"
    // 执行语句
    // sqlite3_step, 当使用这个方法时, 执行完毕后, 会自动跳到结果集的下一行, 如果依然有记录就返回SQLITE_ROW,
    while sqlite3_step(stmt) == SQLITE_ROW {

        let columnCount = sqlite3_column_count(stmt)

        for i in 0..<columnCount {

            //  获取列的类型
            let type = sqlite3_column_type(stmt, i)

            if type == SQLITE_TEXT {
                let text = UnsafePointer<Int8>(sqlite3_column_text(stmt, i))
                let str = String(CString: text, encoding: NSUTF8StringEncoding)
                print(str)
            } else if xxx {

            }
        }

    }

    sqlite3_finalize(stmt)

【录播】19.(掌握)FMDB基本使用(19分钟)

1. 什么是FMDB?
    FMDB是iOS平台的SQLite数据库框架
    FMDB以OC的方式封装了SQLite的C语言API

2. FMDB有什么优势?
    使用起来更加面向对象,省去了很多麻烦、冗余的C语言代码
    提供了多线程安全的数据库操作方法,有效地防止数据混乱

3. 安装方式
    Cocoapods
        use_frameworks!
            使用dynamic frameworks的方式集成
        pod 'FMDB'

    手动集成(swift)
        1. 导入FMDB文件
        2. 导入系统依赖库sqlite3.0.tbd
        3. 建立桥接文件, 并导入需要的头文件

4. 核心类
    FMDatabase 
        一个FMDatabase对象就代表一个单独的SQLite数据库 
        用来执行SQL语句

    FMResultSet 
        使用FMDatabase执行查询后的结果集

    FMDatabaseQueue
        用于在多线程中执行多个查询或更新,它是线程安全的


5. 使用步骤

    打开数据库
        通过指定SQLite数据库文件路径来创建FMDatabase对象
            文件路径有三种情况
                具体文件路径
                    如果不存在会自动创建

                空字符串@""
                    会在临时目录创建一个空的数据库
                    当FMDatabase连接关闭时,数据库文件也被删除
                nil
                会创建一个内存中临时数据库,
                当FMDatabase连接关闭时,数据库会被销毁


            FMDatabase *db = [FMDatabase databaseWithPath:path]; 
            if ([db open]) { NSLog(@"打开成功!"); }


    执行更新
        在FMDB中,除查询以外的所有操作,都称为“更新”
            create、drop、insert、update、delete等

        使用executeUpdate:方法执行更新
            - (BOOL)executeUpdate:(NSString*)sql, ...
            - (BOOL)executeUpdateWithFormat:(NSString*)format, ...
            - (BOOL)executeUpdate:(NSString)sql withArgumentsInArray:(NSArray )arguments

        示例
            [db executeUpdate:@"UPDATE t_student SET age = ? WHERE name = ?;", @20, @"Jack"]



    执行查询
        查询方法
            - (FMResultSet )executeQuery:(NSString)sql, ...
            - (FMResultSet )executeQueryWithFormat:(NSString)format, ...
            - (FMResultSet )executeQuery:(NSString )sql withArgumentsInArray:(NSArray *)arguments
        demo
            // 查询数据
            FMResultSet *rs = [db executeQuery:@"SELECT * FROM t_student"];
            // 遍历结果集
            while ([rs next]) {
                NSString *name = [rs stringForColumn:@"name"]; 
                int age = [rs intForColumn:@"age"];
                double score = [rs doubleForColumn:@"score"];
            }
        

    关闭数据库 
        database.close()


【录播】20.(掌握)FMDabaseQueue(9分钟)

FMDatabaseQueue介绍
    FMDatabase这个类是线程不安全的,如果在多个线程中同时使用一个FMDatabase实例,会造成数据混乱等问题 
    为了保证线程安全,FMDB提供方便快捷的FMDatabaseQueue类 


FMDatabaseQueue的创建
    FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:path];


简单使用
    [queue inDatabase:^(FMDatabase *db) {

        [db executeUpdate:@"INSERT INTO t_student(name) VALUES (?)", @"Jack"]; 
    
        // 查询
        FMResultSet *rs = [db executeQuery:@"select * from t_student"];
    }];


使用事务
    [queue inTransaction:^(FMDatabase db, BOOL rollback) { 

        [db executeUpdate:@"INSERT INTO t_student(name) VALUES (?)", @"Jack"]; 
        //查询
        FMResultSet *rs = [db executeQuery:@"select * from t_student"];
    }];
【录播】21.- 总结(18分钟)

行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.