CYLTabBarController源码分析(一)

已经好久没有分析源码了,上个周搞了些服务端的东西,接触到一些比较基础的服务端开发。这里简单的就看看CYLTabBarController。有一个长期计划那就是把服务的Spring框架源码仔细看看

整体印象

这个第三方到底是干什么用的?相对于系统的TabbarController和其他第三方TabbarController有什么优劣势。因为自己没有使用过,所以只有从文档介绍里面看看。

具体对比可以参照对比看这里

个人大致对比了此库和系统自带的UITabBarController。最大的改进大致两个方面:

  • 1.对设置tabBarIterm的样式比如高度、宽度、背景图提供了更为方便的途径。
  • 2.提供了所谓的加号TabBarItme的入口。

接下来就从源码的解读入手来

Tips

大致总结一下里面用到的一些常见的tip

FOUNDATION_EXTERN

如果没有写过第三方库,平时开发中很少用到这个宏定义。

  • 作用:和c中的extrn一样用于跨文件引用,修饰全局变量。比如你写了一个第三方库,外部需要引用到你定义的变量或者常量。那么就需要用到它。否则是不能获取到的。

  • 用法:声明和赋值分别要放在不同的地方。一般情况是在.h中声明,在.m文件中实现。如下:

    • .h文件
    FOUNDATION_EXTERN NSString *const CYLTabBarItemTitle;
    
    • .m文件中
    NSString *const CYLTabBarItemTitle = @"CYLTabBarItemTitle";
    
  • 相似宏定义:UIKIT_EXTERNextern

下面就具体细节深入讨论下:

先来它的定义:

#if defined(__cplusplus)
#define FOUNDATION_EXTERN extern "C"
#else
#define FOUNDATION_EXTERN extern
#endif

这里的__cplusplus是用于支持C++的。无意中查到这句话Microsoft-Specific Predefined Macros __cplusplus Defined for C++ programs only.extern "C"表示编译生成的内部符号名使用C约定。如果嫌概念晦涩,那么只需要只知道FOUNDATION_EXTERNextern区别就是FOUNDATION_EXTERN兼容c++代码。

再来看看UIKIT_EXTERN

#ifdef __cplusplus
#define UIKIT_EXTERN        extern "C" __attribute__((visibility ("default")))
#else
#define UIKIT_EXTERN            extern __attribute__((visibility ("default")))
#endif

对比上面的FOUNDATION_EXTERN可以发现,其实他们的作用是一样的。这里就不再多说了。

OC泛型<>(类型确定)

其实OC泛型这个特性在Xcode7中已经推出了。不知道大家在写OC代码的时候是否用到这个特性。

  • 作用:用于约束集合元素的类型。
  • 用法:直接看个例子吧@property (nonatomic, readwrite, copy) NSArray *viewControllers;这里的数组就元素确定是UIViewController类型,如果你在添加元素的时候,不是添加的UIViewController类型就会有警告。
  • 延伸:关于泛型用<>虽然指定了类型,但是仅仅是指定了这一种类型而已。比如上面的例子那样,添加的元素只能是UIViewController,而它的子类也会有警告,这个时候就需要用到__kindof关键字。
    • __kindof:作用就是扩展泛型,让泛型支持子类。用法比如:@property(nullable, nonatomic,copy) NSArray<__kindof UIViewController *> *viewControllers;这样写了之后即使在数组中添加了UIViewController子类也不会有警告。

代码块(代码组织简洁)

这个特性平时用得比较多,在Swift中就更为常见了。具体代码如下:

self.tabBar.frame = ({
        CGRect frame = self.tabBar.frame;
        CGFloat tabBarHeight = self.tabBarHeight;
        frame.size.height = tabBarHeight;
        frame.origin.y = self.view.frame.size.height - tabBarHeight;
        frame;
    });

通过用({})把一段代码包含起来,使得代码组织更为清晰。简单理解其实就是一个内部函数。

KVC(替换系统属性)

KVC平时用的地方也比较多。因为在OC代码中没有绝对的私有属性,所以才让KVC如此嚣张。常常用来做一些用一般方式达不到的效果。比如系统控价的某些私有属性。

源码中有这么一段。

/**
 *  利用 KVC 把系统的 tabBar 类型改为自定义类型。
 */
- (void)setUpTabBar {
    [self setValue:[[CYLTabBar alloc] init] forKey:@"tabBar"];
}

与设置属性相反的就是获取属性。[self valueForKey:@"tabBar"];

关于具体的KVC具体介绍可以直接在Xcode中查看NSKeyValueCoding.h源码中的78行。里面有详细的说明。

KVO(监听属性变化)

这里说说自己经验:

  • 如果需要监听本类中的属性变化直接通过重写属性的set方法就可以了。
  • 如果需要监听其他类中的属性变化,比较low的做法就是在set方法中发通知。或者通过KVO来实现。

在这个项目中,需要在CYLTabBarController中监听tabBar的变化。所以用到了KVO

代码如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    // 处理tabBar,使用自定义 tabBar 添加 发布按钮
    [self setUpTabBar];
    // KVO注册监听
    if (!self.isObservingSwappableImageViewDefaultOffset) {
        [self.tabBar addObserver:self forKeyPath:@"swappableImageViewDefaultOffset" options:NSKeyValueObservingOptionNew context:CYLSwappableImageViewDefaultOffsetContext];
        self.observingSwappableImageViewDefaultOffset = YES;
    }
    self.delegate = self;
}

注意:使用了KVO一定要记得移除通知,这点和注册通知一样。

 - (void)dealloc {
    // KVO反注册
    if (self.isObservingSwappableImageViewDefaultOffset) {
        [self.tabBar removeObserver:self forKeyPath:@"swappableImageViewDefaultOffset"];
    }
}

在类中实现- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context方法即可。

// KVO监听执行
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if(context != CYLSwappableImageViewDefaultOffsetContext) {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
        return;
    }
    if(context == CYLSwappableImageViewDefaultOffsetContext) {
        CGFloat swappableImageViewDefaultOffset = [change[NSKeyValueChangeNewKey] floatValue];
        [self offsetTabBarSwappableImageViewToFit:swappableImageViewDefaultOffset];
    }
}

动态添加只读属性

在OC中,利用Runtime给类添加新的属性非常方便,以前在添加属性的时候没有注意属性的访问性。但是在做框架的时候就需要考虑到了。

比如@property (nonatomic, readonly) CYLTabBarController *cyl_tabBarController;为了更加便捷的获取到全局cyl_tabBarController特地为NSObject添加了动态属性cyl_tabBarController

实现添加只读属性的的方法不难总的来说就是。在.m文件里面通过两个分类来实现,一个对外,一个对内(通过分类名称控制)。具体到代码如下:

.h文件

@interface NSObject (CYLTabBarController)

@property (nonatomic, readonly) CYLTabBarController *cyl_tabBarController;

@end

注意分类名称CYLTabBarController

.m文件

@implementation NSObject (CYLTabBarControllerItemInternal)

- (void)cyl_setTabBarController:(CYLTabBarController *)tabBarController {
    
    objc_setAssociatedObject(self, @selector(cyl_tabBarController), tabBarController, OBJC_ASSOCIATION_ASSIGN);
}

@end

@implementation NSObject (CYLTabBarController)

- (CYLTabBarController *)cyl_tabBarController {
    CYLTabBarController *tabBarController = objc_getAssociatedObject(self, @selector(cyl_tabBarController));
    if (tabBarController) {
        return tabBarController;
    }
    if ([self isKindOfClass:[UIViewController class]] && [(UIViewController *)self parentViewController]) {
        tabBarController = [[(UIViewController *)self parentViewController] cyl_tabBarController];
        return tabBarController;
    }
    id delegate = ((id)[[UIApplication sharedApplication] delegate]);
    UIWindow *window = delegate.window;
    if ([window.rootViewController isKindOfClass:[CYLTabBarController class]]) {
        tabBarController = (CYLTabBarController *)window.rootViewController;
    }
    return tabBarController;
}

@end

扩大视图点击区域

这个需求在平时开发中还是挺常见的,最多的就是扩大button的点击区域。可以通过- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event实现,也可以通过- (UIView*)hitTest:(CGPoint) point withEvent:(UIEvent*) event实现。

在这个库里面是通过- (UIView*)hitTest:(CGPoint) point withEvent:(UIEvent*) event实现的。

  • 1.判断是否响应点击事件
  • 1.1 是否隐藏
  • 1.2 透明度小于0
  • 1.3 是否开启userInteractionEnabled
  • 1.4 frame是否为0
  • 2.找到对应响应视图
  • 2.1 点击的点是否在视图内部(CGRectContainsPoint
  • 3.返回对应的响应视图

注意:这的point是以重写hitTest视图的frame为参考系

你可能感兴趣的:(CYLTabBarController源码分析(一))