iOS_CNBlog项目开发 (基于博客园api开发) 下篇
这篇博文基于上一篇iOS_CNBlog项目开发 (基于博客园api开发)所写.
过了刚好两个星期, 这次基于上一次的1.0版本, 完善了新的功能, 也修复了之前的一些bug, 算是完成1.1版本吧, 一次进步一下点总是好的, 贴上github:)地址, 喜欢的可以玩弄玩弄 https://github.com/samAroundGitHub/CNBlog .
然后也贴上这次主要加入的新功能gif吧.
这次主要是修复了一个bug, 新加入了一个博客收藏功能, 一个新闻关注功能, 以及头像可以更换了(然而并没有什么特别的)...
虽然没有什么特别但是也介绍一下吧.
1. 收藏功能实现
存储方式
这次本地存储用的是CoreData, 然后使用过后发现, 相比于sqlite, coredata存储方式的操作感觉更面向对象一点, 然后不用会sql语句也能快速上手吧
CoreData使用方法:
a. coredata的创建
方法1. 一开始新建project的时候直接勾选, 这样, Xcode就会自动在AppDelegate下面生成的代码, 其实就是一些操作coredata需要用到的对象初始化, 而且Xcode还会自动生成coredata文件 .xcdatamodeld , 然后你就可以像使用sql图形界面一样操作coredata, 其中entity对应sql中的table, attritube对应table中的键值, 然后可以添加关系, 跟使用sql差不多.
#pragma mark - Core Data stack @synthesize managedObjectContext = _managedObjectContext; @synthesize managedObjectModel = _managedObjectModel; @synthesize persistentStoreCoordinator = _persistentStoreCoordinator; - (NSURL *)applicationDocumentsDirectory { // The directory the application uses to store the Core Data store file. This code uses a directory named "com.easyToCode.CoreDataTest" in the application's documents directory. return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; } - (NSManagedObjectModel *)managedObjectModel { // The managed object model for the application. It is a fatal error for the application not to be able to find and load its model. if (_managedObjectModel != nil) { return _managedObjectModel; } NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"CoreDataTest" withExtension:@"momd"]; _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; return _managedObjectModel; } - (NSPersistentStoreCoordinator *)persistentStoreCoordinator { // The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it. if (_persistentStoreCoordinator != nil) { return _persistentStoreCoordinator; } // Create the coordinator and store _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]]; NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CoreDataTest.sqlite"]; NSError *error = nil; NSString *failureReason = @"There was an error creating or loading the application's saved data."; if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) { // Report any error we got. NSMutableDictionary *dict = [NSMutableDictionary dictionary]; dict[NSLocalizedDescriptionKey] = @"Failed to initialize the application's saved data"; dict[NSLocalizedFailureReasonErrorKey] = failureReason; dict[NSUnderlyingErrorKey] = error; error = [NSError errorWithDomain:@"YOUR_ERROR_DOMAIN" code:9999 userInfo:dict]; // Replace this with code to handle the error appropriately. // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } return _persistentStoreCoordinator; } - (NSManagedObjectContext *)managedObjectContext { // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) if (_managedObjectContext != nil) { return _managedObjectContext; } NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; if (!coordinator) { return nil; } _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; [_managedObjectContext setPersistentStoreCoordinator:coordinator]; return _managedObjectContext; } #pragma mark - Core Data Saving support - (void)saveContext { NSManagedObjectContext *managedObjectContext = self.managedObjectContext; if (managedObjectContext != nil) { NSError *error = nil; if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) { // Replace this implementation with code to handle the error appropriately. // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } } }
如果一开始创建项目的时候错过了勾选怎么办? 没关系, 看方法2
方法2. 一开始创建项目的时候不清楚要用什么存储方法所以没有勾选use core data, 没关系, 我们还是可以直接创建coredata文件, 然后添加好自己需要存储的数据表, 像下面一样Create NSManagedObject Subclass, 然后Xcode就会自动生成一个继承至NSManagedObject的类和coredata表对应, 这样关联起来可以使用了.
b. coredata的对象准备
使用coredata需要准备最基本的3个对象
// CoreData实体 @property (nonatomic, strong) NSManagedObjectModel *sm_model; // 操作实体 @property (nonatomic, strong) NSManagedObjectContext *sm_context; // 存储策略 @property (nonatomic, strong) NSPersistentStoreCoordinator *sm_coordinator;
这三个对象有什么用?
NSManagedObjectModel就好比CoreData对象, 里面包含着 .xcdatamodeld下所有entities
NSManagedObjectContext就是一个操作CoreData的对象, 你保存数据到哪, 它都管着
NSPersistentStoreCoordinator就是CoreData储存策略, 它关联着模型和数据库持久化
三个对象怎么创建?
// coradata实体 - (NSManagedObjectModel *)sm_model { if (!_sm_model) { // nil表示从mainBundle加载 _sm_model = [NSManagedObjectModel mergedModelFromBundles:nil]; } return _sm_model; } // 存储策略 - (NSPersistentStoreCoordinator *)sm_coordinator { if (!_sm_coordinator) { // 通过模型和数据库持久化 _sm_coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.sm_model]; // 持久化到coredata, 默认路径为 /documents/coredata.db NSString *document = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; document = [document stringByAppendingPathComponent:@"coredata.db"]; NSURL *url = [NSURL fileURLWithPath:document]; // 错误记录 NSError *error; NSString *failureReason = @"There was an error creating or loading the application's saved data."; if (![_sm_coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:nil error:&error]) { // Report any error we got. NSMutableDictionary *dict = [NSMutableDictionary dictionary]; dict[NSLocalizedDescriptionKey] = @"Failed to initialize the application's saved data"; dict[NSLocalizedFailureReasonErrorKey] = failureReason; dict[NSUnderlyingErrorKey] = error; error = [NSError errorWithDomain:@"YOUR_ERROR_DOMAIN" code:9999 userInfo:dict]; // Replace this with code to handle the error appropriately. // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } } return _sm_coordinator; } // 操作实体 - (NSManagedObjectContext *)sm_context { if (!_sm_context) { _sm_context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; _sm_context.persistentStoreCoordinator = self.sm_coordinator; } return _sm_context; }
c. 操作coredata
其实操作coredata跟操作sql很像, 也是增删改查, 只是操作coredata用对象加一些方法, 操作sql就是写sql语句
// 增 删 改 查 //////////////////////////////////////////////////////////////////////////// // 关联实体对象和实体上下文 // entity对应Coredata的entity // self.m_context对应coredata操作对象NSManagedObjectContext // 用kvc对关联的对象赋值 NSManagedObject *obj = [NSEntityDescription insertNewObjectForEntityForName:entity inManagedObjectContext:self.sm_context]; // 绑定数据 for (int i = 0; i < MIN(names.count, values.count); i++) { [obj setValue:values[i] forKey:names[i]]; } // 保存上下文关联对象 [self.sm_context save:nil]; //////////////////////////////////////////////////////////////////////////// // 检索对象 NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entity]; // 设置检索条件 request.predicate = [NSPredicate predicateWithFormat:predicate]; // 删除操作 for (NSManagedObject *obj in [self.sm_context executeFetchRequest:request error:nil]) { [self.sm_context deleteObject:obj]; } // 保存上下文关联对象 [self.sm_context save:nil]; //////////////////////////////////////////////////////////////////////////// // 检索对象 NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entity]; // 设置检索条件 request.predicate = [NSPredicate predicateWithFormat:predicate]; // 更新操作 for (NSManagedObject *obj in [self.sm_context executeFetchRequest:request error:nil]) { [obj setValue:value forKey:name]; } // 保存上下文关联对象 [self.sm_context save:nil]; //////////////////////////////////////////////////////////////////////////// // 检索对象 NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entity]; // 设置检索条件 request.predicate = [NSPredicate predicateWithFormat:predicate]; // NSLog(@"%@", request.predicate); // 查找操作 return [self.sm_context executeFetchRequest:request error:nil]; ////////////////////////////////////////////////////////////////////////////
然后为了进一步面向对象, 我也写了一个工具类 SMCoreDataTool github:), 一个轻量级的工具, 能够满足部分开发要求, 简化开发
其.h文件如下
@interface SMCoreDataTool : NSObject /** * mainBundle下所有entity */ @property (nonatomic, strong, readonly) NSArray *sm_entitys; /** * 单例 */ + (instancetype)shareSMTool; /** * 增删改查操作 */ + (void)sm_toolAddDataWithEntity:(NSString *)entity attributeNames:(NSArray *)names attributeValues:(NSArray *)values; + (void)sm_toolDeleteDataWithEntity:(NSString *)entity andPredicate:(NSString *)predicate; + (void)sm_toolUpdateDataWithEntity:(NSString *)entity attributeName:(NSString *)name predicate:(NSString *)predicate andUpdateValue:(NSString *)value; + (NSArray *)sm_toolSearchDataWithEntity:(NSString *)entity andPredicate:(NSString *)predicate; /** * 运行时 增加数据操作 */ + (void)sm_toolAddDataWithEntity:(NSString *)entity attributeModel:(id)model; /** * 清除coredata */ + (void)sm_toolClearCoraDataWithEntiy:(NSString *)entity; @end
简单说明一下.
外部暴露类方法, 内部是用单例调用对象方法, 然后提供了增删改查4个方法, 其中增的方法还额外提供多一个选择, 可以直接传入model, 其内部运用了runtime机制会自行判断能插入的值.
比如coredata如→
model如→
那么增删改查操作:
// 添加方法1 [SMCoreDataTool sm_toolAddDataWithEntity:@"Entity" attributeNames:@[@"name", @"uri"] attributeValues:@[@"jack", @"www.codedata.com"]]; // 添加方法2 // 这里coredata没有age属性, 所以不会存入该数据 // 只有model与core data同时存在某属性, 该属性才会存储 SMModel *model = [[SMModel alloc] init]; model.name = @"中文 乱码 华盛顿了"; model.uri = @"www.aaa.ccc"; model.age = @"11"; [SMCoreDataTool sm_toolAddDataWithEntity:@"Entity" attributeModel:model]; // 删除操作 [SMCoreDataTool sm_toolDeleteDataWithEntity:@"Entity" andPredicate:@"name like 'jack'"]; // 修改操作 [SMCoreDataTool sm_toolUpdateDataWithEntity:@"Entity" attributeName:@"name" predicate:@"name == 'update'" andUpdateValue:@"hehe"]; // 查找操作 NSArray *arr = [SMCoreDataTool sm_toolSearchDataWithEntity:@"Entity" andPredicate:nil]; for (NSManagedObject *obj in arr) { NSLog(@"%@ - %@", [obj valueForKey:@"name"], [obj valueForKey:@"uri"]); }
有兴趣的, github:)自行玩弄下.
2. 头像修改
头像修改功能就很简单啦, 基本是调用了苹果自带ImagePicker, 然后加入了兼容iOS8
核心代码如下:
if (iOS8) { UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"获取图片" message:nil preferredStyle:UIAlertControllerStyleActionSheet]; // 判断是否支持相机 if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { UIAlertAction *defaultActionTakePhoto = [UIAlertAction actionWithTitle:@"拍照" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init]; imagePicker.delegate = self; imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera; imagePicker.allowsEditing = YES; [self presentViewController:imagePicker animated:YES completion:nil]; }]; [alertController addAction:defaultActionTakePhoto]; } UIAlertAction *defaultActionFromPhotoGraf = [UIAlertAction actionWithTitle:@"从相册选择" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init]; imagePicker.delegate = self; imagePicker.allowsEditing = YES; imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; [self presentViewController:imagePicker animated:YES completion:nil]; }]; UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { }]; [alertController addAction:defaultActionFromPhotoGraf]; [alertController addAction:cancelAction]; [self presentViewController:alertController animated:YES completion:nil]; } else { UIActionSheet *sheet; // 判断是否支持相机 if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { sheet = [[UIActionSheet alloc] initWithTitle:@"获取图片" delegate:self cancelButtonTitle:@"取消" destructiveButtonTitle:nil otherButtonTitles:@"相机", @"从相册选择", nil]; } else { sheet = [[UIActionSheet alloc] initWithTitle:@"获取图片" delegate:self cancelButtonTitle:@"取消" destructiveButtonTitle:nil otherButtonTitles:@"从相册选择", nil]; } [sheet showInView:self.view]; }
3. SMXMLParserTool
上篇文章有说过这个也是我自行开发的工具类, 使用sax解析xml, 基于NSXMLParser开发, 之前没有独立放到github, 现在独立贴出来, 方便下载使用.
.h 文件
+ (instancetype)sm_toolWithURLString:(NSString *)urlString nodeName:(NSString *)nodeName completeHandler:(void (^)(NSArray *contentArray, NSError *error))completerHandler; - (instancetype)sm_initWithURLString:(NSString *)urlString nodeName:(NSString *)nodeName completeHandler:(void (^)(NSArray *contentArray, NSError *error))completerHandler; @property (nonatomic, readonly, strong) NSArray *contentArray; @property (nonatomic, strong) NSString *nodeName;
使用类方法:
+ (instancetype)sm_toolWithURLString:(NSString *)urlString nodeName:(NSString *)nodeName completeHandler:(void (^)(NSArray *contentArray, NSError *error))completerHandler;
传入url和xml的大节点名, 然后就会自动解析大节点下各节点, 内部发送异步网络请求, 然后封装了block回调方法, 返回内容可以直接在block内部使用, contentArray就是返回的结果. 这是一个小工具, 基本能够实现功能吧. 喜欢的可以把玩一下 github:)