因为这篇文章 搞定一个问题 [__NSCFString bytes]: unrecognized selector sent to instance
iOS-FMDB-数据库使用-(executeUpdate: withArgumentsInArray:,executeUpdate:)
数据库插入NSData
首先把 字典 序列化成 NSData
然后把 NSData 写入数据库
然后读取的时候再反序列化 成字典
但是 读取的时候反序列化失败 崩溃
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFString bytes]: unrecognized selector sent to instance 0x7fbc99092000'
*** First throw call stack:
(
0 CoreFoundation 0x000000010fd94d4b __exceptionPreprocess + 171
1 libobjc.A.dylib 0x000000010f7f221e objc_exception_throw + 48
2 CoreFoundation 0x000000010fe04f04 -[NSObject(NSObject) doesNotRecognizeSelector:] + 132
3 CoreFoundation 0x000000010fd1a005 ___forwarding___ + 1013
4 CoreFoundation 0x000000010fd19b88 _CF_forwarding_prep_0 + 120
5 Foundation 0x000000010f2bfc2e -[NSKeyedUnarchiver initForReadingWithData:] + 314
6 Foundation 0x000000010f2bfa4a +[NSKeyedUnarchiver unarchiveObjectWithData:] + 61
7 chatcoin 0x000000010edf03df __59-[DAChatDatabaseManager queryMessagesWithParams:fromTable:]_block_invoke + 191
8 FMDB 0x000000010eff604c __30-[FMDatabaseQueue inDatabase:]_block_invoke + 124
...
libc++abi.dylib: terminating with uncaught exception of type NSException
不要以为是查询数据的时候有问题, 之所以崩溃是无法反序列化, 这个原因是由于插入数据的时候造成的
因为我用的表名是 变量 不是固定的
所以需要字符串拼接
以前的代码是这样的
NSString *messageId = dict[@"messageId"];
NSData *messageData = [NSKeyedArchiver archivedDataWithRootObject:dict];
// 这个时候直接反序列化, 是没有任何问题的
NSDictionary *message11 = [NSKeyedUnarchiver unarchiveObjectWithData:messageData];
// sql 语句 拼接参数和表名
NSString *sql = [NSString stringWithFormat:@"INSERT INTO %@ (userId, messageId, messageData) VALUES ('%@', '%@', '%@');", tableN, userId, messageId,messageData];
insertTableResult = [db executeUpdate:sql];
如果我换个固定表名, 就没有任何问题 所以 是表名 引起的问题
NSString *sql = [NSString stringWithFormat:@"INSERT INTO t_message (userId, messageId, messageData) VALUES ('%@', '%@', '%@');",userId, messageId,messageData];
所以这个原因 就是由于文章 开头那个链接 说明的问题
如果有 特殊字符 例如 ' 那么就会插入失败
其实我都插入成功了.... 只是反序列化不成...
修正: 按照 文章说的 方式2插入数据, 既能 动态表名,又能反序列化成功
// 方式2 当方式1 中插入的数据有特殊的字符是,就会数据写入失败,使用方式2可以完美解决这一问题。
NSString *sql001 = [NSString stringWithFormat:@"insert into %@ (userId, messageId, messageData) values (?,?,?)",tableN];
if ([db executeUpdate:sql001 withArgumentsInArray:@[ userId, messageId,messageData]]){
NSLog(@"插入成功!");
}else{
NSLog(@"插入失败");
*rollback = YES;
[db rollback];
break;
}
经过测试,发现上面这个方法有个问题
因为它 需要一个数组,也就是这样
NSArray *arr = @[conversationId,loginId, messageId,fromId, toId, messageContent, userType, messageBodyType, timestamp];
insertTableResult = [db executeUpdate:sql withArgumentsInArray:arr];
如果 toId 这个字段 为nil 那么就会崩溃, 因为你向数组中插入了一个nil 数据
而数组是以nil结尾的,
目前有个替代办法就是
id toId = messageRes.toId ? messageRes.toId : [NSNull null];
因为数组中可以插入 [NSNull null]
NSArray * array = @[@11, @15, nil, @"1111"]; //这个写法肯定报错!
NSArray * array = @[@11, @15, [NSNull null], @"1111"]; //这个写法就可以
但是 当你从数据库中取数据的时候, 这个时候 取的就是 [NSNull null] ,而你的 toId 这个字段 其实定义的的 NSString
取数据的时候 用这个
messageRes.toId = [set objectForColumnName:@"toId"]; // 取出为 [NSNull]
messageRes.toId = [set stringForColumn:@"toId"]; //取出为 nil 字符串
更新 解决方案 还是用 executeUpdate:
// 把数据库表名 拼接到字符串中
NSString *sql = [NSString stringWithFormat:@"insert into %@ (loginId, messageContent, userType, messageData) values (?,?,?,?)",tableN];
insertTableResult = [db executeUpdate:sql, messageRes.loginId, messageRes.messageContent, @(messageRes.userType), messageData];
FMDB的三种查询方法
-(FMResultSet *)executeQuery:(NSString*)sql, ...
- (FMResultSet *)executeQueryWithFormat:(NSString*)format, ...
- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments
// executeUpdate : 不确定的参数用?来占位
// executeUpdateWithFormat : 不确定的参数用%@、%d等来占位
// 此处一定要注意Format后面不是单纯的字符串NSString,不能随便的组装字符串,它只能接Values括号里面的占位参数,不能更改Values前面的参数,例如:
错误的表达:
[self.db executeUpdateWithFormat:@"INSERT INTO %@ VALUES (%@, %d);", @"t_student (name, age)",name, arc4random_uniform(40)];
正确表达:
[self.db executeUpdateWithFormat:@"INSERT INTO t_student (name, age) VALUES (%@, %d);", name, arc4random_uniform(40)];
更新 附 代码
- (void)insertMessages:(NSArray *)messages toTable:(NSString *)tableName
{
[self creatChatTableWithName:tableName];
NSString *tableN = [NSString stringWithFormat:@"t_chat_%@", tableName];
BOOL __block insertTableResult = NO;
NSInteger __block insertNum = 0;
[dataBaseQueue inTransaction:^(FMDatabase *db, BOOL *rollback) {
//FMDB默认情况是关闭缓存的
[db shouldCacheStatements];
for (DAChatMessageRes *messageRes in messages) {
// conversationId loginId messageId fromId toId messageContent userType messageBodyType timestamp
NSString *sql = [NSString stringWithFormat:@"insert or replace into %@ (conversationId, loginId, messageId, fromId, toId, messageContent, userType, messageBodyType, timestamp) values (?,?,?,?,?,?,?,?,?)",tableN];
insertTableResult = [db executeUpdate:sql, messageRes.conversationId,messageRes.loginId, messageRes.messageId,messageRes.fromId, messageRes.toId, messageRes.messageContent, @(messageRes.userType), @(messageRes.messageBodyType), @(messageRes.timestamp)];
if (insertTableResult){
insertNum += 1;
}else{
NSLog(@"插入失败");
*rollback = YES;
[db rollback];
break;
}
}
}];
NSLog(@"插入 %zd条数据", insertNum);
}