CBBlockKit:源码分析
来源:互联网 发布:js文件为什么不能编辑 编辑:程序博客网 时间:2024/06/14 08:26
前言
这个工具集后续会持续维护,更希望的是初学者查看源码后,对于Block的使用,Category构建,Delegate和DataSourse代码分离的思想能有一定的了解。感谢Welkin Xie一直以来对于我的指导。����
项目地址
Github: https://github.com/cbangchen/CBBlockKit 感谢大家的支持����
安装方法
后续将集成到Cocoapods,现在请直接拖入工程即可。
为什么要写这个工具集
得益于Lighter View Controllers对于ViewController的轻量化技巧讲述,得益于BlocksKit万物不Block的思想影响。
我觉得,如果一个Block管理一个视图对象,所有的关于该对象的属性设置,事件监听和数据安装都只在且只在该Block中出现,这样的话对于每一个视图对象的管理将变得简单,代码风格也得到一定的程度的统一,一定是不错的事情。
这个Kit的所有功能
统一代码风格,增加代码可读性。
Block执掌大局,调用Block代替繁杂的代理方法和数据源方法,同时原代理和数据源方法依然正常使用,且原代理和数据源方法具有更高优先级。
对于AFNetworing的二次封装,自定义缓存策略,严格把控缓存有效性。
对于FMDB的二次封装,简单易用,轻量级满足需求。
集成了Masonry框架,构建页面更简单 <- 神器不用说吧。
配合InjectionForXcode插件,动态进行界面修改,更Runtime。
关于Live Reload的几点说明
这个功能的使用是通过插件InjectionForXcode来实现的,直接点击下载安装,然后重启XCode后Load bundles一下就可以了。(不支持XCode8)
如果以上方法无法安装,请点击此处进入Github下载安装。
本工具集Category的源码分析
UIView
整个Kit最重要的部分是Category,而由于几乎所有的控件均继承于UIView(UIControl也继承于UIView),所以Category最重要的部分就是对于UIView的拓展了。
1.首先是对于坐标运算的拓展。
使用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); }}
2.四大手势(单击,双击,长按,拖拽)的添加。
首先声明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;}
3.信号设置和信号发送方法。
设置了这样的一个属性,当它的值发生改变时,执行
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
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); }}
UITableview
Delegate和DataSourse的分离
根据代理方法和数据源,另创建了两个文件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。
代码例子
其他Category
其他的视图对象拓展大抵与UITableView相似,请下载源码查看。
本工具集Network的源码分析
此模块之前已经开源,点击进入Github查看,点击进入博客查看分析。
本工具集Store的源码分析
这个模块主要是对于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来决定,相似不表。
总结
更详细的代码请大家下载源码查看,谢谢大家的关注,如果可以,请点个star支持一下,谢谢。
- CBBlockKit:源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析:SparseArray分析
- 源码- Spark Broadcast源码分析
- Android源码/框架源码分析
- 【Android应用源码分析】HandlerThread 源码分析
- 【Android应用源码分析】IntentService 源码分析
- java源码分析01-Object源码分析
- VC++源码分析 - 中国象棋源码分析
- [Java源码分析]ArrayList源码分析
- [java源码分析]LinkedList源码分析
- 腾讯笔试题:二进制地址为011011110000,大小为(4)10和(16)10块的伙伴地址
- 通过本地yum源安装软件报错[Errno 14] PYCURL ERROR 56 - "Failure when receiving data from the peer"
- Android 如果禁止statusbar下拉
- C++虚函数表一点理解
- UIWebView Objective C 和JavaScript 那点事
- CBBlockKit:源码分析
- Codeforces Aim Tech Round 3 (Div.2 )C.Letters Cyclic Shift 【贪心】水题
- bootstrap中,navbar 宽度等同浏览器窗口问题。
- Android动画的种类
- AIM Tech Round 3 (Div. 2) B. Checkpoints
- Spring AOP的xml配置和注解配置
- MYSQL性能调优: 对聚簇索引和非聚簇索引的认识
- 基于Jquery的文本提示控件 poshytip
- 【ES】ElasticSearch 安装介绍 <一>