这个工具集后续会持续维护,更但愿的是初学者查看源码后,对于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来决定,类似不表。