一个应用程序扩展不是一个应用,它在主体应用程序中单独打包并生成单独的二进制文件。应用程序扩展不同于一个应用程序,它的功能相对于应用来说更加明确、单一,并且要严格遵循扩展点的协议。

应用程序扩展的生命周期

因为一个应用程序扩展不是一个应用,所以它的生命周期和运行环境也不同于应用。一般情况来说,应用程序扩展的生命周期开始于用户从某个应用中使用它。这个应用能够让用户选择某一种扩展来实现某个特定功能或任务,这种应用我们称为扩展载体应用。当扩展载体应用给扩展发送一个请求,让扩展代替响应用户的操作时,应用扩展就会被激活并意味着生命周期也随之开始。当应用扩展完成载体应用发送的请求任务后,该扩展的生命周期也随之结束。

比如说,有这样一个场景,用户在扩展载体应用中选中一段文字,并点击分享按钮,从分享列表中选择某一个分享扩展,将选中的内容分享至社区网站。此时,扩展载体应用就会启动该扩展,让它去响应用户的请求,即分享所选文本的内容。应用扩展基本的生命周期归纳在下图2-1中。
生命周期

在图2-1中的第2步中,系统实例化扩展载体应用中定义好的扩展,并建立载体应用于扩展之间的通信通道。然后扩展会根据上下文场景显示相关的界面,并使用载体应用接收到的信息执行用户的操作(在上述的例子中,分享扩展就使用用户选中的文本信息来执行相关操作)。

在图2-1的第3步中,用户在扩展中执行或取消某个任务,或者直接可以忽略该扩展。在对用户的操作做出响应的过程中,扩展会根据载体应用的请求立即开始执行任务,但如果有特殊需要时,扩展也会在系统后台去执行任务,这种情况下,扩展不会显示其相关界面,并且载体应用会显示用户操作扩展前的界面。当应用扩展执行完任务后,不管是返回还是延迟返回,但最后其执行结果都要返回给载体应用。

当扩展开始执行任务之后(或者开始在系统后台执行任务),系统就会终止扩展,比如图2-1中的第4步。

应用扩展是如何进行通信的

当应用扩展在运行的时候,它会直接和载体应用进行通信。但是应用扩展不会和调用它的应用进行直接通信,因为有可能应用扩展在运行,但是调用它的应用程序并没有运行。另外还有一点,扩展载体应用和调用扩展应用之间是绝对不会进行通信的。图2-2中描述了应用扩展、载体应用、调用扩展应用之间的关系。
关系

当一个扩展需要和调用它的应用进行通信时,只能间接的通过扩展载体应用提供的上下文进行。比如说,一个应用扩展有可能会运行调用它的应用程序。在这个例子中,应用扩展不能通过API直接和调用它的应用传递消息。但是,应用扩展和调用它的应用可以共同访问一个定义为私有的共享容器。在图2-3中描述了扩展和调用扩展应用之间通信的模式。
通信

注意:在底层,系统使用进程间通信来确保应用扩展和扩展载体应用做到无缝结合。在你的代码中,你从来不会考虑这个通用的通信机制。那是因为我们使用的扩展API是扩展点和系统已经高度封装后的。

原文地址:Understand How an Extension Works

重要提示:
这个文档只是一个使用API或了解技术的初级文档。Apple提供该文档的目的在于能让开发者尽快熟悉新技术和新的API,用于开发者针对其持有的设备进行开发。但是该文档会不断的更新,因为目前较新的技术和语言还在不断的完善,所以使用该文档提供的技术开发的软件不一定是最终的操作系统所能完全支持的,要时刻关注该文档的更新,以便了解新的API和技术特性。

当iOS8.0和OS X v10.10来临后,一个全新的概念出现在我们眼前,那就是应用扩展。顾名思义,应用扩展允许开发者对应用的功能进行扩展,究其根本的作用就是通过应用扩展能够让用户在使用别的应用时可以快速便捷的了解该应用的相关信息。你可以开发一个应用扩展,用来执行某些特定的任务,当用户使用该扩展后就可以在各种情况下去执行该任务。比如说,你开发了一款分享应用扩展,他可以让用户快速的将当前浏览的内容分享到你的社区网站。再比如说,你开发了一款实时显示比赛当前分数的扩展,那么用户就可以在通知中心的当天通知中看到比赛比分。你甚至可以开发一款自定义的键盘,可以让用户用它来替代系统自带的键盘。

应用扩展的类型

iOS和OS X定义了几种应用扩展的类型,每一种类型的扩展都对应系统中一块展示区域,像分享、通知中心、键盘等。我们把这些区域成为扩展点。当你开发某一个扩展点的应用扩展时,都有相对应的API以及要遵循的策略。还要注意的一点是,当你针某个扩展点开发应用扩展时,该应用扩展的功能必须要符合该扩展点的功能特性。

表1-1列出了iOS和OS X中的扩展点,并列举了对应扩展点的应用示例。


































扩展点 应用扩展示例
今日通知(iOS和OS X) 在通知中心的今日通知中快速查看或处理任务。(这个扩展点中的应用扩展称为Widget)
分享(iOS和OS X) 将网页内容或其他内容分享给他人。
Action(iOS和OS X) 处理或查看其他应用中的信息。
照片编辑(iOS) 在Photos应用中编辑照片或视频。
Finder(OS X) 在Finder中直接显示文件的同步信息等。
文本管理(iOS) 提供用于查看和管理文件的文本库。
自定义键盘(iOS) 用于替换系统自带的键盘。

由于系统定义了扩展特定领域,所以当你在开发应用扩展时,其功能要适用于所有列出的这些扩展点。比如说你要开发一个分享信息的应用扩展,那么你只能选择分享扩展点,而不能使用今日通知或Action扩展点。

重要提示:你开发的应用扩展只可能满足于某一个扩展点,你不能开发一个类似通用的应用扩展去满足所有的扩展点。

Xcode和应用商店帮助你创建并提交应用扩展

应用扩展不同于应用,虽然你需要基于应用去开发应用扩展,但是每个应用扩展都是独立于应用运行的二进制文件。

当你要创建一个应用扩展时,需要在项目中创建一个新的Target。和其他Target一样,扩展Target将设置信息和相关文件打包在Products文件下生成一个扩展名为.appex的包。你可以在应用项目中创建多个扩展Target(一个应用程序可以包含一个或多个应用扩展,该应用程序称为调用扩展应用程序)。

开发一个应用扩展最好的切入点是使用Xcode提供的所有平台上扩展点的应用扩展模板去开发。每个模板包含扩展点的具体实现文件和相关设置,并生成独立的二进制文件添加到你的应用程序中。

注意:在iOS中,包含扩展的应用必须提供一个扩展之外的功能。而在OS X中没有这个硬性要求,一个包含扩展的应用不要求必须提供一个额外功能。

当你将包含扩展的应用提交到应用商店后,用户只要下载并安装了你的应用,那么同时也自动安装了你的应用扩展。

当安装了应用扩展之后,用户首先必须要开启他们。通常,用户可根据当前处理的任务情形使用某个对应的应用扩展。比如,如果你的扩展启用了“Today”通知,那用户可以在通知中心编辑“Today ”view 来添加你的扩展。在其他情况中,用户可以使用 iOS 中的“Settings”或者 OS X 中的“System Preferences”来启用和管理扩展。

用户在不同的情形下体验不同的应用扩展

虽然每种类型的应用扩展的功能都是不同的,但是他们在用户体验及操作方式上还是有共同点的。如果你准备开发一个应用扩展,那么必须明白在不同的扩展点中,用户最喜欢的体验方式是什么,这一点至关重要。应用扩展的最高境界就是快速、流畅、只关注与单一任务。

通常用户是通过系统提供的某个界面开启应用扩展。比如说,如果一个用户想使用分享扩展,那么就需要通过应用程序中系统提供的分享按钮来选择开启。一个应用扩展必须要提供一个图标,以便用户选择和识别,通常情况下,应用扩展的图标与应用程序的图标是相同的。

虽然大多数的应用程序扩展都提供了一些自定义的用户界面,但一般用户不会看到你自定义的用户界面,除非他们进入到应用程序扩展中。当用户进入扩展,你自定义的界面可以将用户的视线、思维带入新的上下文中。由于用户在应用中是可以辨别出你开发的扩展的,所以当你的扩展功能非常独特或者击中用户内心需求的时候,他们会各位青睐你的扩展。当用户意识到扩展其实是独立运行的实体时,他们就可以选择将体验不好或功能不好的扩展移除或停用。

为了让用户平滑过渡到你的应用程序扩展,你要斟酌自定义的界面与扩展点界面的风格,做一个权衡。比如说,最好的方法就是让你的通知扩展看起来像是通知中心中原生的Widget,再比如说照片编辑扩展,你应该完全遵循iOS中的Photos应用界面去设计扩展的界面。

注意:即使你的应用程序扩展没有自定义用户界面(不包括图标),但用户仍然知道该扩展来自与当前应用不同的应用,因为通过激活扩展的方式,或扩展的功能就可以判断出。

原文地址:App Extensions Increase Your Impact

Swift中的Bool类型是许多原始函数的基础。所以基于它可以展示一个有趣的如何构建基本类型的示例。这篇文章的主旨是在Swift中创建一个类似Bool类型的新类型MyBool。我们希望通过这个简单的示例,能让你更清晰的了解Swift语言的工作原理。

让我们从最基本的定义开始。我们用枚举来定义MyBool类型的模型,它有两个不同的case

enum MyBool {
    case myTrue, myFalse
}

为了使大家不会产生混淆,这篇文章中我们将MyBool的两个case命名为myTruemyFalse。我们希望MyBool的构造函数MyBool()将其自身赋值为false,所以我们提供了如下init方法:

extension MyBool {
    init() { self = .myFalse }
}

Swift中的枚举会隐式的在它们自身内申明其枚举检索器的范围,允许我们使用MyBool.myFalse这种语法调用其成员,如果根据上下文可以推断出类型的话,我们甚至可以使用.myFalse调用其成员。但是在真正使用中,我们还是希望使用原始的truefalse关键字。想要做到这一点,我们的新类型MyBool需要遵循BooleanLiteralConvertible协议,像这样:

extension MyBool : BooleanLiteralConvertible {
    static func convertFromBooleanLiteral(value: Bool) -> MyBool {
        return value ? myTrue : myFalse
    }
}

// 我们现在就可以给MyBool类型的变量赋值为true或false.
var a : MyBool = true

通过以上设置,我们有了自己的基本类型,但是目前我们用它还做不了什么。布尔值需要通过if条件语句进行测试。在Swift中,我们通过BooleanType协议来做到这一点,该协议允许任意类型用于进行逻辑判断:

extension MyBool : BooleanType {
    func getBooleanType() -> Bool {
        switch self {
        case .myTrue: return true
        case .myFalse: return false
        }
    }   
}

// 现在我们就可以将MyBool类型的变量a用于'if'和'while'语句中进行测试.
if a {}

我们更希望所有遵循了BooleanType协议的类型都要强制转换为MyBool类型,所以我们可以这样写:

extension MyBool {
    // MyBool类型构造函数的参数设定为BooleanType类型.
    init(_ v : BooleanType) {
        if v.getBooleanType() {
            self = .myTrue
        } else {
            self = .myFalse
        }
    }
}

// 现在我们就可以这样进行转换了.
var basicBool : Bool = true
a = MyBool(basicBool)

注意构造函数里的_,它可以使我们在使用构造函数时省略参数命名。可以使用MyBool(x)这种语法,而不用使用MyBool(v: x)这种啰嗦的语法。

现在我们有了基本的功能,现在让我们来定义它的操作符,先来看看如何定义==操作符。没有关联数据的简单的枚举(像MyBool一样)是由编译器自动认为遵循Equatable协议进行编译的,所以没有必须要实现额外的代码。尽管如此,你也可以让任意类型遵循Equatable协议,并实现==操作符。比如我们的MyBool类型:

extension MyBool : Equatable {
}

func ==(lhs: MyBool, rhs: MyBool) -> Bool {
    switch (lhs, rhs) {
    case (.myTrue,.myTrue), (.myFalse,.myFalse):
        return true
    default:
        return false
    }
}

// 现在我们就可以使用==和!=进行比较了.
if a == a {}
if a != a {}

这里我们在swich语句中使用简单的匹配模式来处理。由于MyBool现在遵循了Equatable协议,所以他已经自动实现了!=操作符。再让我们加一些二进制运算符:

func &(lhs: MyBool, rhs: MyBool) -> MyBool {
    if lhs {
        return rhs
    }
    return false
}

func |(lhs: MyBool, rhs: MyBool) -> MyBool {
    if lhs {
        return true
    }
    return rhs
}

func ^(lhs: MyBool, rhs: MyBool) -> MyBool {
    return MyBool(lhs != rhs)
}

有了基本的运算符后,我们就可以实现各种有用的一元和复合赋值运算符,比如:

prefix func !(a: MyBool) -> MyBool {
    return a ^ true
}

// 复合赋值(按位)
func &=(inout lhs: MyBool, rhs: MyBool) {
    lhs = lhs & rhs
}

&=运算符将左边的运算对象作为inout对象,因为要对它进行读写操作,并且其效果对于操作者是可见的。Swift提供的值类型,使我们可以完全掌控丰富多变的各种操作,比如enumstruct

至此,简单的MyBool类型已经具备了基本的运算符操作。希望这篇文章能给你一些思路,使你能够在自己的代码中构建更高级更复杂的类型。

原文地址:Boolean

在我们的ios应用中,UITableViewController往往是项目中最大的文件,它们包含了许多冗余的、不必要的代码。所以有很多代码的复用率非常低。这篇学习笔记可以让我们构建出轻量级的UITableViewController,以及可复用的UITableViewDataSource

示例代码猛戳这里

把DataSource分离出来

我们的示例很简单,就是先展示一个UITableViewController,然后点击某行,推出第二个UITableViewController
项目结构:
示例

StoryBoard结构:
示例

运行截图:
示例

FirstTableViewController类中有以下几个方法:

override func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {

    return firstCellData!.datas.count;

}

override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {

    var cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as UITableViewCell!;

    cell.textLabel.text = firstCellData?.datas[indexPath.row];

    return cell;

}

override func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {

    let stvc = SecondTableViewController();

    self.navigationController.pushViewController(stvc, animated: true);

}

SecondTableViewController类中有以下几个方法:

override func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {

    return secondCellData!.titleDatas.count;

}

override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {

    var cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as UITableViewCell!;

    cell.textLabel.text = secondCellData?.titleDatas[indexPath.row];
    cell.detailTextLabel.text = secondCellData?.subtitleDatas[indexPath.row];

    return cell;

}

从上面两个类的代码片段中可以看出,它们都重写了UITableViewDataSource最基本的两个方法,即设定每个Section有多少行和生成Cell并装配数据。更准确的说这些方法都是针对数组做一些事情。所以我们可以尝试把处理数组相关的代码移到单独的类中。对于Cell的设置,我们可以通过闭包来实现。

我们可以创建一个CustomDataSource类(其实命名为ArrayDataSource更好),继承NSObject并遵循UITableViewDataSource协议。

import Foundation
import UIKit

class CustomDataSource: NSObject, UITableViewDataSource {

    var cellData: Array<AnyObject>?;
    var cellIdentifier: String?;
    var configureCell: ((AnyObject,AnyObject) -> ())?;

    init(cellData: Array<AnyObject>, cellIdentifier: String, configureCell: (AnyObject,AnyObject) -> ()) {

        super.init();
        self.cellData = cellData;
        self.cellIdentifier = cellIdentifier;
        self.configureCell = configureCell;

    }

    func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {

        return cellData!.count;

    }

    func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {

        var cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as UITableViewCell!;

        configureCell!(cell,cellData![indexPath.row]);

        return cell;

    }

}

现在,你就可以把UITableViewController的这两个方法去掉了。取而代之,你可以创建一个CustomDataSource类的实例作为UITableViewControllerDataSource

//FirstTableViewController

 func setupTableView() {

    customDataSource = CustomDataSource(cellData: items, cellIdentifier: cellIdentifier, configureCell: {(cell, firstCellData) in

        var firstCell = cell as FirstCell;
        firstCell.configureForCell(firstCellData as FirstCellData);

        });

    self.tableView.dataSource = customDataSource;

}





// SecondTableViewController

 func setupTableView() {

    customDataSource = CustomDataSource(cellData: items, cellIdentifier: cellIdentifier, configureCell: {(cell, secondCellData) in

        var secondCell = cell as SecondCell;
        secondCell.configureForCell(secondCellData as SecondCellData);

        });

    self.tableView.dataSource = customDataSource;

}

设置Cell的代码在对应的View层实现。

class FirstCell: UITableViewCell {

    func configureForCell(item: FirstCellData!) {

        self.textLabel.text = item.title;

    }

}





class SecondCell: UITableViewCell {

    func configureForCell(item: SecondCellData!) {

        self.textLabel.text = item.title;
        self.detailTextLabel.text = item.subtitle;

    }

}

现在每次你想把数组显示在某些Table View中的时候都可以复用DataSource的这些代码,当然也可以举一反三,将DataSource中处理其他数据类型的代码也分离出来,划分为多个处理不同数据类型或业务逻辑的DataSource,形成Data Source Pool,以后就可以随意复用啦。

总结:

在MVC设计模式中,我们应该把代码放置在合适、正确的位置,比如不要在View Controller中做网络请求的逻辑,你应该将它们封装在Model层的类中。也不应该在View Controller中构建复杂的View,而是应该将它们封装在View层的UIView子类中等等。

虽然以上技巧并不适用于所有项目的任何角落,但是我们作为程序员要本着写可维护的代码、写可让别人看懂的代码为遵旨。通过这些小技巧我们至少可以将笨重的View Controller变得小巧、整洁。

参考文献:Lighter View Controllers

文件和初始化

到目前为止,大多数开发者已经可以写出简单的Swift应用或者在Playground实验Swift语言的新特性。也许你也经历过这种情况,当你将Playground中运行正常的代码拷贝到Swift源文件中却发生了编译错误,“这到底是怎么回事?Playground文件和Swift源文件之间到底有什么不同?” 这篇文将告诉你们如何处理Swift项目中的各种文件,以及如何初始化全局数据。

应用中的文件

一个Swift应用必定会包含很多个源文件,基本上每个源文件中都有构成该应用的函数、类和其他一些申明等。Swift应用中的大多数源文件都是不需要按顺序访问的,都是无顺序的,你甚至可以在某个模块的最底部导入需要的源文件(虽然Swift不推荐这种编码风格)。

不管怎样,在大多数Swift的源文件中是不允许有最顶层级别的代码的。这里解释一下顶层代码,任何写在函数体、类之外,或被封装供他人调用的可执行语句我们称为顶层代码。我们之所以不允许出现顶层代码,是因为它会影响我们判断程序是从哪里开始运行的。

Playground,REPL,顶层代码

你可能会奇怪,为什么下面的代码在Playground中可以完美执行。由于它并没有包含任何其他东西,所以它必然是顶层代码:

println("Hello world")

上面的单行程序在没有任何其他代码的情况就可以正常运行,是因为Playground支持执行顶层代码。并且在Playground中引入的文件或者代码是按自上而下的顺序执行的。比如说,你不能在定义某个类型之前去使用它。当然,在Swift的Playground中也可以定义函数、类和其他在Swift中合法的任何类型,但并没有必要这么做。Playground的目的在于让开发者们能更简单、更快速的学习Swift语言和实验新的API,而不用创建大量Swift源文件去做这些事。

除了Playground,顶层代码也可以在REPL(Read-Eval-Print-Loop)中运行或作为脚本在Swift文件启动时运行。通过脚本使用Swift时,你可以在终端中用#!/usr/bin/xcrun swift或者xcrun swift myFile.swift的方式使用Swift文件。

应用程序的入口与“main.swift”

你可能也注意到了,在上面的文章中,我们提到在大多数的源文件中是不允许使用顶层代码的。但对一个文件除外,那就是main.swift文件,该文件的作用类似于Playground,但是它是随着你应用的其他源文件一起编译的。main.swift文件中允许顶层代码并且执行顺序是自上而下的。实际上,main.swift文件中的第一行代码就默认为是程序的入口。正因为如此,所以我们才能看到在Swift最小的程序只有一行代码,但它必须要在main.swift文件中。

在Xcode中,Mac程序模板文件中就包含一个main.swift文件,但在iOS程序的项目模板中是通过在Swift文件中添加@UIApplicationMain标签注明项目入口的。这样做会让编译器忽略main.swift入口文件,而将标注有@UIApplicationMain标签的文件当做入口文件。

全局变量

我们已经知道了Swift是如何判断程序的执行入口,那么全局变量是如何工作的呢?下面的这行代码,在运行时需要初始化吗?

var someGlobal = foo()

在单文件的程序中,代码是自上而下执行的,这类似于函数中变量的执行方式。这虽然看起来很简单,但是在复杂的程序中我们就不是很好回答这个问题了。我们从下面三个方面来考虑:

  1. 限制初始化,像简单的常量表达式,比如C语言。
  2. 任意初始化,在应用程序加载执行静态构造函数时初始化,比如C++语言。
  3. 延迟初始化,当全局变量第一次被使用的时候初始化,比如Java语言。

我们基本排除第一种情况,因为在Swift中不需要像C语言中的常量表达式。在Swift中,常量通常是在函数调用的时候执行的(内联)。而且也有更好的理由使用复杂的初始化方法,比如设置一个单例或者实例化一个字典。

第二种情况我们也基本排除,因为它在大型、复杂的程序中的效率很差。因为所有的初始化都要在应用程序启动之前,但是我们无法预测初始化的顺序,所以会有问题。

Swift采用第三种情况,这是最好的方法:允许自定义初始化,在程序启动时不会因为要进行大量初始化而降低效率,并且我们也可以预知每次初始化完成的顺序。

延迟初始化的全局变量(也包括结构体和枚举中的静态成员)是在第一次访问他们的时候才初始化的,并且以dispatch_once运行,确保了线程安全。你可以更酷的使用dispatch_once:只需要申明一个全局变量并初始化,再将其访问级别申明为private

总结

Swift语言的设计使得它可以很方便的在Playground中进行试验或快捷的编写脚本。一个完整的程序可以只有一行代码。当然,你也可以使用Swift编写出各种复杂的应用程序。你可以通过main.swift掌控各种初始化的完成时机,或者通过@UIApplicationMain标签指定你的iOS应用的程序入口。

本文翻译自Swift官方博客,原文地址:Files and Initialization