iOS - 反射

版权所有,禁止匿名转载;禁止商业使用。

移动互联网下iOS客户端的开发,一般都会与服务端进行通讯,也会使用到Sqlite数据库来保存一些数据,按常规的搞法,一般都需要手动建表结构,写实体类对象,然后写插入、更新、查询等语句来实现功能,因此想到是否有一种通用的办法来进行一些代码方面的减负工作。利用反射的机制,可以很方便的实现。

首先,我们进行了以下的约定:

  1. sqlite的数据库表名直接使用实体类的类名;

  2. sqlite的数据字段使用实体类的属性名称;

  3. sqlite的数据类型统一设为text(引起sqlite这种文本数据库是动态类型的,存储的本质都是文本)

  4. 实体类的类型统一都设为NSString

做以上的约定,只是减少使用过程中,由于类型的不同造成不必要的麻烦,如果要支持各种类型,需要编写各种判断代码,进行格式的处理,有兴趣的同学可以进一步研究。

先扩展了NSObject,名称叫NSObject+Property,然后添加下面代码。

  1. 利用反射取得NSObject的属性,并存入到数组中

     - (NSArray *)getPropertyList: (Class)clazz
     {
         u_int count;
         objc_property_t *properties = class_copyPropertyList(clazz, &count);
         NSMutableArray *propertyArray = [NSMutableArray arrayWithCapacity:count];
    
         for (int i = 0; i < count ; i++)
         {
         const char* propertyName = property_getName(properties[i]);
         [propertyArray addObject: [NSString stringWithUTF8String: propertyName]];
         }
         free(properties); 
         return propertyArray;
     }
  2. 根据属性生成创建Sqlite表的语句

     - (NSString *)tableSql:(NSString *)tablename
     {
         NSMutableString *sql = [[NSMutableString alloc] init];
         NSArray *array = [self getPropertyList];
         [sql appendFormat:@"create table %@ (",tablename] ;
         NSInteger i = 0;
         for (NSString *key in array) {
         if (i>0) {
         [sql appendString:@","];
         }
         [sql appendFormat:@"%@ text",key];
         i++;
         }
         [sql appendString:@")"];
         return sql;
     }
  3. 把一个实体对象,封装成字典Dictionary  

     - (NSDictionary *)convertDictionary
     {
         NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
         NSArray *propertyList = [self getPropertyList];
         for (NSString *key in propertyList)
         {
             SEL selector = NSSelectorFromString(key);
    
             #pragma clang diagnostic push
             #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
             id value = [self performSelector:selector];
             #pragma clang diagnostic pop
    
             if (value == nil)
             {
                 value = [NSNull null];
             }
    
             [dict setObject:value forKey:key];
         }
         return dict;
     }
  4. 从一个字典中还原成一个实体对象

     - (void)dictionaryForObject:(NSDictionary*) dict
     {
         for (NSString *key in [dict allKeys])
         {
             id value = [dict objectForKey:key];
    
             if (value==[NSNull null])
             {
                 continue;
              }
             if ([value isKindOfClass:[NSDictionary class]]) 
             {
                 id subObj = [self valueForKey:key];
                 if (subObj)
                      [subObj dictionaryForObject:value];
             }
             else
             {
                 [self setValue:value forKeyPath:key];
             }
         }
     }
  5. 返回一个对象的类型名称

  6.  - (NSString *)className
     {
         return [NSString stringWithUTF8String:object_getClassName(self)];
     }

以上是对NSObject的一个扩展,使用了Obj-C的Category特性

以下是与数据存储相关,定义为DbHelper,对sqlite进行操作

  1. 把id类型的数据对象插入到数据

     -(void)insertObject:(id)object
     {
         NSString *tablename = [object className];
         NSMutableString *sql = [[NSMutableString alloc] init];
         NSArray *array = [object getPropertyList];
         [sql appendFormat:@"insert into %@ (",tablename] ;
         NSInteger i = 0;
         for (NSString *key in array) 
         {
             if (i>0) {
                  [sql appendString:@","];
             }
             [sql appendFormat:@"%@",key];
             i++;
         }
         [sql appendString:@") values ("];
         NSMutableArray *arrayValue = [NSMutableArray array];
         i=0;
         for (NSString *key in array)
         {
             SEL selector = NSSelectorFromString(key);
    
             #pragma clang diagnostic push
             #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
             id value = [object performSelector:selector];
             #pragma clang diagnostic pop
             if (value==nil) 
             {
                  value = @"";
             }
             [arrayValue addObject:value];
             if (i>0)
             {
                  [sql appendString:@","];
             }
             [sql appendString:@"?"];
             i++;
         }
         [sql appendString:@")"];
         [_db executeUpdate:sql withArgumentsInArray:arrayValue];
     }
  2. 把字典NSDictionary对象插入到数据库中
    在与服务器进行交互时候,我们一般采用Json进行数据通讯,从服务端获取Json字符,通过JSONKit框架,反序列化成NSDictionary对象,然后插入到数据库
    生成插入的sql语句

     -(NSString *)createInsertSqlByDictionary:(NSDictionary *)dict tablename:(NSString *)table
     {
    
         NSMutableString *sql = [[NSMutableString alloc] init];
         [sql appendFormat:@"insert into %@ (",table] ;
         NSInteger i = 0;
         for (NSString *key in dict.allKeys)
         {
             if (i>0) {
                 [sql appendString:@","];
             }
             [sql appendFormat:@"%@",key];
             i++;
         }
         [sql appendString:@") values ("];
         i = 0;
         for (NSString *key in dict.allKeys) 
         {
             if (i>0) 
             {
                  [sql appendString:@","];
             }
             [sql appendFormat:@":%@",key];
             i++;
         }
         [sql appendString:@")"];
         return sql;
     }

    把字典插入到数据库中

     -(void)insertBySql:(NSString *)sql dict:(NSDictionary *)dict
     {
         if (sql && sql.length>0) {
             [_dbQueue inDatabase:^(FMDatabase *db) {
             [db executeUpdate:sql withParameterDictionary:dict];
             }];
         }
     }

    取数据

  3. 从数据库取数据,封装成成字典,然后放入到数组中

     -(NSArray *)queryDbToDictionaryArray:(NSString *)tablename sql:(NSString *)sql
     {
         FMResultSet *resultSet=[_db executeQuery:sql];
         NSArray *columnArray = [self fMSetColumnArray:resultSet];
         NSMutableArray *syncArray = [[NSMutableArray alloc] init];
         NSString *columnName = nil;
         while ([resultSet next])
         {
             NSMutableDictionary *syncData = [[NSMutableDictionary alloc] init];
             for(int i =0;i<columnArray.count;i++)
             {
                 columnName = [columnArray objectAtIndex:i];
                 NSString *columnValue = [resultSet stringForColumn: columnName];
                 if (columnValue==nil)
                 {
                     columnValue=@"";
                 }
                 [syncData setObject:columnValue forKey:columnName];
             }
             [syncArray addObject:syncData];
         }
             if ([syncArray count]==0) 
             {
                  return nil;
             }
    
         return syncArray;
     }
  4. 从数据库中取数据,封装成对象,然后放入数组中

     -(NSArray *)queryDbToObjectArray:(Class )clazz sql:(NSString *)sql
     {
         FMResultSet *resultSet=[_db executeQuery:sql];
         NSArray *columnArray = [self fMSetColumnArray:resultSet];
         NSMutableArray *syncArray = [[NSMutableArray alloc] init];
         NSString *columnName = nil;
         while ([resultSet next])
         {
             NSObject *obj = [[clazz alloc] init];
    
             if (obj==nil)
             {
                  continue;
             }
    
             for(int i =0;i<columnArray.count;i++)
             {
                 columnName = [columnArray objectAtIndex:i];
                 NSString *columnValue = [resultSet stringForColumn: columnName];
                 SEL selector = NSSelectorFromString(columnName);
    
                 if ([obj respondsToSelector:selector])
                 {
                      [obj setValue:columnValue forKeyPath:columnName ];
                 }
             }
             [syncArray addObject:obj];
         }
         if ([syncArray count]==0) 
         {
             return nil;
         }
         return syncArray;
     }

    在数据量很大时候,考虑到性能问题,此方法需要酌情使用。

有了这几个东西,进行开发就省去了很多时间和代码量,直接动态生成表,从服务端接口取到数据,直接插入到数据库中保存,显示数据时,从数据库中取出数据放入到对象数组中。

0 0