這篇博文基於上一篇iOS_CNBlog項目開發 (基於博客園api開發)所寫. 過了剛好兩個星期, 這次基於上一次的1.0版本, 完善了新的功能, 也修複了之前的一些bug, 算是完成1.1版本吧, 一次進步一下點總是好的, 貼上github:)地址, 喜歡的可以玩弄玩弄 https://githu ...
這篇博文基於上一篇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. 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;View Code
使用類方法:
+ (instancetype)sm_toolWithURLString:(NSString *)urlString nodeName:(NSString *)nodeName completeHandler:(void (^)(NSArray *contentArray, NSError *error))completerHandler;
傳入url和xml的大節點名, 然後就會自動解析大節點下各節點, 內部發送非同步網路請求, 然後封裝了block回調方法, 返回內容可以直接在block內部使用, contentArray就是返回的結果. 這是一個小工具, 基本能夠實現功能吧. 喜歡的可以把玩一下 github:)