ZCLemo的学习领域

有时候,静下心来学习是一种享受!


  • 首页

  • 归档

  • 关于

  • 标签

数据埋点

发表于 2017-07-17   |  

1.最近公司做了一个关于高端理财的项目,之前一些项目主要是通过第三方“友盟”进行数据统计。这次公司在项目中提出了数据埋点的需求,自己采集使用app人群的数据,自己在后台进行大数据的分析。

2.埋点分为页面统计和事件统计,公司为项目中每个页面定义了唯一的pageId,就为了这件事我们和产品争论了很长时间,我们客户端希望我们只传页面对应的类名,然后后台根据数据自己关联。产品又说安卓和iOS的类名可能不一样,所以他们不能统一。我们又给出了方案,我们新增一个字段”type”用来区分是安卓还是iOS,然后你们自己在关联。后来还是胳膊扭不过大腿,pageId传了,”type”也传了。。。干不过那就想办法做吧,页面怎么和pageId关联就成了一个问题,既然一一对应,我就想到了字典,那就写在一个本地plist文件里吧。每次进入页面和离开页面都要去plist去取,也不太合适,我就写了单例,只读取一次plist。但是还要每次去单例里去值,所以后来又想了一个办法,给基类定义一个属性,每次读取到了就保存下来,只要页面没有被销货,每次就不要重新取了,也算是做到了性能方面的优化吧。

3.事件统计比较简单,每个事件对应一个事件id,所以只要硬编码定义事件id,在需要加的地方加一下就行了,和友盟的mobClick方法类似。

4.事件和页面埋点数据都存到本地,达到一定的条数就上传,传完就删除。我也限制了最大存储条数,以防在断网和服务器异常的情况下无限制的往本地存储。

5.最后上Demo,github地址

ios组件化三种方案

发表于 2017-05-10   |  

前言:

前段时间由于公司业务的需要,要求对项目进行组件化的拆分,减少各个模块之间耦合。希望达到的效果是各个组件能单独的打私有pod,方便在其它工程里引用。由于项目现在比较庞大,目前只做了本地拆分。期间学习了很多有关组件化方面的知识,现在做个总结,后面会有有三种方案相关的资料链接。先上我自己对于三种方案总结的一个demo,github地址

概述:

组件化是为了各个模块不进行直接的相互引用,降低耦合度,那么如果组件A想调用组件B的时候要怎么办呢,其实原理上都是通过中间件来调用,而不需要模块间相互引用。 我们所看到的组件化方案,大体总结来说有三种:

- 1.procotol方案
- 2.URL路由方案
- 3.target-action方案

一、procotol协议注册方案

关于procotol协议注册方案见到别人分享比较少,有次查资料的时候看到了,就研究了一下。
在demo中ProcotolManager作为中间件:

1
2
3
- (void)registServiceProvide:(id)provide forProcotol:(Protocol *)procotol;

- (id)serviceProvideForProcotol:(Protocol *)procotol;

所有组件对外提供的procotol和组件提供的服务由中间件统一管理,每个组件提供的procotol和服务是一一对应的。

在ProductDetailServiceProvide中:load方法会应用启动的时候调用,就会在ProcotolManager进行注册。ProductDetailServiceProvide遵守了ProductDetailServiceProcotol协议,所以对能外提供productDetailViewControllerWithProductId服务。

1
2
3
4
5
6
7
8
9
10
11
+ (void)load
{
[[ProcotolManager sharedManger] registServiceProvide:[[self alloc] init] forProcotol:@protocol(ProductDetailServiceProcotol)];
}

- (UIViewController *)productDetailViewControllerWithProductId:(NSString *)productId
{
ProcotolProductDetailViewController *detailVC = [[ProcotolProductDetailViewController alloc] init];
detailVC.productId = productId;
return detailVC;
}

所以在首页中,通过ProcotolManager取出ProductDetailServiceProcotol对应的服务提供者ProductDetailServiceProvide,就可以调用产品详情中所提供的服务,而不需要进行直接引用。

二、URL路由方案

URL路由方案参考的是蘑菇街MGJRouter方案
蘑菇街 App 的组件化之路,讲的比较详细。

三、target-action方案

target-action方案是在学习CTMediator的基础上进行的,ZCMediator作为中间件,里面的实现也比较简单。

1
- (id)performTargetName:(NSString *)targetName actionName:(NSString *)actionName paramters:(NSDictionary *)paramtersDict;

执行时查找对应的target有没有对外暴露的服务,如果有则执行。
主要的还是每一个组件暴露出的category,是对中间件的一个扩展,调用每个组件对应的category方法,然后在通过中间件调用对外暴露的服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#import "ZCMediator+ProductDetail.h"

//target
NSString *const MP_PRODUCT_DETAIL_TARGET = @"ProductDetailTarget";

//方法名
NSString *const MP_PRODUCT_DETAIL = @"productDetailViewControllerWithParameters";


@implementation ZCMediator (ProductDetail)

- (UIViewController *)productDetailViewControllerWithProductName:(NSString *)productName productId:(NSString *)productId
{
if (!productName || !productId) {
return nil;
}
return [self performTargetName:MP_PRODUCT_DETAIL_TARGET actionName:MP_PRODUCT_DETAIL paramters:@{@"productName":productName,@"productId":productId}];
}


@end

我们项目里使用的就是target-action方案。下次再写一篇几种方案的比较吧。

参考链接:

iOS组件化方案

iOS应用架构谈 组件化方案

蘑菇街 App 的组件化之路

MVVM使用

发表于 2017-03-13   |  

1.最近比较忙,一直在忙着新项目的开发。以前的项目都是用mvc模式,有些复杂的页面的controller代码里会比较多,虽然也做过一些拆分,但是还不能满足老大的要求。所以在新项目中使用了MVVM模式,MVVM模式的介绍网上的文章一搜一大堆,我这就不介绍了。

2.看过MVVM的一些文章,肯定会知道RAC,虽然说MVVM模式和RAC并没有什么关联,但是RAC对于MVVM模式中ViewModel和View的绑定,ViewModel和Controller的绑定来说相当方便。

3.值得一提的是,我们加入了一些自己的创新,我们给Controller都新增了一个RootView,加在Controller的View上,所有的视图层展示和操作都在这个RootView上进行,这样Controller里面的代码就非常少了。刚开始我们项目中的哥们提出这个方案时,我们一度很不能理解为什么要这样做,这样不是多此一举吗,毕竟每个Controller都有自己的View视图,而且视图中的一些操作我们就不能直接告诉Controller了,还有就是ViewModel处理的一些数据我们要怎么样既能告诉Controller又能告诉RootView。后来经过大家的讨论,觉得这个方法还是可行的,并对上面的疑问都提供了方案。

3.1 对于RootView中的一些操作不能直接告诉Controller的疑问,解决的办法有很多,通过Block回调,代理,RAC都能解决,虽然多做了一步,但还是很方便的嘛。
3.2 对于ViewModel处理的一些数据我们要怎么样既能告诉Controller又能告诉RootView,我们的解决办法是Controller和RootView公用一个ViewModel,这样你想和谁绑定都行了。
3.3 现在我们都认为单独抽出一个RootView是个很不错的决定,无论用哪种设计模式,最后无非都是为了给Controller减负,减少Controller里面的代码量,使结构更清晰。其实想想每个工程里都有一个Window,Window就相当于幕布,所有的Controller的表演都在幕布上进行。那现在把Controller的view想象成幕布,你所有其他View层的表演都自己进行,最后在我的view里面统一放映进行了,管理者少了不少工作,立马就轻松了.

4.最后上代码吧,很简单的一个demo,希望能对大家有所帮助。github地址

基于PhotoKit写的图片,视频选择器

发表于 2016-12-14   |  

1.在公司的项目中,图片选择的时候需要自己定义一个图片选择器,自己根据PhotoKit写了一个Demo,也顺便学习了一下PhotoKit,自己扩展了一下,既能选择图片也能选择视频。

1.1 图片选择,如下图:

img

1.2 获取相册,如下图:

img

1.3 视频选择,如下图:

img

2.相册和媒体的获取代码主要在ZCPhotoManager这个类里面,接下来可能写一篇关于PhotoKit介绍的博客。

2.1 获取相册
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- (NSMutableArray *)showAlbums {

PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
PHFetchResult *topLevelUserCollections = [PHCollectionList fetchTopLevelUserCollectionsWithOptions:nil];
self.fetchResults = @[smartAlbums, topLevelUserCollections];

NSMutableArray *albums = [NSMutableArray array];

for (PHFetchResult *fetchResult in self.fetchResults)
{
for (PHCollection *collection in fetchResult) {
if ([collection isKindOfClass:[PHAssetCollection class]])
{
PHAssetCollection *assetCollection = (PHAssetCollection *)collection;
PHFetchResult *assets = [self assetsInAssetCollection:assetCollection];
if (assets.count > 0) {
if (assetCollection.assetCollectionSubtype == PHAssetCollectionSubtypeSmartAlbumUserLibrary) {
[albums insertObject:assetCollection atIndex:0];
} else {
[albums addObject:assetCollection];
}

}

}
}
}
return albums;
}
2.2 获取相册中的媒体
1
2
3
4
5
6
7
- (PHFetchResult *)assetsInAssetCollection:(PHAssetCollection *)album{

PHFetchOptions *options = [[PHFetchOptions alloc] init];
options.predicate = [NSPredicate predicateWithFormat:@"mediaType in %@", self.mediaTypes];
options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];
return [PHAsset fetchAssetsInAssetCollection:(PHAssetCollection *)album options:options];
}
2.3 然后主要的功能就是图片的展示,布局。

3.直接上Demo

github地址

3.1 由于时间比较短,里面的UI写的可能有点粗糙,主要是为了自己学习,就这样吧。

MBProgressHUD的一些常用的封装

发表于 2016-11-29   |  

1 项目中,大部分的网络加载时都会加上一个等待菊花框,MBProgressHUD是一个很好用的第三方库,在MBProgressHUD基础上进行了一些封装,在项目中用到的机会比较大,希望对你们有用。

1.1 第一种就是就是普通的菊花,如下图:

img

1.2 提示Toast,网络请求提示,报错,提醒之类的,如下图:

img

1.3 gif图类型的菊花,现在大部分都不用系统的菊花了,都会用gif图代替,如下图:(图好像截的有问题,具体效果看代码)

img

2.下面上代码:

github地址

3.提示一下,有时候一个控制器可能同时进行多个网络请求,需要多个请求结束后再隐藏菊花,里面也提供了方法,使用- (void)hiddenWaitingForTransaction方法,里面也有例子,对于gif型菊花和普通型菊花都适用。

iOS明文密文切换时问题

发表于 2016-11-23   |  

1.项目中经常遇到密码需要明文,密文切换的样式,如下图:

img

2.切换过程中,会在10以下的系统会出现几个问题,1.出现空格,光标位置错乱,2.字体变化,3.密文时输入文字清空,下面分别列举出解决办法:

3.1 第一种问题解决办法,先保存文本,再清空输入,再赋值,就解决了空格问题

1
2
3
4
5
6
7
8
9
10
11

- (void)eyesBtnClick:(UIButton *)btn
{
self.textField.secureTextEntry = btn.selected;
btn.selected = !btn.selected;

NSString *oriText = self.textField.text;
self.textField.text = @"";
self.textField.text = oriText;

}

3.2 字体变化问题,重新设置一下字体,保持和现在设置的一样

1
2
3
4
5
6
7
8
9
10
11
12

- (void)eyesBtnClick:(UIButton *)btn
{
self.textField.secureTextEntry = btn.selected;
btn.selected = !btn.selected;

NSString *oriText = self.textField.text;
self.textField.text = @"";
self.textField.text = oriText;

self.textField.font = [UIFont fontWithName:@"HelveticaNeue" size:16];
}

3.3 密文输入清空问题,在代理里面,加多一个判断能避免密文清空的问题,如下:

1
2
3
4
5
6
7
8
9
10
11

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
//明文切换密文后避免被清空
NSString *toBeString = [textField.text stringByReplacingCharactersInRange:range withString:string];
if (textField == self.textField && textField.isSecureTextEntry) {
textField.text = toBeString;
return NO;
}
return YES;
}

iOS卡片式布局view

发表于 2016-10-27   |  

1.新版本的项目中,深深的感受到了设计对我们的恶意,设计的页面有这样的,

img

这样的

img

还有这样的

img

什么鬼,并不是几个页面只是这样的,而是大面积的页面都是这样的,当我看到时我的内心是奔溃的,但没办法,架不住设计师的坚持。

2.没办法,只有自己做了,刚开始分析下看能不能用背景图片来解决,后来发现自己想多了,因为根本没有规律,凹槽的地方根本没规律,都是出现在随心所欲的出现在各个地方。经过大家最后的商量,画出凹槽,往上面一块块拼接吧。结合Masonry,下面上代码。

link

3.例子里凹槽部分完全是自己画的,虚线和两个半圆。如果一屏展示不下你想要展示的view,可以用scrollview,再根据Masonry来控制scrollview的滚动范围,后面我会再传一下这部分代码。我一直在想能不能有什么好的办法可以解决,也希望有大神给我好的建议。

navigation黑边问题

发表于 2016-10-24   |  

今天在做项目时,出现了navigationbar 隐藏/显示 出现黑边问题,搞了好久,终于找到解决办法。。。

1.在网上找了好久,基本上都是以下的方法,如下:
1.1 在页面即将出现时隐藏navgaionbar
1
2
3
4
5
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:YES animated:animated];
}
1.2 在页面即将消失时显示navgaionbar
1
2
3
4
5
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.navigationController setNavigationBarHidden:NO animated:animated];
}

结果悲剧的发现黑边情况还是会出现,因为上述方法是针对页面间push时有作用,但是项目中我的页面是present一个登录页,坑啊,还好找到了如下方法,使用navigationController的代理方法:

2.1 页面即将出现时设置代理
1
2
3
4
5
6
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];

self.navigationController.delegate = self;
}
2.2 页面消失时取消代理
1
2
3
4
5
6
7
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
if (self.navigationController.delegate == self) {
self.navigationController.delegate = nil;
}
}
代理方法
1
2
3
4
5
6
7
8
- (void)navigationController:(UINavigationController*)navigationController willShowViewController:(UIViewController*)viewController animated:(BOOL)animated
{
if (viewController == self) {
[self.navigationController setNavigationBarHidden:YES animated:YES];
}else{
[self.navigationController setNavigationBarHidden:NO animated:YES];
}
}

TTTAttributedLabel 高度计算

发表于 2016-10-24   |  
1.TTTAttributedLabel里的文字高度计算和普通label的动态高度不同,如果你用普通label的动态高度计算的话,在TTTAttributedLabel里则显示不对
2.下面方法
1
2
3
4
5
6
7
8
9
10
11
// tttLabel是TTTAttributedLabel的实例
// _model.content是NSString实例

__block CGFloat height = 0;
[self. tttLabel setText:_model.content afterInheritingLabelAttributesAndConfiguringWithBlock:^NSMutableAttributedString *(NSMutableAttributedString *mutableAttributedString) {
height = [TTTAttributedLabel sizeThatFitsAttributedString:mutableAttributedString
withConstraints:CGSizeMake(200, MAXFLOAT)
limitedToNumberOfLines:0].height;
return mutableAttributedString;
}];
NSLog(@"%f",height);

ios画虚线

发表于 2016-10-17   |  

项目中用到了自己画的虚线,记录一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {


// 画虚线

CAShapeLayer *shapeLayer = [CAShapeLayer layer];
[shapeLayer setFillColor:[[UIColor clearColor] CGColor]];

// 设置虚线颜色为blackColor
[shapeLayer setStrokeColor:[[UIColor colorWithHex:@"#dcd2de"] CGColor]];
[shapeLayer setStrokeColor:[[UIColor colorWithRed:223/255.0 green:223/255.0 blue:223/255.0 alpha:1.0f] CGColor]];

// 3.0f设置虚线的宽度
[shapeLayer setLineWidth:1.0f];
[shapeLayer setLineJoin:kCALineJoinRound];

// 8=线的宽度 3=每条线的间距
[shapeLayer setLineDashPattern:
[NSArray arrayWithObjects:[NSNumber numberWithInt:8],
[NSNumber numberWithInt:6],nil]];

// Setup the path
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, 25, 15);
CGPathAddLineToPoint(path, NULL, frame.size.width-25,15);
[shapeLayer setPath:path];
CGPathRelease(path);

// 可以把self改成任何你想要的UIView
[[self layer] addSublayer:shapeLayer];

}
return self;
}
12
赵琛

赵琛

我是赵琛,一名iOS开发工程师!我的邮箱18356033655@163.com

20 日志
2 标签
© 2017 赵琛
由 Hexo 强力驱动
主题 - NexT.Pisces