常见iOS后台技术

本文主要探讨一些常用后台任务的最佳实践。我们将会看看如何并发地使用 Core Data ,如何并行绘制 UI ,如何做异步网络请求等。最后我们将研究如何异步处理大型文件,以保持较低的内存占用。

因为在异步编程中非常容易犯错误,所以,本文中的例子都将使用很简单的方式。因为使用简单的结构可以帮助我们看透代码,抓住问题本质。如果你最后把代码写成了复杂的嵌套回调的话,那么你很可能应该重新考虑自己当初的设计选择了。

操作队列 (Operation Queues) 还是 GCD ?

目前在 iOS 和 OS X 中有两套先进的同步 API 可供我们使用:操作队列GCD 。其中 GCD 是基于 C 的底层的 API ,而操作队列则是 GCD 实现的 Objective-C API。关于我们可以使用的并行 API 的更加全面的总览,可以参见 并发编程:API 及挑战

操作队列提供了在 GCD 中不那么容易复制的有用特性。其中最重要的一个就是可以取消在任务处理队列中的任务,在稍后的例子中我们会看到这个。而且操作队列在管理操作间的依赖关系方面也容易一些。另一面,GCD 给予你更多的控制权力以及操作队列中所不能使用的底层函数。详细介绍可以参考底层并发 API 这篇文章。

扩展阅读:

后台的 Core Data

在着手 Core Data 的并行处理之前,最好先打一些基础。我们强烈建议通读苹果的官方文档 Concurrency with Core Data guide 。这个文档中罗列了基本规则,比如绝对不要在线程间传递 managed objects等。这并不单是说你绝不应该在另一个线程中去更改某个其他线程的 managed object ,甚至是读取其中的属性都是不能做的。要想传递这样的对象,正确做法是通过传递它的 object ID ,然后从其他对应线程所绑定的 context 中去获取这个对象。

其实只要你遵循那些规则,并使用这篇文章里所描述的方法的话,处理 Core Data 的并行编程还是比较容易的。

Xcode 所提供的 Core Data 标准模版中,所设立的是运行在主线程中的一个存储调度 (persistent store coordinator)和一个托管对象上下文 (managed object context) 的方式。在很多情况下,这种模式可以运行良好。创建新的对象和修改已存在的对象开销都非常小,也都能在主线程中没有困难滴完成。然后,如果你想要做大量的处理,那么把它放到一个后台上下文来做会比较好。一个典型的应用场景是将大量数据导入到 Core Data 中。

我们的方式非常简单,并且可以被很好地描述:

Read on →

并发编程:API以及挑战

并发所描述的概念就是同时运行多个任务。这些任务可能是以在单核 CPU 上分时(时间共享)的形式同时运行,也可能是在多核 CPU 上以真正的并行方式来运行。

OS X 和 iOS 提供了几种不同的 API 来支持并发编程。每一个 API 都具有不同的功能和使用限制,这使它们适合不同的任务。同时,这些 API 处在不同的抽象层级上。我们有可能用其进行非常深入底层的操作,但是这也意味着背负起将任务进行良好处理的巨大责任。

实际上,并发编程是一个很有挑战的主题,它有许多错综复杂的问题和陷阱。当开发者在使用类似 Grand Central Dispatch(GCD)或 NSOperationQueue 的 API 时,很容易遗忘这些问题和陷阱。本文首先对 OS X 和 iOS 中不同的并发编程 API 进行一些介绍,然后再深入了解并发编程中独立于与你所使用的特定 API 的一些内在挑战。

OS X 和 iOS 中的并发编程

苹果的移动和桌面操作系统中提供了相同的并发编程API。 本文会介绍 pthreadNSThreadGCDNSOperationQueue,以及 NSRunLoop。实际上把 run loop 也列在其中是有点奇怪,因为它并不能实现真正的并行,不过因为它与并发编程有莫大的关系,因此值得我们进行一些深入了解。

由于高层 API 是基于底层 API 构建的,所以我们首先将从底层的 API 开始介绍,然后逐步扩展到高层 API。不过在具体编程中,选择 API 的顺序刚好相反:因为大多数情况下,选择高层的 API 不仅可以完成底层 API 能完成的任务,而且能够让并发模型变得简单。

如果你对我们为何坚持推荐使用高抽象层级以及简单的并行代码有所疑问的话,那么你可以看看这篇文章的第二部分并发编程中面临的挑战,以及 Peter Steinberger 写的关于线程安全的文章。

线程

线程(thread)是组成进程的子单元,操作系统的调度器可以对线程进行单独的调度。实际上,所有的并发编程 API 都是构建于线程之上的 —— 包括 GCD 和操作队列(operation queues)。

多线程可以在单核 CPU 上同时(或者至少看作同时)运行。操作系统将小的时间片分配给每一个线程,这样就能够让用户感觉到有多个任务在同时进行。如果 CPU 是多核的,那么线程就可以真正的以并发方式被执行,从而减少了完成某项操作所需要的总时间。

Read on →

绘制像素到屏幕

一个像素是如何绘制到屏幕上去的?有很多种方式将一些东西映射到显示屏上,他们需要调用不同的框架、许多功能和方法的结合体。这里我们大概的看一下屏幕之后发生的事情。当你想要弄清楚什么时候、怎么去查明并解决问题时,我希望这篇文章能帮助你理解哪一个 API 可以更好的帮你解决问题。我们将聚焦于 iOS,然而我讨论的大多数问题也同样适用于 OS X。

图形堆栈

当像素映射到屏幕上的时候,后台发生了很多事情。但一旦他们显示到屏幕上,每一个像素均由三个颜色组件构成:红,绿,蓝。三个独立的颜色单元会根据给定的颜色显示到一个像素上。在 iPhone5 的液晶显示器上有1,136×640=727,040个像素,因此有2,181,120个颜色单元。在15寸视网膜屏的 MacBook Pro 上,这一数字达到15.5百万以上。所有的图形堆栈一起工作以确保每次正确的显示。当你滚动整个屏幕的时候,数以百万计的颜色单元必须以每秒60次的速度刷新,这是一个很大的工作量。

软件组成

从简单的角度来看,软件堆栈看起来有点像这样:

软件堆栈

Display 的上一层便是图形处理单元 GPU,GPU 是一个专门为图形高迸发计算而量身定做的处理单元。这也是为什么它能同时更新所有的像素,并呈现到显示器上。它迸发的本性让它能高效的将不同纹理合成起来。我们将有一小块内容来更详细的讨论图形合成。关键的是,GPU 是非常专业的,因此在某些工作上非常高效。比如,GPU 非常快,并且比 CPU 使用更少的电来完成工作。通常 CPU 都有一个普遍的目的,它可以做很多不同的事情,但是合成图像在 CPU 上却显得比较慢。

GPU Driver 是直接和 GPU 交流的代码块。不同的GPU是不同的性能怪兽,但是驱动使他们在下一个层级上显示的更为统一,典型的驱动有 OpenGL/OpenGL ES.

OpenGL(Open Graphics Library) 是一个提供了 2D 和 3D 图形渲染的 API。GPU 是一块非常特殊的硬件,OpenGL 和 GPU 密切的工作以提高GPU的能力,并实现硬件加速渲染。对大多数人来说,OpenGL 看起来非常底层,但是当它在1992年第一次发布的时候(20多年前的事了)是第一个和图形硬件(GPU)交流的标准化方式,这是一个重大的飞跃,程序员不再需要为每个GPU重写他们的应用了。

OpenGL 之上扩展出很多东西。在 iOS 上,几乎所有的东西都是通过 Core Animation 绘制出来,然而在 OS X 上,绕过 Core Animation 直接使用 Core Graphics 绘制的情况并不少见。对于一些专门的应用,尤其是游戏,程序可能直接和 OpenGL/OpenGL ES 交流。事情变得使人更加困惑,因为 Core Animation 使用 Core Graphics 来做一些渲染。像 AVFoundation,Core Image 框架,和其他一些混合的入口。

要记住一件事情,GPU 是一个非常强大的图形硬件,并且在显示像素方面起着核心作用。它连接到 CPU。从硬件上讲两者之间存在某种类型的总线,并且有像 OpenGL,Core Animation 和 Core Graphics 这样的框架来在 GPU 和 CPU 之间精心安排数据的传输。为了将像素显示到屏幕上,一些处理将在 CPU 上进行。然后数据将会传送到 GPU,这也需要做一些相应的操作,最终像素显示到屏幕上。

这个过程的每一部分都有各自的挑战,并且许多时候需要做出折中的选择。

硬件参与者

Read on →

iCould 和 Core Data

当乔布斯第一次在苹果全球开发大会上介绍 iCloud 的时候,他将无缝同步的功能描述的太过完美,以至于让人怀疑其是否真的能实现。但当你在 iOS 5iOS 6 系统中尝试使用 iCloud Core Data 同步的时候你会对其真实情况了如指掌。

库风格应用(译者注:”盒子类型”,比如 iPhoto )的同步中的问题导致很多开发者放弃支持 iCloud,而选择一些其他的方案比如 SimperiumTICoreDataSyncWasabiSync

2013年初,在苹果公司不透明及充满 bug 的 iCloud Core Data 同步实现中挣扎多年后,开发者终于公开批判了这项服务的重大缺陷并将这个话题推上了风口浪尖。 最终被 Ellis Hamburger 在一篇尖锐文章提出。

WWDC

苹果也注意到了,很明显这些事情必须改变。在 WWDC 2013,Nick Gillett 宣布 Core Data 团队花了一年时间专注于在 iOS 7 中解决一些 iCloud 最令人挫败的漏洞,承诺大幅改善问题并且让开发者更简单的使用。“我们明显减少了开发者所需要编写的复杂代码的数量。” Nick Gillett在 [“What’s New in Core Data and iCloud”] 舞台上讲到。 在 iOS 7 中,Apple 专注于 iCloud 的速度,可靠性,和性能,事实上这卓有成效。

让我们看看具体有哪些改变,以及如何在 iOS 7 应用程序实现 Core Data。

设置

要设置一个 iCloud Core Data 应用,你首先需要在你的应用中请求 iCloud 的访问权限,让你的应用程序可以读写一个或多个开放性容器 (ubiquity containers),在 Xcode 5中你可以在你应用 target 的 “Capabilities” 选项卡中轻易完成着这一切。

在开放性容器内部,Core Data Framework 将会存储所有的事务日志 – 记录你的所有持久化的存储 – 为了跨设备同步数据做准备。 Core Data 使用了一个被称为多源复制(multi-master replication)的技术来同步 iOS 和 Macs 之间的数据。可持久化存储的数据存在了每个设备的 CoreDataUbiquitySupport 文件夹里,你可以在应用沙盒中找到他。当用户修改了 iCloud accounts,Core Data framework 会管理多个账户,而并不需要你自己去监听NSUbiquityIdentityDidChangeNotification

每一个事务日志都是一个plist文件,负责实体的跟踪插入,删除以及更新。这些日志会自动被系统按照一定基准合并。

在你设置iCloud的持久化存储的时候,调用

addPersistentStoreWithType:configuration:URL:options:error:

或者

migratePersistentStore:toURL:options:withType:error:的时候注意需要设置一些选项:
Read on →

一个少年

我要是没有生在这个世界就好了 受伤的心灵如此诉说

要是这么想的话就中了那个混蛋的当了 不要认输呀少年

「让内心休息一下」这种事 看来是不会被允许的呢

略微玩耍一下 把该做的事都置于身后吧 选择自己喜欢的事情吧

在这双手伸出之前 还有着无尽的喜悦

那无穷无尽而来的悲伤 如果无法背负的话就不背好了

为何要去勉强自己去感受痛苦呢? 本来谁都可以自由自在

像那振翅翱翔的鸟儿一般 像那海面中跳跃的飞鱼一般

在这欢乐的地方 比谁都能 自由微笑的少年吧

如果穿上流行的服饰的话 明天也会变得落伍吧

要不要去赶潮流 选择自己喜欢的方法就好了

在这双脚踏出路程之前 还有着无限的幸福存在

如果有着无尽的烦恼的话 那就让他烦恼下去好了

欲速则不达 不要不懂装懂 每个人都有缺陷

自己诞生而来的意义 即使最后再想也是绝妙的

微风轻轻拂过 将身心委任给涓涓小溪 成为一个学习的少年吧

成为在这个世界上 比谁都能 自由恋爱的少年吧

精简开发iOS

View controllers 通常是 iOS 项目中最大的文件,并且它们包含了许多不必要的代码。所以 View controllers 中的代码几乎总是复用率最低的。我们将会看到给 view controllers 瘦身的技术,让代码变得可以复用,以及把代码移动到更合适的地方。

把 Data Source 和其他 Protocols 分离出来

UITableViewDataSource 的代码提取出来放到一个单独的类中,是为 view controller 瘦身的强大技术之一。当你多做几次,你就能总结出一些模式,并且创建出可复用的类。

举个例,在示例项目中,有个 PhotosViewController 类,它有以下几个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# pragma mark Pragma

- (Photo*)photoAtIndexPath:(NSIndexPath*)indexPath {
    return photos[(NSUInteger)indexPath.row];
}

- (NSInteger)tableView:(UITableView*)tableView
 numberOfRowsInSection:(NSInteger)section {
    return photos.count;
}

- (UITableViewCell*)tableView:(UITableView*)tableView
        cellForRowAtIndexPath:(NSIndexPath*)indexPath {
    PhotoCell* cell = [tableView dequeueReusableCellWithIdentifier:PhotoCellIdentifier
                                                      forIndexPath:indexPath];
    Photo* photo = [self photoAtIndexPath:indexPath];
    cell.label.text = photo.name;
    return cell;
}

这些代码基本都是围绕数组做一些事情,更针对地说,是围绕 view controller 所管理的 photos 数组做一些事情。我们可以尝试把数组相关的代码移到单独的类中。我们使用一个 block 来设置 cell,也可以用 delegate 来做这件事,这取决于你的习惯。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@implementation ArrayDataSource

- (id)itemAtIndexPath:(NSIndexPath*)indexPath {
    return items[(NSUInteger)indexPath.row];
}

- (NSInteger)tableView:(UITableView*)tableView
 numberOfRowsInSection:(NSInteger)section {
    return items.count;
}

- (UITableViewCell*)tableView:(UITableView*)tableView
        cellForRowAtIndexPath:(NSIndexPath*)indexPath {
    id cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier
                                              forIndexPath:indexPath];
    id item = [self itemAtIndexPath:indexPath];
    configureCellBlock(cell,item);
    return cell;
}

@end
Read on →

你真的懂GCD吗?

GCD是什么,你知道吗?你知道了GCD,你确定你会使用吗?

这一篇文章是站在初学者角度去分析GCD,原因是这个很多iOS开发者根本就没用过,即使用过,不知道其中的原理。讲解之前认识一下什么是线程,为什么要介绍线程。是因为GCD是Grand Central Dispatch的缩写,是一系列的BSD层面的接口,在Mac 10.6 和iOS4.0以后才引入的,且现在NSOperation和NSOperationQueue的多线程的实现就是基于GCD的。目前这个特性也被移植到 FreeBSD上了,可以查看libdispatch这个开源项目。

iPhone中的线程应用并不是无节制的,官方给出的资料显示iPhone OS下的主线程的堆栈大小是1M,第二个线程开始都是512KB。并且该值不能通过编译器开关或线程API函数来更改。只有主线程有直接修改UI的能力。

一、线程的概述


有些程序是一条直线,起点到终点;有些程序是一个圆,不断循环,直到将它切断。直线的如简单的Hello World,运行打印完,它的生命周期便结束了,像昙花一现那样;圆如操作系统,一直运行直到你关机。

一个运行着的程序就是一个进程或者叫做一个任务,一个进程至少包含一个线程,线程就是程序的执行流。Mac和iOS中的程序启动,创建好一个进程的同时, 一个线程便开始运行,这个线程叫主线程。主线程在程序中的地位和其他线程不同,它是其他线程最终的父线程,且所有界面的显示操作即AppKit或 UIKit的操作必须在主线程进行。

系统中的每一个进程都有自己独立的虚拟内存空间,而同一个进程中的多个线程则共用进程的内存空间。每创建一个新的线程,都需要一些内存(如每个线程有自己的Stack空间)和消耗一定的CPU时间。另外当多个线程对同一个资源出现争夺的时候需要注意线程安全问题。

二、创建线程

创建一个新的线程就是给进程增加了一个执行流,执行流总得有要执行的代码吧,所以新建一个线程需要提供一个函数或者方法作为线程的入口。

1.使用NSThread

NSThread提供了创建线程的途径,还可以提供了检测当前线程是否是主线程的方法。 使用NSThread创建一个新的线程有两种方式:

  • 1.创建一个NSThread的对象,调用其start方法。对于这种方式的NSThread对象的创建,可以使用一个目标对象的方法初始化一个NSThread对象,或者创建一个继承NSThread类的子类,实现其main方法,然后在直接创建这个子类的对象。
  • 2.使用 detachNewThreadSelector:toTarget:withObject:这个类方法创建一个线程,这个比较直接了,直接使用目标对象的方法作为线程启动入口。

2.使用NSObject

其实NSObject直接就加入了多线程的支持,允许对象的某个方法在后台运行。如:

1
[myObj performSelectorInBackground:@selector(doSomething) withObject:nil];

3.POSIX Thread

Read on →

iOS设备信息收集

收集iOS设备的信息,包含设备系统版本,设备型号,手机型号,屏幕分辨率,当地所在时区,CPU 型号,系统语言,网络环境,应用名称以及应用版本,还有判断设备是否,应用是否被破解

需要的头文件

1
2
3
4
5
6
7
8
9
10
11
#import "ISSMobileClick.h"
#import "AppInfoModel.h"
#import <CoreLocation/CoreLocation.h>
#import <CoreTelephony/CTCarrier.h>
#import <CoreTelephony/CTTelephonyNetworkInfo.h>
#import <objc/runtime.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <mach/machine.h>
#include <sys/types.h>
#import "ITTNetworkTrafficManager.h"

实现方法,我使用类方法方便调用,无需实例化,直接类调用

Read on →

Markdown 入门新手指南

作为一款「写作软件」在诞生之初就支持了 Markdown,Markdown 是一种「电子邮件」风格的「标记语言」,我强烈推荐所有写作者学习和掌握该语言。

在此,总结 Markdown 的优点如下:

  • 纯文本,所以兼容性极强,可以用所有文本编辑器打开。
  • 让你专注于文字而不是排版。
  • 格式转换方便,Markdown 的文本你可以轻松转换为 html、电子书等。
  • Markdown 的标记语法有极好的可读性。

当然,既然如此推崇 Markdown ,也必定会教会你使用 Markdown ,这也是本文的目的所在。不过,虽然 Markdown 的语法已经足够简单,但是现有的 Markdown 语法说明更多的是写给 web 从业者看的,对于很多写作者来说,学习起来效率很低,现在,特地为写作者量身定做本指南,从写作者的实际需求出发,介绍写作者真正实用的常用格式,深入浅出、图文并茂地让您迅速掌握 Markdown 语法。

为了使您更好地学习,我们建议您登录,将您的编辑器切换至 Markdown 编辑器,新建一篇空白笔记,然后点击右上角的预览模式,此时,您的界面应当如下图所示,左侧为编辑区域,右侧为预览区域,您在左侧输入 Markdown 语法的文本,右侧会立即帮您呈现最终结果,好了,让我们开始学习吧~

Markdown:

标题

这是最为常用的格式,在平时常用的的文本编辑器中大多是这样实现的:输入文本、选中文本、设置标题格式。

而在 Markdown 中,你只需要在文本前面加上 # 即可,同理、你还可以增加二级标题、三级标题、四级标题、五级标题和六级标题,总共六级,只需要增加 # 即可,标题字号相应降低。例如:

# 一级标题
## 二级标题
### 三级标题
#### 四级标题
##### 五级标题
###### 六级标题

注:# 和「一级标题」之间建议保留一个字符的空格,这是最标准的 Markdown 写法。

你可以你的编辑器中尝试输入这六级标题,可以参考下方的截图:
一级标题至六级标题

列表

Read on →

教程:一步步在github上建立octopress博客

目标

创建网址为http://itmonkeylife.github.io/Blog的博客.博客使用octopress程序,搭建在github的服务器上.网页与本地机器的Blog文件夹同步.

步骤

0) 配置环境

octopress需要ruby和git支持. 因此我们需要先安装这两个程序. 这儿分windows和Mac分别讲下.

Windows(简略说下,详细教程请搜索):

下载并安装git:

http://git-scm.com/download

下载并安装Ruby和开发组件(DEVELOPMENT KIT),共两个文件,Ruby推荐1.9.3版,对octopuses支持很好:

http://rubyinstaller.org/downloads/

Mac:

Mac本身自带了git,因此不需要单独安装.

Octopress需要带openssl组件的ruby支持.

但新版本的ruby似乎编译对参数--with-openssl-dir=my/openssl/dir支持有问题, 因此推荐使用rvm安装管理多版本的Ruby, rvm默认安装Ruby 2.0版本, 这儿为了不发生兼容性问题, 除了默认的2.0, 再安装个带openssl的1.9.3版.

  • 确认Xcode和其自带的命令行工具已经安装.

    如没有,Xcode可在App Store免费下载.命令行工具可以在Xcode- Preferences - Download 中 点击安装 Command Line Tools.

  • 安装rvm, rvm是个软件管理工具,和Linux上的apt-get,yum之类类似.

sudo curl -L https://get.rvm.io | bash -s stable

在~/.bash_profile文件末尾添加:

echo '[[ -s "$HOME/.rvm/scripts/rvm" ]] && . "$HOME/.rvm/scripts/rvm"

加载下配置文件:

source ~/.bash_profile
  • 安装1.93版支持openssl的Ruby.
rvm install 1.9.3 --with-gcc=clang ----with-openssl-dir=$HOME/.rvm/usr
rvm use 1.9.3 --default

1) 下载octopress至本地Blog文件夹

octopress的源代码同样在github上管理,因此我们使用git克隆一份到本地的Blog文件夹,并进入此文件夹. 如你没有域名,可以使用github提供的默认域名:yourname.github.com,yourname是你github的用户名.

git clone git@github.com:ITMonkeyLife/Blog.git ITMonkeyLife/Blog
cd ITMonkeyLife/Blog

2) 安装相关组件

Read on →