lly's Blog

用心记录点滴


  • 首页

  • 归档

App Programming Guide for iOS

发表于 2016-11-28   |  

最近看了下苹果官方文档这块,做一下总结。

后台执行

一、短时间的后台执行

1.使用 beginBackgroundTaskWithName 方法创建一个后台任务,具体代码如下:

1
2
3
4
5
6
__block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithName:@"MyTask" expirationHandler:^{
// Clean up any unfinished task business by marking where you
// stopped or ending the task outright.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];

该任务可以在后台运行180秒的时间,UIApllication提供了字段
backgroundTimeRemaining获取当前剩余时间。

2.使用NSURLSession的后台下载任务,可以在后台继续下载一段时间。创建一个后台任务的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (NSURLSession *)backgroundURLSession {
static NSURLSession *session = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *identifier = @"com.yourcompany.appId.BackgroundSession";
NSURLSessionConfiguration* sessionConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:identifier];
sessionConfig.sessionSendsLaunchEvents = YES;//默认是YES
sessionConfig.discretionary = YES;//性能相关
defaultSessionConfiguration];
session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
});
return session;
}

二、长时间的后台运行

有些App需要长时间在后台运行的,比如音乐播放器 录音App 导航App (Voice over IP)VoipApp 外设App 大量后台下载任务类。。。需要在Xcode上做相应的设置才行,参考下面这个图,在Capabilities的Background Modes标签中勾选你想实现的功能。

实现特定功能的策略

一、隐私策略,使用磁盘加密保护数据

使用NSData类设置保护等级。
当写入新文件时,你可以使用NSData的writeToFile:options:error:方法,具体实现如下:

1
2
NSError *error = nil;
[[NSData data] writeToFile:@"文件需要写入的路径" options:NSDataWritingAtomic error:&error];

options参数传入的即为文件的保护等级,具体枚举类型如下:

1
2
3
4
5
6
7
8
9
10
11
typedef NS_OPTIONS(NSUInteger, NSDataWritingOptions) {
NSDataWritingAtomic = 1UL << 0, //保存时提示使用辅助文件,相当于自动
NSDataWritingWithoutOverwriting,//提示,以防止覆盖现有的文件.不能和NSDataWritingAtomic合并使用。
NSDataWritingFileProtectionNone,//文件被加密但是当设备被锁屏时不能被密码保护
NSDataWritingFileProtectionComplete,//当设备被锁屏文件被加密且不能被访问
NSDataWritingFileProtectionCompleteUnlessOpen,//当设备被锁屏文件被加密且不能被访问,如果当文件被访问时设备锁屏,应用仍然可以在锁屏状态下访问文件.
NSDataWritingFileProtectionCompleteUntilFirstUserAuthentication,//文件被加密且不能访问直到设备启动,用户解锁一次后。
NSDataWritingFileProtectionMask,
// Options with old names for NSData writing methods. Please stop using these old names.
NSAtomicWrite = NSDataWritingAtomic // Deprecated name for NSDataWritingAtomic
};

对于已存在的文件,你可以使用NSFileManager的setAttributes:ofItemAtPath:error:方法。具体实现如下:

1
[[NSFileManager defaultManager] setAttributes:@{NSFileProtectionComplete:NSFileProtectionKey} ofItemAtPath:@"文件路径" error:nil]

NSFileManager的保护等级枚举值如下:

1
2
3
4
5
6
7
NSFileProtectionNone //文件未受保护,随时可以问 (Default)
NSFileProtectionComplete //文件受到保护,而且只有在设备未被锁定时才可访问
NSFileProtectionCompleteUntilFirstUserAuthenticatio//文件受到保护,直到设备启动且用户第一次输入密码
NSFileProtectionCompleteUnlessOpen//文件受到保护,而且只有在设备未被锁定时才可打开,不过即便在设备被锁定时,已经打开的文件还是可以继续使用和写入

二、区别每一个用户

区别每一个用户或者设备主要有下面几种方式实现:

1.账号密码方式

2.设备唯一标识符 [[UIDevice currentDevice]identifierForVendor];App卸载后再安装,值不一样。

3.广告标识符NSString *adId = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
重置系统,值会重新生成。

4.三方框架(OpenUDID)等

三、尊重的限制(评级)

每个App都会有一个评级,而且App所展示的内容也有对应的评级,如果评级不在允许的范围,则不能使用该内容。这个评级系统是USA的。

具体获取方式:

1
BOOL isBooksAllow = [[NSUserDefaults standardUserDefaults] objectForKey:@"com.apple.content-rating.ExplicitBooksAllowed"];

如果objectForKey返回为空,这意味着这个限制信息是不可用的。在这种情况下,您的应用程序可以使用自己的策略来确定适当的评级。

四、保存当前App的状态

实现appdelegate的下面两个代理,告诉系统你需要保存和恢复当前App的状态:

1
2
3
4
5
6
7
- (BOOL) application:(UIApplication *)application shouldSaveApplicationState:(NSCoder *)coder {
return YES;
}
- (BOOL) application:(UIApplication *)application shouldRestoreApplicationState:(NSCoder *)coder {
return YES;
}

初始化每个VC的时候,需要为restorationIdentifier和restorationClass这两个字段赋值,否则,不能保存VC的状态。

具体保存和恢复过程如下:

保存过程:
1.告诉UIKit支持保存
2.告诉UIKit什么控制器和视图需要保存
3.给保存的对象编码

恢复过程:
1.告诉UIKit支持恢复
2.提供需要的UIKit对象
3.让保存的对象恢复原样

对应的每个VC需要遵守UIViewControllerRestoration,UIStateRestoring这两个协议,然后自己实现下面这3个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
+ (nullable UIViewController *) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder{
UIViewController *vc = (UIViewController *)[UIApplication sharedApplication].delegate.window.rootViewController;
return vc;
}
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
[coder encodeObject:self.progressLabel.text forKey:@"LabelTextKey"];
CGFloat progress = self.downloadProgress.progress;
[coder encodeObject:[NSString stringWithFormat:@"%.2f",self.downloadProgress.progress] forKey:@"ProgressKey"];
[super encodeRestorableStateWithCoder:coder];
}
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder
{
NSString *string = [coder decodeObjectForKey:@"LabelTextKey"];
self.progressLabel.text = string;
CGFloat progress = [[coder decodeObjectForKey:@"ProgressKey"] floatValue];
self.downloadProgress.progress = progress;
[super decodeRestorableStateWithCoder:coder];
}

下面是保存和恢复的流程图:

保存

恢复

App之间的通信

一、AirDrop

创建一个UIActivityViewController实例即可实现airdrop功能,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
UIActivityViewController *activity = [[UIActivityViewController alloc] initWithActivityItems:@[[[NSBundle mainBundle] URLForResource:@"Steve" withExtension:@"pdf"]] applicationActivities:@[[[UIActivity alloc] init]]];
// hide AirDrop
// activity.excludedActivityTypes = @[UIActivityTypeAirDrop];
UIPopoverPresentationController *popover = activity.popoverPresentationController;
if (popover) {
popover.sourceView = self.activityButton;
popover.permittedArrowDirections = UIPopoverArrowDirectionAny;
}
//只能模态的显示
[self presentViewController:activity animated:YES completion:NULL];

其中,excludedActivityTypes字段可以控制界面显示内容。

tips:因为 UIActivityViewController 只支持模态显示,在iPad上使用时,需要使用UIPopoverPresentationController设置显示样式。

二、URL Schemes

Schemes表示一个URL中最初始的位置即://之前的那段字符
比如http://www.apple.com这个网址的Schemes是http,支持浏览器打开。

如果一个app设置了Url Schemes,则可以通过这个url从第三方打开该APP,并可以传递一些参数。
每个URL必须能唯一标识一个APP,如果Url Schemes发生冲突,则最后被安装的APP会被调用。

系统的APP的URL Schemes优先级最高。

PS: iOS9新增了一个白名单,苹果规定开发者只能设置50个 URL Schemes 来打开别的应用,在openurl之前要先用canOpenUrl判断URL是否能被打开,但是返回上一个App不需要判断,也不用设置白名单

三、KeyChain

KeyChain是苹果提供的一种安全的保存用户名、密码、证书的方式,将敏感信息保存在keychain中后,这些信息不会随着app的卸载而丢失,除非开发人员在app中手动删除敏感信息,否则,这些信息将会一直保存在keychain中.

手机刷机或者恢复出厂后数据会丢失。

iOS10以后,使用KeyChain需要在Xcode的Capabilities中打开KeyChain权限,否则无法获取保存和读取数据的权限。

四、UIPasteboard

粘贴板分为系统粘贴板和自定义粘贴板,自定义粘贴板只能同组的APP可以互相访问,系统的粘贴板所有的App都可以访问。

详见App之间的通信demo

性能tips

一、像素混合

当UILabel的背景色和父视图的背景色不一致,或者几个重叠的控件之间的背景色不一致时,会出现像素混合的问题,修改方法是将所有的控件背景色调为一致。

二、离屏渲染

给控件设置圆角的时候会出现离屏渲染的问题,如果是UIImageView,可以通过重画UIImage为圆形图片,再赋值给UIImageView的方式解决,具体代码参考如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (UIImage *)lly_drawRadiusWithRoundCorner:(CGFloat)radius andSize:(CGSize) size{
CGRect rect = CGRectMake(0,0,size.width,size.height);
UIGraphicsBeginImageContextWithOptions(rect.size, NO, [UIScreen mainScreen].scale);
CGContextAddPath(UIGraphicsGetCurrentContext(), [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(radius, radius)].CGPath);
CGContextClip(UIGraphicsGetCurrentContext());
[self drawInRect:rect];
CGContextDrawPath(UIGraphicsGetCurrentContext(), kCGPathFillStroke);
UIImage *output = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return output;
}

– END –

菲李莫属隐私政策

发表于 2016-10-11   |  

个人信息保护政策

为切实保护菲李莫属用户隐私权,优化用户体验,我公司根据现行法规及政策,制定本《个人信息保护政策》。 本《个人信息保护政策》将详细说明菲李莫属在获取、管理及保护用户个人信息方面的政策及措施。本《个人信息保护政策》适用于菲李莫属向您提供的所有服务,无论您是通过计算机设备、移动终端或其他设备获得的菲李莫属服务。

个人信息的收集

您已知悉且同意,在您注册菲李莫属帐号或使用菲李莫属提供的服务时,菲李莫属将记录您提供的相关个人信息,如:姓名、手机号码等,上述个人信息是您获得菲李莫属提供服务的基础。同时,基于优化用户体验之目的,菲李莫属会获取与提升菲李莫属服务有关的其他信息,例如当您访问菲李莫属时,我们可能会收集哪些服务的受欢迎程度、浏览器软件信息等以便优化我们的服务。

个人信息的管理

为了向您提供更好的服务或产品,菲李莫属会在下述情形使用您的个人信息:
1)根据相关法律法规的要求;
2)根据您的授权;
3)根据菲李莫属相关服务条款、应用许可使用协议的约定。

此外,您已知悉并同意:在现行法律法规允许的范围内,菲李莫属可能会将您非隐私的个人信息用于市场营销,使用方式包括但不限于:在菲李莫属平台中向您展示或提供广告和促销资料,向您通告或推荐菲李莫属的服务或产品信息,以及其他此类根据您使用菲李莫属服务或产品的情况所认为您可能会感兴趣的信息。其中也包括您在采取授权等某动作时选择分享的信息,例如当您新增朋友、在动态中新增地标、使用菲李莫属的联络人汇入工具等。

未经您本人允许,菲李莫属不会向任何第三方披露您的个人信息,下列情形除外:
1)菲李莫属已经取得您或您监护人的授权;
2)司法机关或行政机关给予法定程序要求菲李莫属披露的;
3)菲李莫属为维护自身合法权益而向用户提起诉讼或仲裁时;
4)根据您与菲李莫属相关服务条款、应用许可使用协议的约定;
5)法律法规规定的其他情形。

个人信息的保护

菲李莫属将尽一切合理努力保护其获得的用户个人信息。为防止用户个人信息在意外的、未经授权的情况下被非法访问、复制、修改、传送、遗失、破坏、处理或使用,菲李莫属已经并将继续采取以下措施保护您的个人信息:
1)以适当的方式对用户的个人信息进行加密处理;
2)在适当的位置使用密码对用户个人信息进行保护;
3)限制对用户个人信息的访问;
4)其他的合理措施。

尽管已经采取了上述合理有效措施,并已经遵守了相关法律规定要求的标准,但菲李莫属仍然无法保证您的个人信息通过不安全途径进行交流时的安全性。因此,用户个人应采取积极措施保证个人信息的安全,如:定期修改帐号密码,不将自己的帐号密码等个人信息透露给他人。

您知悉:菲李莫属提供的个人信息保护措施仅适用于菲李莫属平台,一旦您离开菲李莫属,浏览或使用其他网站、服务及内容资源,菲李莫属即没有能力及义务保护您在菲李莫属以外的网站提交的任何个人信息,无论您登陆或浏览上述网站是否基于菲李莫属的链接或引导。

个人信息的访问

当您完成菲李莫属的帐号注册后,您可以查阅或修改您提交给菲李莫属的个人信息。一般情况下,您可随时浏览、修改自己提交的信息,但出于安全性和身份识别(如号码申诉服务)的考虑,您可能无法修改注册时提供的某些初始注册信息及验证信息。

对未成年人个人信息的特别保护
菲李莫属非常重视对未成年人个人信息的保护。若您是18周岁以下的未成年人,在使用菲李莫属的服务前,应确保事先取得监护人的同意,如您在菲李莫属上申请注册账号,菲李莫属将默认为您已得到前述同意。菲李莫属将根据国家相关法律法规及本《个人信息保护政策》的规定保护未成年人的个人信息。

隐私保护政策的修改

菲李莫属有权随时修改《个人信息保护政策》的任何条款,一旦《个人信息保护政策》的内容发生变动,菲李莫属将会直接在网站上公布修改之后的《个人信息保护政策》,该公布行为视为菲李莫属已经通知您修改内容。菲李莫属也可通过其他适当方式向用户提示修改内容。如果您不同意菲李莫属对本《个人信息保护政策》相关条款所做的修改,您有权停止使用菲李莫属服务。如果您继续使用菲李莫属服务,则视为您接受本公司对本协议相关条款所做的修改。

菲李莫属 ©llyblog.com 10-11 09:39

仿映客直播app

发表于 2016-09-14   |  

先看看demo效果,我用的Gif brewery3制作的gif,导出格式好大,只好压缩尺寸和帧率了。。。

直播相关的资料我就不在这里详细介绍了,网上资料特别多,搭建本地服务器,获取视频流和音频流,推流和拉流,播放器等等…

先说说下面这个tabbar吧 这个是我自定义的一个,继承了UITabbarController 然后手动调整高度为60 设置背景图的时候需要把图片模式设置为拉伸模式(UIImageResizingModeStretch),中间圆形按钮也需要手动添加一个uibutton,注意设置中心点在tabbar中心位置。

接口是通过Charles抓取的,图片资源在mac上下载映客ipa然后导出既可。

项目中的直播流播放器是用的开源项目KxMovie做的,把它的界面稍微改了一下,网上有很多类似demo都用的ijkplayer,我也下载这些demo跑过,延迟不是一般的高,至少10s以上,完全无法忍受。。。

点赞效果是我以前没事自己写的一个小demo 集成进来的

弹幕效果也是用的第三方开源项目BarrageRenderer

demo在这里,欢迎下载。

iOS卡片控件的实现

发表于 2016-09-01   |  

###需求
CardPageDemo

最近公司给了个需求,实现上面卡片效果,这种控件网上也有现成的demo,但有些地方还是要自己研究下才能满足使用,下面记录一下我的研究成果。

1.使用UICollectionView控件。

如果这是单纯的轮播图片,使用UIScrollView就可以满足需要,但是上面的效果有缩放还有渐变,用UIScrollView的话代码量大,实现比较复杂,而且数据处理起来也没有用UICollectionView方便,这里再放一个用UIScrollView实现的demo,有需要的可以对比一下。

2.自定义UICollecionViewFlowLayout类。

使用过UICollectionView的同学们对这个类应该都不陌生吧,瀑布流布局类,可以通过自定义这个类实现各种瀑布流的效果,我要实现的这个效果当然也是需要自定义这个类了。

先来看看prepareLayout这个函数的实现吧,这个是布局初始化的时候调用的,可以在这个函数里面自定义每一个cell的大小,cell之间的间距等属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)prepareLayout{
[super prepareLayout];
//滑动方向
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
//两个cell的间距
self.minimumLineSpacing = kLineSpace;
//计算cell超出显示的宽度
CGFloat width = ((self.collectionView.frame.size.width - kPageCardWidth)-(kLineSpace*2))/2;
//第一个cell和最后一个cell相对于屏幕的偏移
self.sectionInset = UIEdgeInsetsMake(0, kLineSpace+width, 0, kLineSpace+width);
//每个cell实际的大小
self.itemSize = CGSizeMake(kPageCardWidth,kPageCardHeight);
}

结合下面这两张图片,几个属性的关系就很清楚了:

首尾cell
首尾cell

中间cell
中间cell

要保证每一次cell滑动之后都能精确的移动到屏幕的中间,就必须计算出图上标的这几个参数的值,这里有两种情况:

1.cell宽度固定。

当cell的宽度是一个常量,两个cell之间的间距(minimumLineSpacing)一般都是不变的,很容易计算出前后两个cell超出显示在屏幕上的宽度 CGFloat width = ((self.collectionView.frame.size.width - kPageCardWidth)-(kLineSpace*2))/2;(参考中间cell这张图),首尾情况时,只要让sectionInset左右的偏移量 = width+minimumLineSpacing,左右的空白宽度等于了另一边的非空白的宽度(参考首尾cell这张图),这样无论滑到哪个cell,就能精确计算出移动到屏幕中间所需要的偏移量了。

2.前后cell超出显示在屏幕上的宽度(图中的width)固定。
这种情况就要根据屏幕宽度计算出cell的宽度kPageCardWidth = self.collectionView.frame.size.width - (kLineSpace x 2) -(width x 2); sectionInset左右的偏移量计算方法和第一种情况一样。

我这里的cell的宽度是固定值,所以计算起来相对简单,如果要让每个cell都在屏幕的中心点,对于第一个cell,只需要sectionInset左右边的偏移量 = width + minimumLineSpacing。

而对于后面的cell,则需要通过控制每一次滑动后当前显示cell的中心坐标。这个坐标当然是有方法来返回的,看下面这个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity {
// 分页以1/3处
if (proposedContentOffset.x > self.previousOffsetX + self.itemSize.width / 3.0) {
self.previousOffsetX += kPageCardWidth+kLineSpace ;
self.pageNum = self.previousOffsetX/(kPageCardWidth+kLineSpace);
if ([self.delegate respondsToSelector:@selector(scrollToPageIndex:)]) {
[self.delegate scrollToPageIndex:self.pageNum];
}
} else if (proposedContentOffset.x < self.previousOffsetX - self.itemSize.width / 3.0) {
self.previousOffsetX -= kPageCardWidth+kLineSpace;
self.pageNum = self.previousOffsetX/(kPageCardWidth+kLineSpace);
if ([self.delegate respondsToSelector:@selector(scrollToPageIndex:)]) {
[self.delegate scrollToPageIndex:self.pageNum];
}
}
//将当前cell移动到屏幕中间位置
proposedContentOffset.x = self.previousOffsetX;
return proposedContentOffset;
}

这个函数返回当前cell的中心点移动到哪个坐标点。

以第一个cell为基准(第一个cell已经在屏幕的中心),后面的每一个cell如果要移动到屏幕中心,X方向上的位移应该为2个cell中心点之前的距离,即previousOffsetX = kPageCardWidth(cell的宽度)+kLineSpace(2个cell的间距)。

OK,完成了一半了,然后就是缩放和渐变。

直接上代码

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
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray *superAttributes = [super layoutAttributesForElementsInRect:rect];
NSArray *attributes = [[NSArray alloc] initWithArray:superAttributes copyItems:YES];
CGRect visibleRect = CGRectMake(self.collectionView.contentOffset.x,
self.collectionView.contentOffset.y,
self.collectionView.frame.size.width,
self.collectionView.frame.size.height);
CGFloat offset = CGRectGetMidX(visibleRect);
[attributes enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes *attribute, NSUInteger idx, BOOL * _Nonnull stop) {
CGFloat distance = offset - attribute.center.x;
// 越往中心移动,值越小,那么缩放就越小,从而显示就越大
// 同样,超过中心后,越往左、右走,缩放就越大,显示就越小
CGFloat scaleForDistance = distance / self.itemSize.width;
// 0.1可调整,值越大,显示就越大
CGFloat scaleForCell = 1 + 0.1 * (1 - fabs(scaleForDistance));
//只在Y轴方向做缩放
attribute.transform3D = CATransform3DMakeScale(1, scaleForCell, 1);
attribute.zIndex = 1;
//渐变
CGFloat scaleForAlpha = 1 - fabsf(scaleForDistance)*0.4;
attribute.alpha = scaleForAlpha;
}];
return attributes;
}

上面这个函数返回一个UICollectionViewLayoutAttributes *类型的数组,简单点说就是滑动过程中动态计算每一个cell的属性然后传给UICollectionView显示出来,在这里动态修改每个cell的属性,显示的结果就是渐变和动画的效果了,具体可参考代码注释。根据位移控制好属性改变的系数即可。


###补充需求--轮播

轮播

还是原来的配方,还是熟悉的问题,轮播还是用的前后各加一个数据源的方式实现。具体参考我下面的demo。

这里我总结一下,如果要让轮播的过度看上去不那么明显应该注意两点:

1.应该让 前后cell超出显示在屏幕上的宽度(图中的width)固定 即上面说的第二种情况,且width的大小不宜过大。

2.cell的背景色和UICollectionView的背景色色差不宜过大。


###轮播补充
上面这种实现方式在处理最后一张到第一张的衔接的时候效果不太好,最近研究了下,比较好的实现方式是放多个section,初始化的时候手动滚动到中间section,每个section内容相同,这样在滑动的时候就不会出现上面跳一下的问题。这里要注意的就是处理好每个section之间的间距问题,具体参考我的demo。
新demo的效果如下:
轮播

这里是我的demo,请下载。

个人博客的搭建

发表于 2016-08-30   |  

如何搭建一个自己的博客呢?
看这一篇文章就够了。

##补充:

1.安装Node.js的时候出现问题,直接从官网下载的安装包安装后没用,这个时候可以选择通过命令行来安装,参考这篇文章,通过命令行安装的时候还是会有问题,目录不存在的问题,解决方法在这里 。

2.配置SSH-Key的一般步骤很简单,网上有很多教程,随手搬这篇过来,这是配置一个账号的,如果你同时拥有多个账号(比如公司一个账号,自己还有一个账号),可以参考这一篇文章。

3.使用过程中可能对markdown语法不太熟悉的小伙伴,可以看看这一篇文章.

以上是我在搭建自己的个人博客时遇到的问题,做一下记录,方便后来者。

记录自己写的线上bug

发表于 2015-11-27   |  

给NSDictionary设置nil值导致线上崩溃

崩溃描述

这个问题是昨天新版本上线后firebase开始报的,当时正在扫线上的崩溃问题(因为老板注意到我们的崩溃率比其他产品线高很多),所以在崩溃刚出现时就发现了。崩溃原因并不复杂,以下是出现崩溃的代码片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (void)p_recordMetrics:(NSURLSessionTaskMetrics *)metrics{
if (metrics.transactionMetrics > 0) {
NSMutableDictionary * track = [NSMutableDictionary dictionary];
NSURLSessionTaskTransactionMetrics *tm = metrics.transactionMetrics.lastObject;
[track setObject:@(metrics.taskInterval.duration * 1000) forKey:@"duration"];
[track setObject:tm.request.URL.absoluteString forKey:@"uri"];
NSTimeInterval domain = [tm.domainLookupEndDate timeIntervalSinceDate:tm.domainLookupStartDate] * 1000;
NSTimeInterval connect = [tm.connectEndDate timeIntervalSinceDate:tm.connectStartDate] * 1000;
NSTimeInterval tls = [tm.secureConnectionEndDate timeIntervalSinceDate:tm.secureConnectionStartDate] * 1000;
NSTimeInterval request = [tm.requestEndDate timeIntervalSinceDate:tm.requestStartDate] * 1000;
NSTimeInterval response = [tm.responseEndDate timeIntervalSinceDate:tm.responseStartDate] * 1000;
NSString *str = [NSString stringWithFormat:@"dns:%f,connect:%f,tls:%f,request:%f,response:%f",domain,connect,tls,request,response];
[track setObject:str forKey:@"metrics"];
[[FMBMonitor shareManager] trackData:track type:FMBMonitorTrackerTypeAPISample];
}
}

崩溃发生在给track设置uri的时候,在iOS13上,这个tm.request.URL.absoluteString字段有可能为nil,其他版本下暂时未发现崩溃。

造成的影响

线上的崩溃数增加了一倍,又提高了整体崩溃率。。。

解决过程

发现问题后,马上对代码进行了修改,增加了非空的判断,本来准备跟下个版本一起发,后来发现崩溃数增长有点快,受影响用户会比较多,所以临时发了个bugfix版本。

槽点

万万想不到啊,这个字段居然可能为空,那这个tm的意义何在,就算我拿到下面的耗时数据了也没有任何用啊。

而且只在13上出现这个问题,果然是bugOS啊。。。

经验教训

以后再给字典赋值时,只要不是基本数据类型,一律进行为空判断,管你是从哪里搞的数据,这个社会除了自己,谁也靠不住啊。

使用高版本的api导致线上崩溃

崩溃描述

这个崩溃是之前做开屏广告时写的一个崩溃,当时是重构之前的代码,在新增计时器时为了图方便,用了个iOS10以后的api,当时开发环境都是10以上的设备,所以没有发现问题。

造成的影响

每天新增几千个崩溃。。。

解决过程

发版后第二天才发现问题,然后马上替换了api,发了bugfix版本。

槽点

这个完全是自己的锅。

经验教训

以后在使用系统api,特别是一个api簇的时候,一定要注意api版本兼容性。

代码重构导致api参数丢失的bug

bug描述

在做用户间送礼功能时,对之前的送礼模块进行了一些重构,将一些重复的代码块进行了封装,提炼出了一些公用的函数,在函数提炼过程中,将之前上下文使用到的数据以参数的形式做了传递,其中有一个函数在传参过程中出现了错误,传了初始数据,导致将处理后的数据丢失了。

造成的影响

由于将收礼人的id丢失了,服务器无法统计当前直播间的收礼情况,导致运营的活动无法正常进行,只能临时换活动方案。

解决过程

这个问题很快就定位到并解决了,刚好跟最新的一个版本发布。

槽点

这已经不是第一次在重构过程中出现线上bug了,如何保证重构代码的正确性我可能需要做一些总结了。
快速的业务迭代和对代码质量的严格要求之间确实存在一个博弈和取舍的问题。当然这也和目前项目人手不足有关系,希望新同学的加入能使现状有所缓解,这样我也有更多的时间和精力去做那些一直想做也是真正对项目有帮助的事情。

经验教训

  • 对重构后的代码块一定要进行覆盖测试

  • 在代码重构过程,特别是函数提炼过程中,一定要注意被提炼的这些代码块在之前的上下文中所使用到的数据,保证传参的正确性

  • 代码重构过程,切记不能偷懒,还是得按重构的标准步骤来,慢工出细活不是没有道理

1…78
lly

lly

耿直的一Boy

76 日志
© 2021 lly
由 Hexo 强力驱动
主题 - NexT.Mist