CBBlockKit:我只想用Block寫東西

CBBlcokKit

前言

這個工具集後續會持續維護,更但願的是初學者查看源碼後,對於Block的使用Category構建Delegate和DataSourse代碼分離的思想能有必定的瞭解。感謝Welkin Xie一直以來對於個人指導。??git

項目地址

Github: https://github.com/cbangchen/... 感謝你們的支持??github

安裝方法

後續將集成到Cocoapods,如今請直接拖入工程便可。sql

爲何要寫這個工具集

得益於Lighter View Controllers對於ViewController的輕量化技巧講述,得益於BlocksKit萬物不Block的思想影響。數據庫

我以爲,若是一個Block管理一個視圖對象,全部的關於該對象的屬性設置,事件監聽和數據安裝都只在且只在該Block中出現,這樣的話對於每個視圖對象的管理將變得簡單,代碼風格也獲得必定的程度的統一,必定是不錯的事情。xcode

這個Kit的全部功能

  • 統一代碼風格,增長代碼可讀性。緩存

  • Block執掌大局,調用Block代替繁雜的代理方法和數據源方法,同時原代理和數據源方法依然正常使用,且原代理和數據源方法具備更高優先級。框架

  • 對於AFNetworing的二次封裝,自定義緩存策略,嚴格把控緩存有效性。ide

  • 對於FMDB的二次封裝,簡單易用,輕量級知足需求。工具

  • 集成了Masonry框架,構建頁面更簡單 <- 神器不用說吧。源碼分析

  • 配合InjectionForXcode插件,動態進行界面修改,更Runtime。

關於Live Reload的幾點說明

這個功能的使用是經過插件InjectionForXcode來實現的,直接點擊下載安裝,而後重啓XCode後Load bundles一下就能夠了。(不支持XCode8)
若是以上方法沒法安裝,請點擊此處進入Github下載安裝。

本工具集Category的源碼分析

UIView

整個Kit最重要的部分是Category,而因爲幾乎全部的控件均繼承於UIViewUIControl也繼承於UIView),因此Category最重要的部分就是對於UIView的拓展了。

1.首先是對於座標運算的拓展。

使用SetterGetter來作:

// 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中的屬性不會本身生成SetterGetter,因此這裏使用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的分離

根據代理方法和數據源,另建立了兩個文件CBTableViewDataSourseCBTableViewDelegate來接收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;

值得注意的是,咱們在tableViewDelegatetableViewDataSourse中依然設置了@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支持一下,謝謝。

相關文章
相關標籤/搜索