2018-04-11_FMDB

本文示例代碼查看

0.0 FMDB 結(jié)構(gòu)圖

結(jié)構(gòu)圖

1.FMDB 使用方法

1.1 類

1.1.1 三個(gè)基本類

  • FMDatabase: 代表一個(gè) SQLite 數(shù)據(jù)庫(kù),用于執(zhí)行 SQLite 語(yǔ)句;
  • FMResultSet:表示FMDatabase執(zhí)行查詢后結(jié)果集
  • FMDatabaseQueue:如果你想在多線程中執(zhí)行多個(gè)查詢或更新,你應(yīng)該使用該類。這是線程安全的稽鞭。

1.1.2 其他類

  • FMDatabasePool:使用任務(wù)池的形式,對(duì)多線程的操作提供支持凹髓。(官方不推薦使用)
  • FMDatabaseAdditions: 擴(kuò)展`FMDatabase類,新增對(duì)查詢結(jié)果只返回單個(gè)值的方法進(jìn)行簡(jiǎn)化,對(duì)表、列是否存在,版本號(hào),校驗(yàn)SQL等等功能。

1.2 數(shù)據(jù)庫(kù)操作

創(chuàng)建Student模型

Student.h

#import <Foundation/Foundation.h>

@interface Student : NSObject

@property (nonatomic,copy)NSString *name;

@property (nonatomic,copy)NSString *sex;

@property (nonatomic,assign) int age;

@property (nonatomic,assign) int studentID;

@end

1.2.1 創(chuàng)建數(shù)據(jù)庫(kù)

創(chuàng)建方式如下圖:


初始化數(shù)據(jù)庫(kù)方法
  • 以上初始化方法本質(zhì)上只是給了數(shù)據(jù)庫(kù)一個(gè)名字,并沒(méi)有真實(shí)創(chuàng)建或者獲取數(shù)據(jù)庫(kù),
  • open函數(shù)才是真正獲取到數(shù)據(jù)庫(kù)线欲,其本質(zhì)上也就是調(diào)用SQLite的C/C++接口 – sqlite3_open()
NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    
    NSString *file = [doc stringByAppendingPathComponent:@"student.sqlite"];
    NSLog(@"%@",file);
    
    //2.獲得數(shù)據(jù)庫(kù)
    FMDatabase *db = [FMDatabase databaseWithPath:file];
    
    //3.使用如下語(yǔ)句,如果打開失敗汽摹,可能是權(quán)限不足或者資源不足李丰。通常打開完操作操作后,需要調(diào)用`close`方法來(lái)關(guān)閉數(shù)據(jù)庫(kù)逼泣。在和數(shù)據(jù)庫(kù)交互之前嫌套,數(shù)據(jù)庫(kù)必須是打開的。如果資源或權(quán)限不足無(wú)法打開或創(chuàng)建數(shù)據(jù)庫(kù)圾旨,都會(huì)導(dǎo)致打開失敗踱讨。
    if ([db open]){
        //4.創(chuàng)表
        BOOL result = [db executeUpdate:@"CREATE TABLE IF NOT EXISTS student (studentID integer PRIMARY KEY AUTOINCREMENT, name text NOT NULL, age integer NOT NULL);"];
        if (result){
            NSLog(@"創(chuàng)建表成功");
        }else {
            NSLog(@"創(chuàng)建表失敗");
        }
    }

如果創(chuàng)建失敗可以調(diào)用 - (NSString*)lastErrorMessage;- (int)lastErrorCode;查看錯(cuò)誤信息

1.2.2 打開/關(guān)閉數(shù)據(jù)庫(kù)

打開數(shù)據(jù)庫(kù)
- (BOOL)open;
- (BOOL)openWithFlags:(int)flags;
- (BOOL)openWithFlags:(int)flags vfs:(NSString * _Nullable)vfsName;

*關(guān)閉數(shù)據(jù)庫(kù)
- (BOOL)close;
主要封裝了sqlite_close函數(shù)。
主要功能:
①清除緩存的prepare語(yǔ)句砍的;
②關(guān)閉數(shù)據(jù)庫(kù)(關(guān)閉數(shù)據(jù)的時(shí)候?qū)?shù)據(jù)庫(kù)當(dāng)中的prepare語(yǔ)句全部清除掉)

1.2.3 更新數(shù)據(jù)

使用

- (BOOL)executeUpdate:(NSString*)sql, ...;

- (BOOL)executeUpdateWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2);

- (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments;

方法執(zhí)行更新操作痹筛,該方法返回BOOL型, 成功返回YES, 失敗返回NO.

說(shuō)明:

  • 除了SELECT外的SQL操作,都被視為更新操作.包括CREATE, UPDATE, INSERT,ALTER,COMMIT,BEGIN,DETACH,DELETE,DROP,END, EXPLAIN,VACUUM, REPLACE等;

  • - (BOOL)executeUpdate:(NSString*)sql, ...;使用標(biāo)準(zhǔn)的SQL語(yǔ)句,參數(shù)用?來(lái)占位,參數(shù)必須是對(duì)象類型,不能是int,double``,bool等基本數(shù)據(jù)類型;

  • - (BOOL)executeUpdateWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2);使用字符串的格式化構(gòu)建SQL語(yǔ)句,參數(shù)用%@%d等來(lái)占位.

  • - (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments;
    也可以把對(duì)應(yīng)的參數(shù)裝到數(shù)組里面?zhèn)鬟M(jìn)去,SQL語(yǔ)句中的參數(shù)用?代替.
    如:

NSArray *sqlArr = @[@"靜靜", @11];

[db executeUpdate:@"INSERT INTO student (name,age) values (?,?)" withArgumentsInArray:sqlArr];
    NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    
    NSString *file = [doc stringByAppendingPathComponent:@"student.sqlite"];
    NSLog(@"%@",file);
    
    //2.獲得數(shù)據(jù)庫(kù)
    FMDatabase *db = [FMDatabase databaseWithPath:file];
    
    //3.使用如下語(yǔ)句廓鞠,如果打開失敗帚稠,可能是權(quán)限不足或者資源不足。通常打開完操作操作后床佳,需要調(diào)用 close 方法來(lái)關(guān)閉數(shù)據(jù)庫(kù)滋早。在和數(shù)據(jù)庫(kù)交互 之前,數(shù)據(jù)庫(kù)必須是打開的砌们。如果資源或權(quán)限不足無(wú)法打開或創(chuàng)建數(shù)據(jù)庫(kù)杆麸,都會(huì)導(dǎo)致打開失敗搁进。
    if ([db open]){
        //4.創(chuàng)表
        BOOL result = [db executeUpdate:@"CREATE TABLE IF NOT EXISTS student (studentID integer PRIMARY KEY AUTOINCREMENT, name text NOT NULL, age integer NOT NULL);"];
        if (result){
            NSLog(@"創(chuàng)建表成功");
             //            INSERT INTO student (name, age) VALUES (?, ?);
             
             NSString * sql = @"INSERT INTO student (name, age) VALUES(?, ?);";
             NSString * name = @"靜靜";
             BOOL res = [db executeUpdate:sql, name, @2];
             
             //            BOOL res = [db executeUpdateWithFormat:@"INSERT INTO student (name, age) VALUES (%@, %d);", name, 1];
             
             if (!res) {
             NSLog(@"插入失敗");
             
             } else {
             NSLog(@"插入成功");
             }
             //查看最后一條錯(cuò)誤信息
             NSString *errorStr = [db lastErrorMessage];
             NSLog(@"%@",errorStr);
             
             //查看最后一條錯(cuò)誤
             NSError *error = [db lastError];
             NSLog(@"%@",error);
             
             [db close];
        }else {
            NSLog(@"創(chuàng)建表失敗");
        }
    }

運(yùn)行如圖:

image

還可以使用- (BOOL)executeStatements:(NSString *)sql;或者
- (BOOL)executeStatements:(NSString *)sql withResultBlock:(__attribute__((noescape)) FMDBExecuteStatementsCallbackBlock _Nullable)block;
將多個(gè)SQL執(zhí)行語(yǔ)句寫在一個(gè)字符串中,并執(zhí)行昔头。

NSString *sql1 = @"CREATE TABLE IF NOT EXISTS school (schoolID text PRIMARY KEY, schoolName text NOT NULL, address text NOT NULL);"
                                "insert into school (schoolID,schoolName,address) values ('1','第一中學(xué)','阿里部落');"
                ;
BOOL res1 =  [db executeStatements:sql1];

運(yùn)行如圖:

image

基本上- (BOOL)executeStatements:(NSString *)sql;系列函數(shù)最終封裝的都是

- (BOOL)executeStatements:(NSString *)sql withResultBlock:(__attribute__((noescape)) FMDBExecuteStatementsCallbackBlock _Nullable)block;函數(shù)饼问,而此函數(shù)又是對(duì)sqlite3_exec函數(shù)的封裝。

1.2.4 查詢數(shù)據(jù)

查詢有以下方法:


image

所有查詢函數(shù)都是對(duì)-(FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args;的簡(jiǎn)單封裝揭斧。

-(FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args;此函數(shù)的大致執(zhí)行步驟請(qǐng)見(jiàn)XMIND

    NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    
    NSString *file = [doc stringByAppendingPathComponent:@"student.sqlite"];
    NSLog(@"%@",file);
    
    //2.獲得數(shù)據(jù)庫(kù)
    FMDatabase *db = [FMDatabase databaseWithPath:file];
    if ([db open]) {
        FMResultSet *results = [db executeQuery:@"SELECT * FROM student"];
    
        // 2.遍歷結(jié)果
        while ([results next]) {
            int ID = [results intForColumn:@"studentID"];
            NSString *name = [results stringForColumn:@"name"];
            int age = [results intForColumn:@"age"];
            NSLog(@"%d %@ %d", ID, name, age);
        }
    }

1.2.5 刪除數(shù)據(jù)

    NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    
    NSString *file = [doc stringByAppendingPathComponent:@"student.sqlite"];
    NSLog(@"%@",file);
    
    //2.獲得數(shù)據(jù)庫(kù)
    FMDatabase *db = [FMDatabase databaseWithPath:file];
    if ([db open]) {
       BOOL res = [db executeUpdate:@"delete from student where studentID = ?;",@(1)];
        if (res) {
            NSLog(@"刪除成功");
        }else {
            NSLog(@"刪除失敗");
        }
    }


1.3 多線程處理

FMDatabase這個(gè)類是線程不安全的莱革,如果在多個(gè)線程中同時(shí)使用一個(gè)
FMDatabase實(shí)例,會(huì)造成數(shù)據(jù)混亂等問(wèn)題讹开。

為了保證線程安全盅视,FMDB提供方便快捷的FMDatabaseQueue

代碼如下:

    NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    
    NSString *file = [doc stringByAppendingPathComponent:@"student.sqlite"];
    NSLog(@"%@",file);
    
    FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:file];
    dispatch_queue_t q1 = dispatch_queue_create("queue1", NULL);
    dispatch_queue_t q2 = dispatch_queue_create("queue2", NULL);
    
    dispatch_async(q1, ^{
        for (int i = 0; i < 50; ++i) {
            [queue inDatabase:^(FMDatabase *db2) {
                
                if ([db2 open]){
                    BOOL result = [db2 executeUpdate:@"CREATE TABLE IF NOT EXISTS student (studentID integer PRIMARY KEY AUTOINCREMENT, name text NOT NULL, age integer NOT NULL);"];
                    if (result) {
                        NSString *insertSql1= [NSString stringWithFormat:
                                               @"INSERT INTO '%@' ('%@', '%@') VALUES (?, ?)",
                                               @"student", @"name", @"age"];
                        
                        NSString * name = [NSString stringWithFormat:@"深深 %d", i];
                        NSString * age = [NSString stringWithFormat:@"%d", 10+i];
                        
                        
                        BOOL res = [db2 executeUpdate:insertSql1, name, age];
                        if (!res) {
                            NSLog(@"error to inster data: %@", name);
                        } else {
                            NSLog(@"succ to inster data: %@", name);
                        }
                    }
                }
                
                
            }];
        }
    });
    
    dispatch_async(q2, ^{
        for (int i = 0; i < 50; ++i) {
            [queue inDatabase:^(FMDatabase *db2) {
                if ([db2 open]) {
                    BOOL result = [db2 executeUpdate:@"CREATE TABLE IF NOT EXISTS student (studentID integer PRIMARY KEY AUTOINCREMENT, name text NOT NULL, age integer NOT NULL);"];
                    
                    if (result) {
                        NSString *insertSql2= [NSString stringWithFormat:
                                               @"INSERT INTO '%@' ('%@', '%@') VALUES (?, ?)",
                                               @"student", @"name", @"age"];
                        
                        NSString * name = [NSString stringWithFormat:@"靜靜 %d", i];
                        NSString * age = [NSString stringWithFormat:@"%d", 10+i];
                        
                        BOOL res = [db2 executeUpdate:insertSql2, name, age];
                        if (!res) {
                            NSLog(@"error to inster data: %@", name);
                        } else {
                            NSLog(@"succ to inster data: %@", name);
                        }
                    }
                }
                
                
            }];
        }
    });

1.4 事務(wù)

什么是事務(wù)

說(shuō)一下事務(wù)是什么旦万,比如說(shuō)我們有一個(gè)學(xué)生表和一個(gè)學(xué)生成績(jī)表左冬,而且一個(gè)學(xué)生對(duì)應(yīng)一個(gè)學(xué)生成績(jī)。比如小明的成績(jī)是100分纸型,那么我們要寫兩個(gè)sql語(yǔ)句對(duì)不同的表進(jìn)行插入數(shù)據(jù)。但是如果在這個(gè)過(guò)程中梅忌,小明這個(gè)學(xué)生成功的插入到數(shù)據(jù)庫(kù)狰腌,而成績(jī)插入時(shí)失敗了,怎么辦牧氮?這時(shí)事務(wù)就突出了它的作用琼腔。用事務(wù)可以對(duì)兩個(gè)表進(jìn)行同時(shí)插入,一旦一個(gè)表插入失敗踱葛,那么就會(huì)進(jìn)行事務(wù)回滾丹莲,就是讓另一個(gè)表也不進(jìn)行插入數(shù)據(jù)了。

簡(jiǎn)單的說(shuō)也就是尸诽,事務(wù)可以讓多個(gè)表的數(shù)據(jù)同時(shí)插入甥材,一旦有一個(gè)表操作失敗,那么其他表也都會(huì)失敗性含。當(dāng)然這種說(shuō)法是為了理解洲赵,不是嚴(yán)謹(jǐn)?shù)摹?/p>

那么對(duì)一個(gè)表大量插入數(shù)據(jù)時(shí)也可以用事務(wù)。比如sqlite3商蕴。

數(shù)據(jù)庫(kù)中insert into語(yǔ)句等操作是比較耗時(shí)的叠萍,假如我們一次性插入幾百幾千條數(shù)據(jù)就
會(huì)造成主線程阻塞,以至于UI界面卡住绪商。那么這時(shí)候我們就要開啟一個(gè)事物來(lái)進(jìn)行操作苛谷。

原因就是它以文件的形式存在磁盤中,每次訪問(wèn)時(shí)都要打開一次文件格郁,如果對(duì)數(shù)據(jù)庫(kù)進(jìn)行大量的操作腹殿,就很慢独悴。可是如果我們用事務(wù)的形式提交赫蛇,開始事務(wù)后绵患,進(jìn)行的大量操作語(yǔ)句都保存在內(nèi)存中,當(dāng)提交時(shí)才全部寫入數(shù)據(jù)庫(kù)悟耘,此時(shí)落蝙,數(shù)據(jù)庫(kù)文件也只用打開一次。如果操作錯(cuò)誤暂幼,還可以回滾事務(wù)筏勒。

1.4.1 基本使用

    NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    
    NSString *file = [doc stringByAppendingPathComponent:@"student.sqlite"];
    NSLog(@"%@",file);
    FMDatabase *db = [FMDatabase databaseWithPath:file];
    
    BOOL isSuccess=[db open];
    if (!isSuccess) {
        NSLog(@"打開數(shù)據(jù)庫(kù)失敗");
    }
    [db beginTransaction];
    BOOL isRollBack = NO;
    @try {
        for (int i = 0; i<500; i++) {
            NSString *nId = [NSString stringWithFormat:@"%d",i];
            NSString *strName = [[NSString alloc] initWithFormat:@"student_%d",i];
            NSString *sql = @"INSERT INTO student (name,age) VALUES (?,?)";
            BOOL a = [db executeUpdate:sql,strName,nId];
            if (!a) {
                NSLog(@"插入失敗1");
            }
        }
    }
    @catch (NSException *exception) {
        isRollBack = YES;
        [db rollback];
    }
    @finally {
        if (!isRollBack) {
            [db commit];
        }
    }
    [db close];

1.4.2 FMDB多線程使用事務(wù)

NSDate *date1 = [NSDate date];
    
    // 創(chuàng)建表
    NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    NSString *dbPath = [docPath stringByAppendingPathComponent:@"student.sqlite"];
    
    //多線程安全FMDatabaseQueue可以替代dataBase
    FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:dbPath];
    
    //開啟事務(wù)
    [queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
        
        if(![db open]){
            
            return NSLog(@"事務(wù)打開失敗");
        }
        
        BOOL result = [db executeUpdate:@"CREATE TABLE IF NOT EXISTS student (studentID integer PRIMARY KEY AUTOINCREMENT, name text NOT NULL, age integer NOT NULL);"];

        if (!result) {
            NSLog(@"error when creating student table");
            
            [db close];
        }
        
        for (int i = 0; i<500; i++) {
            
            NSString *insertSql2= [NSString stringWithFormat:
                                   @"INSERT INTO '%@' ('%@', '%@') VALUES (?, ?)",
                                   @"student", @"name", @"age"];
            
            NSString * name = [NSString stringWithFormat:@"靜靜 %d", i];
            NSString * age = [NSString stringWithFormat:@"%d", 10+i];
            
            BOOL result = [db executeUpdate:insertSql2, name, age];;
            if ( !result ) {
                //當(dāng)最后*rollback的值為YES的時(shí)候,事務(wù)回退旺嬉,如果最后*rollback為NO管行,事務(wù)提交
                *rollback = YES;
                return;
            }
        }
        
//        [db close];
    }];
    NSDate *date2 = [NSDate date];
    NSTimeInterval a = [date2 timeIntervalSince1970] - [date1 timeIntervalSince1970];
    NSLog(@"FMDatabaseQueue使用事務(wù)插入500條數(shù)據(jù)用時(shí)%.3f秒",a);

1.5 加解密

  • 使用SQLCipher進(jìn)行加密,改之前的pod 'FMDB'pod 'FMDB/SQLCipher' # FMDB with SQLCipher 即可以用cocoapods來(lái)安裝支持SQLCipher加密數(shù)據(jù)庫(kù)的FMDB

  • 使用-[FMDatabase setKey:]-[FMDatabase setKeyWithData:]
    來(lái)給數(shù)據(jù)庫(kù)設(shè)置密碼或者清除密碼邪媳,使用- [FMDatabase rekey:]- [FMDatabase rekeyWithData:]輸入數(shù)據(jù)庫(kù)密碼以求驗(yàn)證用戶身份捐顷。

這兩類函數(shù)分別對(duì)sqlite3_keysqlite3_rekey函數(shù)進(jìn)行了封裝。

使用方式與原來(lái)的方式一樣雨效,只需要數(shù)據(jù)庫(kù)open之后調(diào)用setKey設(shè)置一下秘鑰即可
下面摘了一段FMDatabaseopen函數(shù)迅涮,在sqlite3_open成功后調(diào)用setKey方法設(shè)置秘鑰

- (BOOL)open {
    if (_db) {
        return YES;
    }

    int err = sqlite3_open([self sqlitePath], &_db );
    if(err != SQLITE_OK) {
        NSLog(@"error opening!: %d", err);
        return NO;
    } else {
        //數(shù)據(jù)庫(kù)open后設(shè)置加密key
        [self setKey:encryptKey_];
    }

    if (_maxBusyRetryTimeInterval > 0.0) {
        // set the handler
        [self setMaxBusyRetryTimeInterval:_maxBusyRetryTimeInterval];
    }

    return YES;
}

為了不修改FMDB的源代碼,我們可以繼承自FMDatabase類重寫需要setKey的幾個(gè)方法徽龟,這里我繼承FMDatabase定義了一個(gè)JJEncryptDatabase類叮姑,提供打開加密文件的功能

JJEncryptDatabase.h

#import "FMDatabase.h"

@interface JJEncryptDatabase : FMDatabase
+ (instancetype)databaseWithPath:(NSString*)aPath encryptKey:(NSString *)encryptKey;

- (instancetype)initWithPath:(NSString*)aPath encryptKey:(NSString *)encryptKey;
@end

JJEncryptDatabase.m

import "JJEncryptDatabase.h"

#import <sqlite3.h>

@interface JJEncryptDatabase (){
    NSString *_encryptKey;
}
@end

@implementation JJEncryptDatabase
+ (instancetype)databaseWithPath:(NSString*)aPath encryptKey:(NSString *)encryptKey {
    return [[[self class]alloc]initWithPath:aPath encryptKey:encryptKey];
    
}

- (instancetype)initWithPath:(NSString*)aPath encryptKey:(NSString *)encryptKey{
    if (self == [self initWithPath:aPath]) {
        _encryptKey = encryptKey;
    }
    return self;
}

#pragma mark 復(fù)寫父類方法
- (BOOL)open {
    BOOL res = [super open];
    if (res && _encryptKey) {
        [self setKey:_encryptKey];
    }
    return res;
}

#if SQLITE_VERSION_NUMBER >= 3005000
- (BOOL)openWithFlags:(int)flags vfs:(NSString *)vfsName {
    BOOL res = [super openWithFlags:flags vfs:vfsName];
    if (res && _encryptKey) {
        //數(shù)據(jù)庫(kù)open后設(shè)置加密key
        [self setKey:_encryptKey];
    }
    return res;
}
#endif
@end

用法與FMDatabase一樣,只是需要傳入secretKey

加密使用

    NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    
    NSString *file = [doc stringByAppendingPathComponent:@"student.sqlite"];
    NSLog(@"%@",file);
    
    BOOL res = [JJEncryptHelper encryptDatabase:file encryptKey:self.encryptKey];
    
    NSString *msg = res ? @"encrypt success" : @"encrypt fail";
    [[[UIAlertView alloc] initWithTitle:nil message:msg delegate:nil cancelButtonTitle:nil otherButtonTitles:@"ok", nil] show];

解密使用

    NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    
    NSString *file = [doc stringByAppendingPathComponent:@"student.sqlite"];
    NSLog(@"%@",file);
    
    BOOL res = [JJEncryptHelper unEncryptDatabase:file encryptKey:@"123"];
    
    NSString *msg = res ? @"decrypt success" : @"decrypt fail";
    [[[UIAlertView alloc] initWithTitle:nil message:msg delegate:nil cancelButtonTitle:nil otherButtonTitles:@"ok", nil] show];
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末据悔,一起剝皮案震驚了整個(gè)濱河市传透,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌极颓,老刑警劉巖朱盐,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異菠隆,居然都是意外死亡托享,警方通過(guò)查閱死者的電腦和手機(jī)闰围,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門羡榴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人运敢,你說(shuō)我怎么就攤上這事忠售〉狙铮” “怎么了羊瘩?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵尘吗,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我黔宛,道長(zhǎng)臀晃,這世上最難降的妖魔是什么徽惋? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮回右,結(jié)果婚禮上漱挚,老公的妹妹穿的比我還像新娘旨涝。我一直安慰自己,他們只是感情好慨默,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布厦取。 她就那樣靜靜地躺著管搪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪霎箍。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天景埃,我揣著相機(jī)與錄音纠亚,去河邊找鬼蒂胞。 笑死条篷,一個(gè)胖子當(dāng)著我的面吹牛赴叹,可吹牛的內(nèi)容都是我干的乞巧。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蚕冬,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼囤热!你這毒婦竟也來(lái)了旁蔼?” 一聲冷哼從身側(cè)響起棺聊,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤躺屁,失蹤者是張志新(化名)和其女友劉穎犀暑,沒(méi)想到半個(gè)月后耐亏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年李根,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了房轿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片囱持。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖掩幢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情上鞠,我是刑警寧澤际邻,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站枯怖,受9級(jí)特大地震影響能曾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜寿冕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望玫恳。 院中可真熱鬧掀序,春花似錦不恭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)打颤。三九已至暴拄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間编饺,已是汗流浹背撕蔼。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留秽誊,地道東北人鲸沮。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像锅论,于是被迫代替她去往敵國(guó)和親讼溺。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容