已经好久没有分析源码了,上个周搞了些服务端的东西,接触到一些比较基础的服务端开发。这里简单的就看看
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_EXTERN
、extern
下面就具体细节深入讨论下:
先来它的定义:
#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_EXTERN
和extern
区别就是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
子类也不会有警告。
- __kindof:作用就是扩展泛型,让泛型支持子类。用法比如:
代码块(代码组织简洁)
这个特性平时用得比较多,在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为参考系