App Programming Guide for iOS

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

后台执行

一、短时间的后台执行

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 –