版权声明:本文为博主原创文章,未经博主容许不得转载。html
(本ORM的源码已经上传到github上 (https://github.com/helloclq/BCSqliteORM_FMDB),你们能够下载测试,如发现什么问题或意见,欢迎你们提出并指正,oschina上的地址为:http://git.oschina.net/BlockCheng/BCSqliteORM_FMDB )java
想本身写一个objective-c的框架:利用objective-c的runtime特性,结合现有的操做sqlite的FMDB库,实现一个轻量级的ORM框架;要求: 颗粒度小、 量级轻、可配置度高、自由性大;android
一我的扛这个项目快两年了,最近在着手重构代码,想精简下现有的数据库层的代码。ios
目前项目的数据库层的状况是这个样子的:项目中共涉及的表有8张,数据库的操做,都是经过FMDB操做Sql直接实现的,都是纯粹的objective-c实体和数据库表间的映射操做,以下图。git
项目中,一张表,一个实体,对应一个DBManager类别,而各个类别里面的操做无非都是增删改查,类别里大体操做流程是:github
【增删改】:根据objective-c实体信息,手动生成sql,而后经由FMDB,直接操做放进sqliteweb
【查】:根据查询条件,手动生成sql,利用FMDB操做sqlite,获得查询结果,而后手动映射成objective-c实体objective-c
8个实体对8个表,每一个增删改查等都手动拼写一遍,总共8遍,而这些实体对数据库的步骤和代码格式基本相似,能够说,这里存在大量的“冗余”代码,必须优化!sql
另一个就是,每次需求变动或迭代时,每给数据库增长一个字段或减小一个字段,都要在 数据库模块 和 实体 间来回修改,着实浪费时间和精力!
数据库
.....(How fucking the code is!此处省略一万字....)
故必须优化这部分。。。
不重复发明轮子是软件开发过程当中的基本思想和原则,我也试着在github等网站上找过其余的开发项目,但很遗憾,没有找到能知足我需求的开源orm库,我须要的是这样的:
不须要持久化化实体中的每一个属性,能够根据本身须要,手动指定存储哪些字段。
数据库和实体间的映射关系能够本身手动指定,不强制绑定。
支持内置指定数据库,app内置数据。
支持各类SQL条件查询、更新、删除。
支持SQL操做日志输出,方便debug。
.....(How fucking the code is!此处有省略....)
(有人问过,当初项目架构时,为啥选用直接用FMDB操做Sqlite,而不用iOS的coredata,我有一堆的缘由,好比直接操做sql高效明了,coredata难用,另一个最重要的缘由是coredata不方便复用,不方便我之后将代码直接翻译到android平台上...总之,一万个理由)
简单地提下针对本次ORM相关的sqlite知识点。
sqlite官方站点:http://www.sqlite.org/
NULL 值为NULL(本次忽略)
INTEGER 值为带符号的整型,根据类别用1,2,3,4,6,8字节存储
REAL 值为浮点型,8字节存储
TEXT 值为text字符串,使用数据库编码(UTF-8, UTF-16BE or UTF-16-LE)存储
BLOB 值为二进制数据,具体看实际输入 (本次忽略)
点到便可,其余的能够参考:
https://www.google.com/#q=sqlite+tutorial
这里也只是简单的提下本次ORM相关的FMDB相关的知识,具体的请参考其github网页:https://github.com/ccgus/fmdb
FMDatabase *db = [FMDatabase databaseWithPath:@"/tmp/tmp.db"];
if (![db open]) { [db release]; return; }
FMResultSet *s = [db executeQuery:@"SELECT * FROM myTable"]; while ([s next]) { //retrieve values for each record }
FMResultSet *s = [db executeQuery:@"SELECT COUNT(*) FROM myTable"]; if ([s next]) { int totalCount = [s intForColumnIndex:0]; }
intForColumn: longForColumn: longLongIntForColumn: boolForColumn: doubleForColumn: stringForColumn: dateForColumn: dataForColumn: dataNoCopyForColumn: UTF8StringForColumnName: objectForColumnName:
[db close];
NSString *sql = @"create table bulktest1 (id integer primary key autoincrement, x text);" "create table bulktest2 (id integer primary key autoincrement, y text);" "create table bulktest3 (id integer primary key autoincrement, z text);" "insert into bulktest1 (x) values ('XXX');" "insert into bulktest2 (y) values ('YYY');" "insert into bulktest3 (z) values ('ZZZ');"; success = [db executeStatements:sql]; sql = @"select count(*) as count from bulktest1;" "select count(*) as count from bulktest2;" "select count(*) as count from bulktest3;"; success = [self.db executeStatements:sql withResultBlock:^int(NSDictionary *dictionary) { NSInteger count = [dictionary[@"count"] integerValue]; XCTAssertEqual(count, 1, @"expected one record for dictionary %@", dictionary); return 0; }];
NSDictionary *argsDict = [NSDictionary dictionaryWithObjectsAndKeys:@"My Name", @"name", nil]; [db executeUpdate:@"INSERT INTO myTable (name) VALUES (:name)" withParameterDictionary:argsDict]; [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @"this has \" lots of ' bizarre \" quotes '"]; [db executeUpdate:@"INSERT INTO myTable VALUES (?)", 42]; [db executeUpdateWithFormat:@"INSERT INTO myTable VALUES (%d)", 42];
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath]; [queue inDatabase:^(FMDatabase *db) { [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]]; [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]]; [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]]; FMResultSet *rs = [db executeQuery:@"select * from foo"]; while ([rs next]) { … } }]; [queue inTransaction:^(FMDatabase *db, BOOL *rollback) { [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]]; [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]]; [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]]; if (whoopsSomethingWrongHappened) { *rollback = YES; return; } // etc… [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:4]]; }];
介绍objective-c runtime的文章不少,这里,我只说我本次用到的几条。
NSString *NSStringFromClass(Class aClass);
objc_property_t class_getProperty(Class cls, const char *name)
const char *property_getName(objc_property_t property)
+ (NSString*)stringWithUTF8String:(const char *)nullTerminatedCString;
NSString* tmpPropertyName = obj; objc_property_t tmpPropery = class_getProperty(entityClass, [tmpPropertyName UTF8String]); const char * type = property_getAttributes(tmpPropery); NSString * typeString = [NSString stringWithUTF8String:type];
NSString* propertyName = key; NSOject *entity; id propertyValue = [(NSObject*)entity valueForKey:propertyName];
NSString* tmpPropertyName = obj; objc_property_t tmpPropery = class_getProperty(entityClass, [tmpPropertyName UTF8String]); const char * type = property_getAttributes(tmpPropery); NSString * typeString = [NSString stringWithUTF8String:type]; NSArray * attributes = [typeString componentsSeparatedByString:@","]; NSString * typeAttribute = [attributes objectAtIndex:0]; NSString * propertyType = [typeAttribute substringFromIndex:1]; const char * rawPropertyType = [propertyType UTF8String]; //https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html if (strcmp(rawPropertyType, @encode(CGFloat)) == 0 ||strcmp(rawPropertyType, @encode(float)) == 0 ||strcmp(rawPropertyType, @encode(double)) == 0 )/*0001*/ { //it's a float } else if (strcmp(rawPropertyType, @encode(int)) == 0) { //it's an int } else if (strcmp(rawPropertyType, @encode(long)) == 0) { //it's a long } else if (strcmp(rawPropertyType, @encode(long long)) == 0) { //it's long long } else if (strcmp(rawPropertyType, @encode(id)) == 0) { //it's some sort of object: id } else if (strcmp(rawPropertyType, @encode(BOOL)) == 0 || strcmp(rawPropertyType, @encode(_Bool)) == 0) { //it's some sort of object: id } else { // According to Apples Documentation you can determine the corresponding encoding values }/*0001*/ if ([typeAttribute hasPrefix:@"T@"] && [typeAttribute length] > 1)/*0001*/ { NSString * typeClassName = [typeAttribute substringWithRange:NSMakeRange(3, [typeAttribute length]-4)]; //turns @"NSDate" into NSDate Class typeClass = NSClassFromString(typeClassName); if (typeClass != nil) { if (typeClass == [NSDate class]) { //nsdate }else if (typeClass == [NSString class] ||typeClass == [NSMutableString class] ) { } } }/*0001*/
同上,这里也只是简述。
java web中,我用过的ORM 框架有,hibernate、mybatis,而他们的实现基本原理能够简述为:反射 + XML或annotation配置 + 基本设计模式,生成sql来操做数据库。
android平台上,好些同窗写了orm和其余的一些注入框架,用的也基本都是 【反射+注解+基本模式】。
鉴于4中的简述,故本次orm中,我也参照这个模式进行构建,java中的反射,对应objective-c中,就是runtime,但遗憾的是,objective-c中无注解,或者也没啥人用xml,plist配置也不现实。故我只能采用绕道的方式实现了:协议 + 类方法。
故本次ORM的基本原理能够归纳为:
【runtime】 + 【协议 + 类方法】 + 基本模式 + FMDB 。
-------------------------------本次完,分割线--------------------------------------
(2)预期+思考【利用objective-c的runtime特性,结合FMDB实现轻量级的ORM】
(3)实体和结构【利用objective-c的runtime特性,结合FMDB实现轻量级的ORM】
【后续持续更新】
再次声明:本文为博主原创文章,未经博主容许不得转载。
(之前在本站及一些其余论坛中发表过几篇博客,后被某些知名网站博主直接copy后,改成本身的原创,碍于时间成本,懒得追究,故本问两次强调,请自重!!)