你的位置:首页 > 操作系统

[操作系统]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();    }  }}

View Code

如果一开始创建项目的时候错过了勾选怎么办? 没关系, 看方法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;}

View Code

 

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"]);  }

View Code

有兴趣的, (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];  }

View Code

 

3. SM

上篇文章有说过这个也是我自行开发的工具类, 使用sax解析

.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;

View Code

使用类方法:

+ (instancetype)sm_toolWithURLString:(NSString *)urlString nodeName:(NSString *)nodeName completeHandler:(void (^)(NSArray *contentArray, NSError *error))completerHandler;

传入url和xm的大节点名, 然后就会自动解析大节点下各节点, 内部发送异步网络请求, 然后封装了block回调方法, 返回内容可以直接在block内部使用, contentArray就是返回的结果. 这是一个小工具, 基本能够实现功能吧. 喜欢的可以把玩一下 github:)