這個工具集後續會持續維護,更但願的是初學者查看源碼後,對於Block的使用,Category構建,Delegate和DataSourse代碼分離的思想能有必定的瞭解。感謝Welkin Xie一直以來對於個人指導。??git
Github: https://github.com/cbangchen/... 感謝你們的支持??github
後續將集成到Cocoapods,如今請直接拖入工程便可。sql
得益於Lighter View Controllers對於ViewController的輕量化技巧講述,得益於BlocksKit萬物不Block的思想影響。數據庫
我以爲,若是一個Block管理一個視圖對象,全部的關於該對象的屬性設置,事件監聽和數據安裝都只在且只在該Block中出現,這樣的話對於每個視圖對象的管理將變得簡單,代碼風格也獲得必定的程度的統一,必定是不錯的事情。xcode
統一代碼風格,增長代碼可讀性。緩存
Block執掌大局,調用Block代替繁雜的代理方法和數據源方法,同時原代理和數據源方法依然正常使用,且原代理和數據源方法具備更高優先級。框架
對於AFNetworing的二次封裝,自定義緩存策略,嚴格把控緩存有效性。ide
對於FMDB的二次封裝,簡單易用,輕量級知足需求。工具
集成了Masonry框架,構建頁面更簡單 <- 神器不用說吧。源碼分析
配合InjectionForXcode插件,動態進行界面修改,更Runtime。
這個功能的使用是經過插件InjectionForXcode來實現的,直接點擊下載安裝,而後重啓XCode後Load bundles一下就能夠了。(不支持XCode8)
若是以上方法沒法安裝,請點擊此處進入Github下載安裝。
整個Kit最重要的部分是Category,而因爲幾乎全部的控件均繼承於UIView(UIControl也繼承於UIView),因此Category最重要的部分就是對於UIView的拓展了。
使用Setter和Getter來作:
// Getter - (CGFloat)originLeft { return self.frame.origin.x; }
// Setter - (void)setOriginLeft:(CGFloat)originLeft { if (!isnan(originLeft)) { self.frame = CGRectMake(originLeft, self.originUp, self.sizeWidth, self.sizeHeight); } }
首先聲明Block屬性(僅分析單擊手勢,其餘手勢同)。
typedef void(^CBGestureBlock)(id s); @property (nonatomic, copy) CBGestureBlock cb_singleTapBlock;
利用
objc_getAssociatedObject
方法來取得綁定的屬性值。對應綁定的字符爲屬性名。
- (CBGestureBlock)cb_singleTapBlock { return objc_getAssociatedObject(self, @"cb_singleTapBlock");; }
因爲Category中的屬性不會本身生成Setter和Getter,因此這裏使用
objc_setAssociatedObject
方法來綁定Block屬性值。達到相同的效果。而後利用searchSpedifiedGestureWithGestureClass:numOfTouch:
方法來尋找已添加的手勢,防止重複添加。
- (void)setCb_singleTapBlock:(CBGestureBlock)cb_singleTapBlock { objc_setAssociatedObject(self, @"cb_singleTapBlock", cb_singleTapBlock, OBJC_ASSOCIATION_COPY_NONATOMIC); if (cb_singleTapBlock) { UITapGestureRecognizer *singleTap = (UITapGestureRecognizer *)[self searchSpedifiedGestureWithGestureClass:[UITapGestureRecognizer class] numOfTouch:1]; if (!singleTap) { singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singTapAction:)]; } [self removeGestureRecognizer:singleTap]; [self addGestureRecognizer:singleTap]; } }
- (UIGestureRecognizer *)searchSpedifiedGestureWithGestureClass:(Class)gestureClass numOfTouch:(NSInteger)numOfTouch { __block UIGestureRecognizer *gestureObj; [self.gestureRecognizers enumerateObjectsUsingBlock:^(__kindof UIGestureRecognizer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if ([obj isKindOfClass:gestureClass]) { if (gestureClass == [UITapGestureRecognizer class]) { if (numOfTouch) { if ([obj numberOfTouches] == numOfTouch) { gestureObj = obj; } } }else { gestureObj = obj; } } }]; return gestureObj; }
設置了這樣的一個屬性,當它的值發生改變時,執行
CBEventMonitorBlock
,並將自身和改變過的字段傳過去,以便於判斷後執行操做。
@property (nonatomic, copy) NSString *cb_signal; typedef void(^CBEventMonitorBlock)(id object, id signal); @property (nonatomic, copy) CBEventMonitorBlock cb_eventMonitor;
- (void)setCb_signal:(NSString *)cb_signal { objc_setAssociatedObject(self, @"cb_signal", cb_signal, OBJC_ASSOCIATION_COPY_NONATOMIC); if (self.cb_eventMonitor) { self.cb_eventMonitor(self, cb_signal); } }
UIControl繼承於UIView並相比之多了直接添加事件,管理事件的功能,因此在這裏咱們添加的再也不是手勢Block而是事件Block,直接取代addTarget
後再執行事件過程,相似RAC的事件綁定。
cb_touchUpInsideBlock,其餘事件Block同。
typedef void(^CBActionBlock)(id sender); @property (nonatomic, copy) CBActionBlock cb_touchUpInsideBlock;
- (void)setCb_touchUpInsideBlock:(CBActionBlock)cb_touchUpInsideBlock { objc_setAssociatedObject(self, @"cb_touchUpInsideBlock", cb_touchUpInsideBlock, OBJC_ASSOCIATION_COPY_NONATOMIC); [self removeTarget:self action:@selector(touchUpInside:) forControlEvents:UIControlEventTouchUpInside]; if (cb_touchUpInsideBlock) { [self addTarget:self action:@selector(touchUpInside:) forControlEvents:UIControlEventTouchUpInside]; } }
- (void)valueChanged:(id)sender { if (self.cb_valueChangedBlock) { self.cb_valueChangedBlock(self); } }
根據代理方法和數據源,另建立了兩個文件CBTableViewDataSourse
和CBTableViewDelegate
來接收UITableView的代理方法和數據源方法。
static CBTableViewDataSourse *tableViewDataSourse; static CBTableViewDelegate *tableViewDelegate; tableViewDataSourse = [[CBTableViewDataSourse alloc] initWithCellIdentifier:cellIdentifier]; tableViewDelegate = [[CBTableViewDelegate alloc] init]; tableViewDataSourse.realDataSourse = delegate; tableViewDelegate.realDelegate = delegate; tableView.delegate = tableViewDelegate; tableView.dataSource = tableViewDataSourse;
值得注意的是,咱們在tableViewDelegate
和tableViewDataSourse
中依然設置了@property (nonatomic, weak, readwrite) id realDataSourse;
的變量來存儲咱們在方法外設置的原代理對象,以確保原代理和數據源方法依然正常使用且具備更高的優先級。
以
cellForRowAtIndexPath
方法爲例,其餘方法同。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell; if (_realDataSourse && [_realDataSourse respondsToSelector:@selector(tableView:cellForRowAtIndexPath:)]) { cell = [_realDataSourse cellForRowAtIndexPath:indexPath]; }else { if (_cb_tableViewCellConfigureBlock) { cell = [tableView dequeueReusableCellWithIdentifier:_cellIdentifier]; if (!cell) cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:_cellIdentifier]; if (_cb_tableViewCellConfigureBlock) { _cb_tableViewCellConfigureBlock(cell, indexPath); } }else { NSAssert(_cb_tableViewCellConfigureBlock, @"CellForRowAtIndexPathBlock can't be nil."); } } return cell; }
從以上方法咱們能夠看出,首先判斷的是原代理對象是否響應該代理方法或者數據源方法。
而若是該方法返回數據且原代理對象響應該方法,即僅執行原代理對象中的代理方法或者數據源方法,若是該方法返回數據但原代理對象不響應該方法,則執行Blcok。
若是該方法爲void方法,不返回數據,即同時執行原代理方法或者數據源方法和Block。
其餘的視圖對象拓展大抵與UITableView類似,請下載源碼查看。
此模塊以前已經開源,點擊進入Github查看,點擊進入博客查看分析。
這個模塊主要是對於FMDB的一個簡單封裝,支持Json數據的直接存儲,很是輕量,知足大部分的應用的需求。
- (instancetype)initDatabaseWithDBName:(NSString *)dbName tableName:(NSString *)tableName;
這個方法裏面首先組合數據庫存儲路徑,而後建立
FMDatabaseQueue
類型的線程block對象,判斷表格是否存在,若是不存在,執行建立操做。
- (instancetype)initDatabaseWithDBName:(NSString *)dbName tableName:(NSString *)tableName { self = [super init]; if (self) { NSAssert(dbName.length, @"String of the dbName can't be empty."); NSAssert(tableName.length, @"String of the tableName can't be empty."); NSString * path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]stringByAppendingPathComponent:dbName]; NSLog(@"%@", path); if (_dbQueue) { [_dbQueue close]; } _dbQueue = [FMDatabaseQueue databaseQueueWithPath:path]; __block BOOL tableExit = NO; [_dbQueue inDatabase:^(FMDatabase *db) { tableExit = [db tableExists:tableName]; }]; if (!tableExit) { NSString * sql = [NSString stringWithFormat:@"CREATE TABLE IF NOT EXISTS %@ (OBJECTID TEXT NOT NULL, DATA TEXT NOT NULL, SAVETIME TEXT NOT NULL, PRIMARY KEY(OBJECTID))", tableName]; __block BOOL crateTableSuccess = NO; [_dbQueue inDatabase:^(FMDatabase *db) { crateTableSuccess = [db executeUpdate:sql]; }]; if (!crateTableSuccess) { NSLog(@"Failed to crate the table named %@", tableName); } } } return self; }
- (void)saveObject:(id)object withKey:(NSString *)key intoTable:(NSString *)tableName;
存儲判斷數據是否能夠轉化爲JSON數據(NSString, NSNumber, NSArray, NSDictionary等),若是能夠即便用
NSJSONSerialization
類的方法將其轉化爲JSON數據,而後在FMDatabaseQueue
對象中執行存儲操做。
- (void)saveObject:(id)object withKey:(NSString *)key intoTable:(NSString *)tableName { NSAssert(object, @"Object which wanna be saved can't be nil."); NSAssert(key.length, @"String of the objectID can't be empty."); NSAssert(tableName.length, @"String of the tableName can't be empty."); NSError *error; NSData *data; if ([NSJSONSerialization isValidJSONObject:object]) { data = [NSJSONSerialization dataWithJSONObject:object options:NSJSONWritingPrettyPrinted error:&error]; } if (error) { NSLog(@"%@", error); return; } NSString * dataString = [[NSString alloc] initWithData:data encoding:(NSUTF8StringEncoding)]; NSDate * currentTime = [NSDate date]; NSString * sql = [NSString stringWithFormat:@"REPLACE INTO %@ (OBJECTID, DATA, SAVETIME) values (?, ?, ?)", tableName]; __block BOOL saveObjectSuccess = NO; [_dbQueue inDatabase:^(FMDatabase *db) { saveObjectSuccess = [db executeUpdate:sql, key, dataString, currentTime]; }]; if (!saveObjectSuccess) { NSLog(@"Failed to save the object with key : %@ from the table : %@", key,tableName); } }
獲取數據的過程與存儲數據類似,取出數據,將其轉化爲JSON數據,並返回。刪除數據則是根據數據的KEY來決定,類似不表。