Skip to content

Instantly share code, notes, and snippets.

@wangzz
Created August 12, 2014 07:54
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wangzz/90a57629143d6d38f0b8 to your computer and use it in GitHub Desktop.
Save wangzz/90a57629143d6d38f0b8 to your computer and use it in GitHub Desktop.
记录遇到的小的知识点。
记录遇到的小的知识点。
@wangzz
Copy link
Author

wangzz commented Aug 12, 2014

iOS7多任务学习笔记

苹果在iOS7中对后台任务真是下足了功夫,带来了不少变化,体现在以下几点:

  • 改变了后台任务的运行方式
  • 增加了后台获取(Background Fetch)
  • 增加了推送唤醒(静默推送,Silent Remote Notifications)
  • 增加了后台传输(Background Transfer Service)

一、后台任务执行方式

1、iOS6(及之前)

在应用被切到后台之后,如果有后台任务的话,应用会有 10mins 左右的时间在后台执行该任务,直到超时以后才进入低功耗休眠状态,如下图:

iOS6 Background Task

2、iOS7(及之后)

应用被切到后台后,将会有 3mins 左右的时间执行后台任务,然后将处于suspend状态。在设备于特定时刻(比如检查邮件或者有来电等)被唤醒时,之前暂停的任务会被继续执行。

也就是说,iOS7会在应用进入后台后尽快让设备进入休眠状态,以节省电力;系统不会专门为第三方应用保持设备活动状态,只会在适当的时候重新唤醒任务。

iOS7 Background Task

对于Voip位置服务等后台长时应用,在应用被切到后台以后会处于suspend状态,当有socket消息或者位置更新时,会被唤醒重新执行任务。

二、后台获取

很多应用都是在用户打开应用以后才进行网络请求,这就造成了每次启动应用都有一个等待时间,iOS7中的后台获取就是为了解决这个不足的:用户在打开app之前让应用有机会获取数据并刷新UI,这样在用户启动应用后立即显示最新内容,这是很好的用户体验。

1、启用后台获取

通过Info.plist添加Background fetch选项

2、设定时间间隔

可以通过以下方式设定系统进行后台获取的时间间隔:

[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];

需要注意的是:

  • 如果不设置该值,系统将使用默认值UIApplicationBackgroundFetchIntervalNever
  • 就算设置了值,并不是就一定会间隔设定的时间就进行一次后台获取,而是系统会根据设定的时间间隔值在方便的时候(比如检查邮件)进行一次后台获取;

我们需要根据自己应用的实际需要设定一个合理的下载时间间隔。

3、实现相关方法

完成上述两步后需要在AppDelegate里实现下述方法:

//File: YourAppDelegate.m
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
    UINavigationController *navigationController = (UINavigationController*)self.window.rootViewController;

    id fetchViewController = navigationController.topViewController;
    if ([fetchViewController respondsToSelector:@selector(fetchDataResult:)]) {
        [fetchViewController fetchDataResult:^(NSError *error, NSArray *results){
            if (!error) {
                if (results.count != 0) {
                    //Update UI with results.
                    //Tell system all done.
                    completionHandler(UIBackgroundFetchResultNewData);
                } else {
                    completionHandler(UIBackgroundFetchResultNoData);
                }
            } else {
                completionHandler(UIBackgroundFetchResultFailed);
            }
        }];
    } else {
        completionHandler(UIBackgroundFetchResultFailed);
    }
}

系统会通过调用该方法实现后台获取功能。

开发者在该方法里要做的事就是完成下载工作、刷新UI,并通知系统获取结束,以便系统尽快回到休眠状态。

通常一次后台获取的执行时间小于一分钟。

通知系统结束的CompletionHandler会接收一个UIBackgroundFetchResult参数,该enum的定义如下:

typedef NS_ENUM(NSUInteger, UIBackgroundFetchResult) {
    UIBackgroundFetchResultNewData,
    UIBackgroundFetchResultNoData,
    UIBackgroundFetchResultFailed
} NS_ENUM_AVAILABLE_IOS(7_0);

分别表示获取到了新数据(此时系统将对现在的UI状态截图并更新App Switcher中你的应用的截屏),没有新数据,以及获取失败。

另一个比较神奇的地方是系统将追踪用户的使用习惯,并根据对每个应用的使用时刻给一个合理的fetch时间。比如系统将记录你在每天早上9点上班的电车上,中午12点半吃饭时,以及22点睡觉前会刷一下微博,只要这个习惯持续个三四天,系统便会将应用的后台获取时刻调节为9点,12点和22点前一点。这样在每次你打开应用都直接有最新内容的同时,也节省了电量和流量。

4、后台获取调试方法

Xcode5提供了两种测试后台获取的方法:

  • 从后天获取中启动应用

点击Xcode5的Product->Scheme->Edit Scheme(或者直接使用快捷键⌘<)。在编辑Scheme的窗口中点Duplicate Scheme按钮复制一个当前方案,然后在新Scheme的option中将Background Fetch打上勾。如下图所示:

image

从这个Scheme来运行应用的时候,应用将不会直接启动切入前台,而是调用后台获取部分代码并更新UI,这样再点击图标进入应用时,你应该可以看到最新的数据和更新好的UI了。

  • 直接模拟一次后台获取

在app调试运行时,点击Xcode5的Debug菜单中的Simulate Background Fetch,即可模拟完成一次获取调用。

三、推送唤醒

在iOS6和之前,用户收到推送消息通过解锁进入你的应用后,appDelegate中通过推送打开应用的回调将被调用,然后你再获取数据,进行显示。

在iOS7中这个行为发生了一些改变,我们有机会使设备在接收到远端推送后让系统唤醒设备和我们的后台应用,并先执行一段代码来准备数据和UI,然后再提示用户有推送。这时用户如果解锁设备进入应用后将不会再有任何加载过程,新的内容将直接得到呈现,这就解决了用户等待问题。

1、启用推送唤醒

在Info.plist的UIBackgroundModes中加入remote-notification

2、更改推送payload

在iOS7中,要想使用推送来唤醒应用运行代码的话,需要在payload中加入content-available,并设置成1。

aps {  
     content-available: 1
     alert: {...}
   }

3、实现相关方法

在AppDelegate中实现-application:didReceiveRemoteNotification:fetchCompletionHandle:即可,类似后台获取部分。

需要说明的是,Apple会限制推送频率。当频率超过一定限制后,带有content-available标志的推送将会被阻塞,以保证用户设备不被频繁唤醒。按照Apple的说法,这个频率在一小时内个位数次的推送的话不会有太大问题。

四、后台传输

iOS6和之前,iOS应用在大块数据的下载这一块限制是比较多的:只有应用在前台时能保持下载(用户按Home键切到后台或者是等到设备自动休眠都可能中止下载),在后台只有很短的最多十分钟时间可以保持网络连接。如果想要完成一个较大数据的下载,用户将不得不打开你的app并且基本无所事事。

iOS7引入了后台传输的相关方式,用来保证应用退出后数据下载或者上传能继续进行。这种传输是由iOS系统进行管理的,没有时间限制,也不要求应用运行在前台。

想要实现后台传输,就必须使用iOS7的新的网络连接的类,NSURLSession,这是iOS7中引入用以替代陈旧的NSURLConnection的类。

通过NSURLSession开始后台下载后,需要在AppDelegate中实现-application:handleEventsForBackgroundURLSession:completionHandler:方法,以刷新UI及通知系统传输结束。

需要说明的是:

  • NSURLSession会首先将下载的内容写到缓存里,等待下载结束时会调用- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location;,这是我们需要将下载完成的文件移到指定的位置;
  • 如果下载过程中应用意外退出,会在下次实例化NSURLSession对象的时候调用- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error;方法,以便取出上次下载的文件继续下载,以实现断点续传,而不是从头再来。

五、参考文档

@wangzz
Copy link
Author

wangzz commented Aug 12, 2014

frame/bounds/center/anchorPoint/position

一、view属性

1、 frame

相对父坐标系。

计算公式:

- (CGRect)frame
{
    float x = center.x - layer.anchorPoint.x * bounds.size.width;
    float y = center.y - layer.anchorPoint.y * bounds.size.height;
    float width = bounds.size.width;
    float height = bounds.size.height;
    return CGRectMake(x, y, width, height);
}

- (void)setFrame:(CGRect) rect
{
    center.x = rect.origin.x + layer.anchorPoint.x * rect.size.width;
    center.y = rect.origin.y + layer.anchorPoint.y * rect.size.height;
    bounds.size.width = rect.size.width;
    bounds.size.height = rect.size.height;
}

由此可见:

更改center、anchorPoint、bounds的值都会是frame的值发生对应改变;

更改frame的值会是bounds的值发生对应改变;

2、 bounds

相对本地坐标系。

2.1 origin

更改bounds的origin会改变当前view的子view的位置,具体原因见下图所示:

bounds

参考链接:http://www.xiaoyaoli.com/?p=1182

2.2 size

更改frame的时候会改变bounds的size,同样更改bounds的size的时候也会改变frame的size;

frame.size和bounds.size不一定能完全一致,比如在view旋转的时候,详情见下图:

coordinates

参考链接:http://stackoverflow.com/questions/5361369/uiview-frame-bounds-and-center

3、 center

相对父坐标系。

更改frame的时候会改变center,同样更改center的时候也会改变frame的origin。

二、layer属性

1、 frame

对应view的frame

2、 bounds

对应view的bounds

3、 anchorPoint

默认值是(0.5,0.5);

是旋转、缩放等操作的中心点;

更改anchorPoint会改变frame的origin,但反过来更改frame不会改变anchorPoint的值。

4、 position

和center永远保持一致

@wangzz
Copy link
Author

wangzz commented Aug 13, 2014

字符编码

一、ASCLL

全称是American Standard Code for Information Interchange(美国信息交换标准代码);

单字节编码,用一个字节(8个位)来表示一个字符,并保证最高位的取值永远为’0’;

最多表示2^7 =128个字符,这128个字符包括95个可打印的字符(涵盖了26个英文字母的大小写以及英文标点符号能)与33个控制字符(不可打印字符);

二、中文编码

名称 GB2312 BIG5 GBK GB18030
作用 国家简体中文字符集 统一繁体字符集 GB2312的扩展,加入对繁体的支持 中日韩文字编码
字节数 1字节-兼容ASCII 2字节-汉字 2字节 2字节 1字节-兼容ASCII 2字节,4字节
范围 7445个符号,包括6763个汉字 21886个符号 21886个符号 27484个符号
兼容性 00-7F范围内是1位,和ASCII兼容 兼容ASCII,但与GB2312冲突 兼容GB2312 兼容GB2312

三、Unicode

采用4个字节表示一个字符,这样理论上Unicode能表示的字符数就达到了2^31 = 2147483648 = 21 亿左右个字符,完全可以涵盖世界上一切语言所用的符号。

存在两个问题:

  • Unicode对所有的字符编码均需要四个字节,而这对于拉丁字母或汉字来说是浪费的,其前面三个或两个字节均是0,这对信息存储来说是极大的浪费。

*另外一个问题就是,如何区分Unicode与其它编码,比如计算机怎么知道四个字节表示一个Unicode中的字符,还是分别表示四个ASCII的字符呢?

以上两个问题,让Unicode的推广上一直面临着困难。

四、UTF-8

UTF-8是Unicode的一种实现方式,而Unicode是一个统一标准规范,Unicode的实现方式除了UTF-8还有其它的,比如UTF-16等。

UTF-8在Unicode的基础上又两个基本规则:

  • 对于单字节字符,字节的第一位为0,后7位为这个符号的Unicode码,所以对于拉丁字母,
    UTF-8与ASCII码是一致的。
  • 对于n字节(n>1)的字符,第一个字节前n位都设为1,第n+1位为0,后面字节的前两位一律设为10,
    剩下没有提及的位,全部为这个符号的Unicode编码。

比如:

Unicode(十六进制) UTF-8(二进制)
单字节:0000 0000-0000 007F 0xxxxxxx
双字节:0000 0080-0000 07FF 110xxxxx 10xxxxxx
三字节:0000 0800-0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
四字节:0001 0000-0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

举例来说,’微’的Unicode是’\u5fae’,二进制表示是”00000000 00000000 01011111 10101110“,其取值就位于’0000 0800-0000 FFFF’之间,所以其UTF-8编码为’11100101 10111110 10101110’

需要注意的一点是Unicode(UTF-8)与GBK,GB2312这些汉字编码规则是完全不兼容的,也就是说这两者之间不能通过任何算法来进行转换,如需转换,一般通过GBK查表的方式来进行。

五、参考文档

@wangzz
Copy link
Author

wangzz commented Aug 15, 2014

ssh远程编译访问证书报User interaction is not allowed.错误解决方案

在编译前解锁keychain:

security show-keychain-info ~/Library/Keychains/login.keychain
if [[ $? != 0 ]]; then
    security unlock-keychain ~/Library/Keychains/login.keychain
    if [[ $? != 0 ]]; then
        echo
        echo "!!! unlock keychain failed"
        exit 4
    fi
fi

通常这就能解决问题了,如果还不行,就将keychain对应证书的访问权限改成下图所示:
keychain
参考链接:http://stackoverflow.com/questions/20205162/user-interaction-is-not-allowed-trying-to-sign-an-osx-app-using-codesign

@wangzz
Copy link
Author

wangzz commented Aug 18, 2014

Xcode Concepts

  • Xcode Target

Target是project或者workspace中通过一系列文件去定义一个product的编译方式的最小单位。它将product的组成部分:源代码和编译说明文件组织在一起,以供编译器使用。一个project能包含若干个target,但是一个target只有一个product(编译结果)。

编译说明文件有两种形式:build settings 和 build phases(构建阶段)。Target的build settings是从 project中继承的,但是可以在target对应的build settings中重写那些继承项;但是build phases则是每个Target独有的。

每次只能通过Xcode scheme指定唯一一个活跃的target。

target之间可以相互关联,在同一个workspace中Xcode能自动处理target之间的依赖关系,这被称为隐式依赖;也可以指定target之间的显示依赖关系。

  • Xcode Project

Xcode project包含了编译product所需的全部代码文件、资源文件、设置项。

Project可以包含若干个target。

Project会为所有Target指定一个默认的build settings选项。

  • Xcode workspace

workspace可以包含若干个project以及 其它的文档。

  • Xcode Scheme

Xcode scheme 是若干可编译的target、编译时的一些设置以及若干测试用例的集合。

scheme可以有很多个,但是只能有一个处于活跃状态。

scheme可以属于project,也可以属于workspace。

@wangzz
Copy link
Author

wangzz commented Aug 18, 2014

Apple关于deprecated方法的描述:

Deprecation does not mean the immediate deletion of an interface from a framework or library. It is simply a way to flag interfaces for which better alternatives exist. You can use deprecated APIs in your code. However, Apple recommends that you migrate to newer interfaces as soon as possible because deprecated APIs may be deleted from a future version of the OS. Check the header files or documentation of the deprecated API for information about any recommended replacement interfaces.

deprecated并不意味着该方法会立马被删除,而是它有了更好的替代方法。开发者需要尽快将标为deprecated的方法替换掉,因为Apple可能在将来的某个系统版本中会将其删除。

@wangzz
Copy link
Author

wangzz commented Aug 19, 2014

Base SDK and Deployment Target Settings

  • Deployment Target

该设置项标识软件支持的最低系统版本。Deployment Target和比它早的系统的特性都可以无条件使用。

  • Base SDK

标识应用能支持的最高系统版本。默认情况下Xcode会将该选项设置为当前最新的系统版本。

二者之间的关系如图所示:

Deployment Target and Base SDK

PS:

  • iOS或OS X 的SDK中的libraries仅仅用于链接,它们本身并不包含任何可执行文件。SDK只有和编译目标机器配合才能正常工作。
  • 当使用Simulator SDKs编译的时候,生成的二进制文件只使用Base SDK指定的系统版本,即和Deployment Target指定的系统版本没有直接关系。

@wangzz
Copy link
Author

wangzz commented Aug 27, 2014

Mac,iOS界面中的坐标系

话说Mac,iOS中的各种坐标系总会让初学者摸不着头脑,一会儿这样一会儿那样。不过有一点是不变的,z轴的正方向总是指向观察者,也就是垂直屏幕平面向上。

1.NSView坐标系
在Mac中NSView的坐标系默认是右手坐标系(View其实是二维坐标系,但是为了方便我们可以假设其是三维坐标系,只是所有界面的变化都是在xy平面上),原点在左下角. NSView提供了一个可以用于覆盖的方法

- (BOOL)isFlipped;

此默认返回NO,当返回YES的时候,则坐标系变成左手坐标系,坐标原点变成左上角。

在Mac的AppKit中有很多界面组件本身就使用了Flipped Coordinate System(覆盖了上面的方法并返回YES),如NSButton,NSTableview,NSSplitView 更详细的看这里 其中Cocoa Use of Flipped Coordinates 这一节 https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CocoaDrawingGuide/Transforms/Transforms.html

2.UIView坐标系
而在iOS的UIView中,则没有所谓的Flipped Coordinate的概念,统一使用左手坐标系,也就是坐标原点在左上角.

3.Quartz坐标系
Quartz(Core Graphics)坐标系使用的右手坐标系,原点在左下角,所以所有使用Core Graphics画图的坐标系都是右手坐标系,当使用CG的相关函数画图到UIView上的时候,需要注意CTM的Flip变换,要不然会出现界面上图形倒过来的现象。由于UIKit的提供的高层方法会自动处理CTM(比如UIImage的drawInRect方法),所以无需自己在CG的上下文中做处理。 参见Quartz 2D Coordinate Systems

4.CALayer坐标系
这个有些变态了,其坐标系和平台有关,在Mac中CALayer使用的是右手坐标系,其原点在左下角;iOS中使用的左手坐标系,其原点在左上角。 参见 Layer Coordinate System

引至:http://geeklu.com/2012/06/3d-coordinate-system/

@wangzz
Copy link
Author

wangzz commented Feb 10, 2015

hit-test

  • 流程

hit-test使用的是逆序递归遍历法:

It implements it by searching the view hierarchy using reverse pre-order depth-first traversal algorithm.

比如window上先后添加了A、B、C三个view:
traversal

那么屏幕上任何一点发生触摸事件时都会按如下顺序遍历:

** C 如果在C上继续递归遍历C的subview,不在C上则遍历B
** B 如果在B上继续递归遍历B的subview,不在B上则遍历A
** A 如果在A上继续递归遍历A的subview,不在A上则遍历A的上一级

直到找到一个包含点击区域的最上层view。

  • 关于多次调用hit-test

** iOS6 及之前系统会重复调用三次hit-test流程;
** iOS7 及之后系统会重复调用两次hit-test流程。

调用那么多次的原因不明。

  • 关于UITabbarController

点击包含UITabBar、UIToolBar等控件上方40像素区域内时,多次hit-test的最后一次中point值会发生随机变化,point的Y值会被增大若干不等的像素。

原因未知。

参考链接: Hit-Testing in iOS

@wangzz
Copy link
Author

wangzz commented Apr 13, 2015

升级Xcode插件失效解决办法

  • 查看Xcode对应版本的UUID:
tail -f /var/log/system.log
  • 更新插件配置文件

得到UUID后执行命令:

find ~/Library/Application\ Support/Developer/Shared/Xcode/Plug-ins -name Info.plist -maxdepth 3 | xargs -I{} defaults write {} DVTPlugInCompatibilityUUIDs -array-add XCode_UUID
  • 参考链接:

XCode升级后插件失效的原理与修复办法

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment