iOS数据存储的五种方式(含FMDB,CoreData使用)

iOS数据存储通常有以下五种方式:
1、Plist 2、NSUserDefaults 3、NSkeyedArchiver 4、SQLite 5、CoreData

等不及想看项目的同学点这儿demo

1、Plist

可被序列化的通常有以下几种类型:
NSString(含NSMutableString),NSArray(含NSMutableArray), NSDictionary(含NSMutableDictionary),NSData(含 NSMutableData),NSNumber,NSDate

使用方法

  • 不在工程中新建plist文件

先创建一个plist的存储路径

NSString *docuPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *plistPath = [docuPath stringByAppendingPathComponent:@"hooyking.plist"];

以NSString保存示例

NSError *error = nil;
[@"张三" writeToFile:plistPath atomically:YES encoding:NSUTF8StringEncoding error:&error];

NSString数据取出

NSError *error = nil;
NSString *string = [NSString stringWithContentsOfFile:plistPath encoding:NSUTF8StringEncoding error:&error];
  • 在工程中新建一个plist文件


    plist.png

以NSArray保存示例

NSString *customerPlistPath = [[NSBundle mainBundle] pathForResource:@"HK" ofType:@"plist"];
NSArray *nameArray = @[@"first",@"last"];
[nameArray writeToFile:customerPlistPath atomically:YES];

NSArray数据取出

NSArray *customerPlistArray = [NSArray arrayWithContentsOfFile:customerPlistPath];
NSLog(@"项目中新建的plist文件保存的数据%@",customerPlistArray);

2、NSUserDefaults

NSUserDefaults支持的数据类型通常有以下几种:
NSNumber(CGFloat、NSInteger、int、float、double), NSString(含NSMutableString),NSData(含NSMutableData,NSArray(含NSMutableArray),NSDictionary(含NSMutableDictionary),BOOL

  • 基本数据类型
    NSUserDefaults存储的对象全是不可变的,即使存进去的是可变的,取出来也是不可变的,如NSMutableArray存进去,取出来就是NSArray了。

以NSString保存示例

NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
[ud setObject:self.textField.text forKey:@"userDefaultKey"];
[ud synchronize];

NSString数据取出

NSString *text = [ud objectForKey:userDefaultKeyStr];
NSLog(@"取出的数据%@",text);
  • 自定义类型
    使用NSUserDefaults保存自定义类型,自定义类型必须遵循NSCoding协议,下面以PersonModel示例。

PersonModel.h中遵循<NSCoding>

PersonModel.h.png

PersonModel.m中实现解归档

//归档
- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:self.name forKey:@"name"];
    [aCoder encodeInteger:self.age forKey:@"age"];
}

//解档
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    self = [super init];
    if (self) {
        self.name = [aDecoder decodeObjectForKey:@"name"];
        self.age = [aDecoder decodeIntegerForKey:@"age"];
    }
    return self;
}

保存数据

NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
PersonModel *model = [PersonModel new];
model.name = @"张哈哈";
model.age = 10;
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:model];
[ud setObject:data forKey:@"customerObjectKey"];
[ud synchronize];

取出数据

NSData *data = [ud objectForKey:@"customerObjectKey"];
PersonModel *model = [NSKeyedUnarchiver unarchiveObjectWithData:data];
NSLog(@"名字:%@--年龄:%zd",model.name,model.age);

3、NSKeyedArchiver

这里的后缀名用了.data,实际上用什么后缀名都无所谓的。

  • 单个普通数据的归解档

文件路径

NSString *docuPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"归档文件路径:%@",docuPath);
NSString *singleCommonFilePath = [docuPath stringByAppendingPathComponent:@"singleCommon.data"];

归档

NSString *singleString = @"单个普通数据的归档看看如何?";
BOOL singleSuccess = [NSKeyedArchiver archiveRootObject:singleString toFile:singleCommonFilePath];
if (singleSuccess) {
    NSLog(@"单个普通数据归档成功");
} else {
    NSLog(@"单个普通数据归档失败");
}

解档

NSString *singleString = [NSKeyedUnarchiver unarchiveObjectWithFile:singleCommonFilePath];
NSLog(@"单个普通数据的解档:%@",singleString);
  • 多个普通数据的归解档

文件路径

NSString *docuPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"归档文件路径:%@",docuPath);
NSString *multipleCommonFilePath = [docuPath stringByAppendingPathComponent:@"multipleCommon.data"];

归档

NSInteger age = 20;
NSString *name = @"张三";
NSArray *toies = @[@"柯尼赛格",@"百达斐丽",@"陆家嘴100套房"];
NSMutableData *mutableData = [[NSMutableData alloc] init];
NSKeyedArchiver *multipleKeyArchiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:mutableData];
[multipleKeyArchiver encodeInteger:age forKey:@"age"];
[multipleKeyArchiver encodeObject:name forKey:@"name"];
[multipleKeyArchiver encodeObject:toies forKey:@"toies"];
[multipleKeyArchiver finishEncoding];//完成归档
BOOL multipleSuccess = [mutableData writeToFile:multipleCommonFilePath atomically:YES];//写入文件
if (multipleSuccess) {
    NSLog(@"多个普通数据归档成功");
} else {
    NSLog(@"多个普通数据归档失败");
}

解档

NSMutableData *mutableData = [NSMutableData dataWithContentsOfFile:multipleCommonFilePath];
NSKeyedUnarchiver *keyedUnarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:mutableData];
NSInteger age = [keyedUnarchiver decodeIntegerForKey:@"age"];
NSString *name = [keyedUnarchiver decodeObjectForKey:@"name"];
NSArray *toies = [keyedUnarchiver decodeObjectForKey:@"toies"];
[keyedUnarchiver finishDecoding];
NSLog(@"多个普通数据的解档:年龄:%zd--名字:%@--玩具:%@,%@,%@",age,name,toies[0],toies[1],toies[2]);
  • 自定义对象的归解档

自定义对象的归档必须遵循NSCoding协议,当然自定义对象还可以包含自定义对象,被包含自定义对象同样需要遵循NSCoding协议,下面以CarModelEngineModel示例,CarModel包含EngineModel

CarModel.png

对于自定义对象,不论他是只保存这个对象还是对象的array,dictionary,set,只要此对象遵循了NSCoding协议,都可进行归档

文件路径

NSString *docuPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"归档文件路径:%@",docuPath);
NSString *customerModelFilePath = [docuPath stringByAppendingPathComponent:@"car.data"];

归档

CarModel *car = [[CarModel alloc] init];
car.price = 50000000;
car.brand = @"布加迪威龙";
car.wheelArr = @[@"左前轮",@"右前轮",@"左后轮",@"右后轮"];

EngineModel *engine = [[EngineModel alloc] init];
engine.model = @"无敌旋风";
engine.cylinderNumber = 100;
    
car.engine = engine;

BOOL success = [NSKeyedArchiver archiveRootObject:car toFile:customerModelFilePath];
if (success) {
    NSLog(@"自定义对象归档成功");
} else {
    NSLog(@"自定义对象归档失败");
}

解档

CarModel *car = [NSKeyedUnarchiver unarchiveObjectWithFile:customerModelFilePath];
NSLog(@"自定义对象的解档:车价格:%.2f--车标:%@--车轮子:%@,%@,%@,%@--发动机型号:%@--发动机气缸数:%zd",
car.price,car.brand,car.wheelArr[0],car.wheelArr[1],car.wheelArr[2],car.wheelArr[3],car.engine.model,car.engine.cylinderNumber);

4.SQLite

SQLite存储类型通常有以下几种:

NULL NULL值
REAL 浮点型(如CGFloat,float,double,不过都需要转成NSNumber存)
INTEGER 整型(如NSInteger,int,不过都需要转成NSNumber存)
TEXT 文本类(如NSString)
BLOB 二进制(如图片、文件NSData

注意这个插入的数据那些都是对象,text对应NSString,blob对应NSData,integer对应NSNumber

通常我们不直接使用系统的SQLite,而是用FMDB,它是对SQLite的封装,使用更加优美。

下面就详细说说FMDB的使用

数据库路径及初始化FMDatabase

NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"documentPath:%@",path);
NSString *dbpath = [path stringByAppendingPathComponent:@"hooyking.db"];
FMDatabase *db = [FMDatabase databaseWithPath:dbpath];
self.myDb = db;

对表操作后记得一定要调用close方法

  • 创建表
- (void)createTable {
    if ([_myDb open]) {
        //这儿创建了一个列有 name|sex|age|nickname|phoneNum|nativePlace|photo 的名字为personTable的表
        BOOL result = [_myDb executeUpdate:@"create table if not exists personTable (name text, sex integer, age integer, nickname text, phoneNum text, nativePlace text, photo blob)"];
        if (result) {
            NSLog(@"创建表成功");
        }
        else {
            NSLog(@"创建表失败");
        }
        [_myDb close];
    }
}
  • 插入数据
- (void)insertData {
    if ([_myDb open]) {
        BOOL result = [_myDb executeUpdate:@"insert into personTable (name, sex, age, nickname, phoneNum, nativePlace, photo) values (?,?,?,?,?,?,?)",@"JDX",[NSNumber numberWithInteger:1],[NSNumber numberWithInteger:18], @"hooyking", [NSNumber numberWithInteger:13888888888],@"sichuan",[NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"picture" ofType:@"jpg"]]];
        if (result) {
            NSLog(@"添加数据成功");
        }
        else {
            NSLog(@"添加数据失败");
        }
        [_myDb close];
    }
}
  • 删除数据
- (void)deleteData {
    //删除这个表里nickname为hooyking的所有数据,列没有删除,依然存在
    if ([_myDb open]) {
        BOOL result = [_myDb executeUpdate:@"delete from personTable where nickname = ?",@"hooyking"];
        if (result) {
            NSLog(@"删除数据成功");
        }
        else {
            NSLog(@"删除数据失败");
        }
        [_myDb close];
    }
}
  • 修改数据
- (void)updateData {
    if ([_myDb open]) {
        BOOL result = [_myDb executeUpdate:@"update personTable set age = ? where nickname = ?",[NSNumber numberWithInteger:25],@"hooyking"];
        if (result) {
            NSLog(@"修改数据成功");
        }
        else {
            NSLog(@"修改数据失败");
        }
        [_myDb close];
    }
}
  • 查询数据
- (void)selectData {
    if ([_myDb open]) {
        //查询多条数据
        FMResultSet *res = [_myDb executeQuery:@"select name, age from personTable"];
        while ([res next]) {
            NSString *name = [res stringForColumn:@"name"];
            NSInteger age = [res intForColumn:@"age"];
            NSLog(@"姓名:%@----年龄:%ld",name,age);
        }
        
        //查询一条数据
        NSLog(@"年龄为25的人:%@",[_myDb stringForQuery:@"select name from personTable where age = ?",@25]);
        
        [_myDb close];
    }
}
  • 建多张表,插入,查询多个数据等
- (void)moreOperate {
    if ([_myDb open]) {
        //创建表
        NSString *createSql = @"create table if not exists studentsTable1 (id integer, name text, sex integer);"
                               "create table if not exists studentsTable2 (id integer, name text, sex integer);"
                               "create table if not exists studentsTable3 (id integer, name text, sex integer);";
        BOOL createResult = [_myDb executeStatements:createSql];
        if (createResult) {
            NSLog(@"创建多张表成功");
        }
        else {
            NSLog(@"创建多张表失败");
        }
        //*********************************插入多条数据(这里涉及到线程安全)**************************************
        
        //**********************方法一*********************
        NSString *insertSql = @"insert into studentsTable1 (id, name, sex) values ('100', '张三', '1');"
                               "insert into studentsTable2 (id, name, sex) values ('200', '李四', '1');"
                               "insert into studentsTable3 (id, name, sex) values ('300', '如花', '0');";
        BOOL insertResult = [_myDb executeStatements:insertSql];
        if (insertResult) {
            NSLog(@"插入数据成功");
        }
        else {
            NSLog(@"插入数据失败");
        }
        
        //*********************方法二**********************
        //看这个方法:moreQueue
        
        
        //查询数据
        NSString *selectSql = @"select * from studentsTable1;"
                               "select * from studentsTable2;"
                               "select * from studentsTable3;";
        BOOL selectResult = [_myDb executeStatements:selectSql withResultBlock:^int(NSDictionary *dictionary) {
            NSLog(@"moreOperate查询到的结果:%@", [[dictionary allValues] componentsJoinedByString:@","]);
            return 0;
        }];
        if (selectResult) {
            NSLog(@"查询成功");
        }
        else {
            NSLog(@"查询失败");
        }
        [_myDb close];
    }
}
  • 多个数据插入线程安全
- (void)moreQueue {
    //方法二里面线程安全又有两种方式,ok继续看
    if ([_myDb open]) {
        NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
        NSString *dbpath = [path stringByAppendingPathComponent:@"hooyking.db"];
        FMDatabaseQueue *dbQueue = [FMDatabaseQueue databaseQueueWithPath:dbpath];
        //第一种(一般就用这种就能保证线程安全了,那条数据有错,那么有错那条就不会插入进去,例如id改为i,自己看效果)
        [dbQueue inDatabase:^(FMDatabase *db) {
            [db executeUpdate:@"insert into studentsTable1 (id, name, sex) values (?,?,?)",[NSNumber numberWithInteger:500], @"王五", [NSNumber numberWithInteger:1]];
            [db executeUpdate:@"insert into studentsTable2 (id, name, sex) values (?,?,?)",[NSNumber numberWithInteger:600], @"陆六", [NSNumber numberWithInteger:1]];
            [db executeUpdate:@"insert into studentsTable3 (id, name, sex) values (?,?,?)",[NSNumber numberWithInteger:700], @"史真香", [NSNumber numberWithInteger:0]];
        }];
        //第二种事务(当插入数据有错时,直接取消将插入的数据,可以改下列试试,例如id改为i,自己看效果)
//        [dbQueue inTransaction:^(FMDatabase *db, BOOL *rollback) {
//            BOOL res1 = [db executeUpdate:@"insert into studentsTable1 (id, name, sex) values (?,?,?)",[NSNumber numberWithInteger:500], @"王五", [NSNumber numberWithInteger:1]];
//            BOOL res2 = [db executeUpdate:@"insert into studentsTable2 (id, name, sex) values (?,?,?)",[NSNumber numberWithInteger:600], @"陆六", [NSNumber numberWithInteger:1]];
//            BOOL res3 = [db executeUpdate:@"insert into studentsTable3 (id, name, sex) values (?,?,?)",[NSNumber numberWithInteger:700], @"史真香", [NSNumber numberWithInteger:0]];
//            if (!res1 || !res2 || !res3) { //我这样写就是三条任何一条有错,这三条就一条都不插入,这个判断条件若是不写,那就会默认哪一条有错,那一条就不会加入,但是其他的正确的会插入
//                *rollback = YES;
//            }
//            [db executeUpdate:@"insert into studentsTable3 (id, name, sex) values (?,?,?)",[NSNumber numberWithInteger:800], @"你真溜", [NSNumber numberWithInteger:0]];
//        }];
    
        //查询数据
        NSString *selectSql = @"select * from studentsTable1;"
                               "select * from studentsTable2;"
                               "select * from studentsTable3;";
        BOOL selectResult = [_myDb executeStatements:selectSql withResultBlock:^int(NSDictionary *dictionary) {
            NSLog(@"moreQueue查询到的结果:%@", [[dictionary allValues] componentsJoinedByString:@","]);
            return 0;
        }];
        if (selectResult) {
            NSLog(@"查询成功");
        }
        else {
            NSLog(@"查询失败");
        }
        
        [_myDb close];
    }
}
  • 对列的操作

  • 添加列

- (void)addColumn {
    if ([_myDb open]) {
        //这儿添加了添加名字为temp类型为text的列
        BOOL addColumnRes = [_myDb executeUpdate:@"alter table studentsTable1 add temp text"];
        if (addColumnRes) {
            NSLog(@"添加列成功");
        }
        else {
            NSLog(@"添加列失败");
        }
        
        [_myDb close];
    }
}
  • 删除列
    SQLite不支持alter对列进行修改与删除的方法来的,所以要删除列的替代方式为新建一个没有你要删除的列的表
    第一步:create table testTable(id integer, name text, sex integer);创建一个新表testTable,这个表没有列temp了;
    第二步:insert into testTable select id, name, sex from studentsTable1;这儿完成了将表studentsTable1列id name sex中的全部数据插入到了表testTable中;
    第三步:drop table if exists studentsTable1;删除原来的表studentsTable1;
    第四步:alter table testTable rename to studentsTable1;将testTable重命名为studentsTable1,若是你要修改列名,方式和删除一样,可自己操作一下。

  • 删除表

- (void)dropTable {
    //销毁这张表(即这个表删除后就不存在了)
    if ([_myDb open]) {
        BOOL result = [_myDb executeUpdate:@"drop table if exists personTable"];
        if (result) {
            NSLog(@"销毁表成功");
        }
        else {
            NSLog(@"销毁表失败");
        }
        [_myDb close];
    }
}

5、CoreData

新建项目时可勾选Use Core Data,勾选之后创建的项目就自带CoreData了


勾选Use Core Data.png

若是不勾选,新建的项目就没有CoreData文件,想使用CoreData就要通过New File选择Data Model

不勾选Use Core Data.png

建好之后为如下图所示


CoreData文件刚建好.png

点击下方Add Entity添加实体

这儿我们添加Teacher与Student实体

Teacher实体添加好后.png
Teacher实体设置关联关系后.png
Student实体设置关联关系后.png

右侧Relationship设置

Properties
transient:设置当前属性是否只存在于内存,不被持久化到本地,如果设置为YES,关联关系属性就不参与持久化操作。transient设置为YES一般存储一些在内存中缓存的数据,如存储临时数据,当app杀死后数据不会存在本地,该选项默认NO。
optional: 设置向MOC保存数据时,这个属性是否必须有值。设置为NO时,MOC进行操作时,若是无值,会失败并返回一个error,该选项默认YES。

Delete Rule
设置关联属性的删除规则。一共四个值:这儿一般选后三个值,当选No Action时,Xcode也会报警告,让设置一个关联关系。
No Action:当前实体删除后对关联实体无任何操作,也不会将关联对象的关联属性指向nil,删除后使用关联对象的关联属性,可能会导致其他问题。
Nullify:删除后会将关联实体的关联属性指向nil,delete Rule默认值。
Cascade:删除当前对象后,会将与之关联的实体也一并删除。
Deny:在删除当前实体时,如果当前对象还指向其他关联对象,则当前实体不能被删除。

Type
设置当前实体与关联实体的关联关系
To One:当前实体只可持有1个关联实体(默认值)
To Many:当前实体只可持有多个关联实体

Count只有选择To Many时才有
设置最小值与最大值,默认值不设置
Minimum:最小值
Maximum:最大值

Advanced
设置索引
indexed: 设置当前属性是否是索引。添加索引后可以提升检索速度,但是对于删除操作,删除索引后其他地方还需要做出相应的变化,所以速度会比较慢,默认值NO。

Spotlight
Store in External Record File: 当存储二进制文件时,如果遇到比较大的文件,是否存储在存储区之外。如果选择YES,存储文件大小超过1MB的文件,都会存储在存储区之外。否则大型文件存储在存储区内,会造成SQLite进行表操作时,效率受到影响,默认值NO。

设置好关系后创建模型文件
Xcode->Editor->Create NSManageredObject Subclass 勾选两个实体,创建好后会报错,在如下图里找到文件,移除后就没了

build Phases删除四个文件.png

你可以在student属性里看到如此这般
@property (nullable, nonatomic, retain) NSSet<Teacher *> *teachers;
你可以在Teacher属性里看到如此这般
@property (nullable, nonatomic, retain) NSSet<Student *> *students;

  • 创建CoreData数据库
- (void)createCoreDataLite {
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"CoreData" withExtension:@"momd"];//CoreData为你自定义的CoreData文件名,momd为固定的类型
    NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    NSPersistentStoreCoordinator *store = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
    NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"coreData.sqlite"];
    NSLog(@"数据库地址%@",path);
    NSURL *sqlURL = [NSURL fileURLWithPath:path];
    NSError *error = nil;
    [store addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:sqlURL options:nil error:&error];
    if (error) {
        NSLog(@"错误%@",error);
    } else {
        NSLog(@"创建数据库成功,如果已添加此数据库,就算再次调用也不用重复添加,直接使用以前的");
    }
    NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    context.persistentStoreCoordinator = store;
    self.context = context;
}
  • 添加数据
- (void)insertData {
    Student *student = [NSEntityDescription insertNewObjectForEntityForName:@"Student" inManagedObjectContext:self.context];
    student.name = [NSString stringWithFormat:@"Student%d",arc4random()%100];
    student.age = arc4random()%20;
    student.sex = arc4random()%2 == 0 ?  @"女" : @"男";

    Teacher *teacher = [NSEntityDescription insertNewObjectForEntityForName:@"Teacher" inManagedObjectContext:self.context];
    teacher.name = [NSString stringWithFormat:@"Teacher%d",arc4random()%100];
    teacher.age = arc4random()%60;
    teacher.students = [NSSet setWithArray:@[student]];
    
    NSError *error = nil;
    if ([self.context save:&error]) {
        NSLog(@"插入成功");
    } else {
        NSLog(@"插入失败");
    }
}
  • 删除数据
- (void)deleteData {
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Teacher"];
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age < 50"];
    request.predicate = predicate;
    NSArray *deleteArray = [self.context executeFetchRequest:request error:nil];
    for (Teacher *model in deleteArray) {
        NSLog(@"老师的学生%@",model.students);
        [self.context deleteObject:model];
    }
    NSError *error = nil;
    if ([self.context save:&error]) {
        NSLog(@"删除成功");
    } else {
        NSLog(@"删除失败%@",error);
    }
}
  • 更新数据
- (void)updateData {
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Teacher"];
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age < 45"];
    request.predicate = predicate;
    NSArray *updateArray = [self.context executeFetchRequest:request error:nil];
    for (Teacher *model in updateArray) {
        model.name = @"张三";
    }
    NSError *error = nil;
    if ([self.context save:&error]) {
        NSLog(@"更新成功");
    } else {
        NSLog(@"更新失败");
    }
}
  • 查询数据
- (void)findData {
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Teacher"];
    //此处可写查询条件(更多条件设定可浏览器自行搜索)
//    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age < 50"];
//    request.predicate = predicate;
    //此处可写排序条件(这儿按年龄排序)
//    NSSortDescriptor *ageSort = [NSSortDescriptor sortDescriptorWithKey:@"age"ascending:YES];
//    request.sortDescriptors = @[ageSort];
    NSArray *resultArray = [self.context executeFetchRequest:request error:nil];
    self.dataMArray = [resultArray mutableCopy];
    [self.tableView reloadData];
}

推荐阅读更多精彩内容