Tag Archives: iOS 8

WWDC2014之App Extensions

一、关于App Extensions

extension是iOS8新开放的一种对几个固定系统区域的扩展机制,它可以在一定程度上弥补iOS的沙盒机制对应用间通信的限制。

extension的出现,为用户提供了在其它应用中使用我们应用提供的服务的便捷方式,比如用户可以在Todaywidgets中查看应用展示的简略信息,而不用再进到我们的应用中,这将是一种全新的用户体验;但是,extension的出现可能会减少用户启动应用的次数,同时还会增大开发者的工作量。

几个关键词

  • extension point

系统中支持extension的区域,extension的类别也是据此区分的,iOS上共有TodayShareActionPhoto EditingStorage ProviderCustom keyboard几种,其中Today中的extension又被称为widget

每种extension point的使用方式和适合干的活都不一样,因此不存在通用的extension。

  • app extension

即为本文所说的extension。extension并不是一个独立的app,它有一个包含在app bundle中的独立bundle,extension的bundle后缀名是.appex。其生命周期也和普通app不同,这些后文将会详述。

extension不能单独存在,必须有一个包含它的containing app。

另外,extension需要用户手动激活,不同的extension激活方式也不同,比如: 比如Today中的widget需要在Today中激活和关闭;Custom keyboard需要在设置中进行相关设置;Photo Editing需要在使用照片时在照片管理器中激活或关闭;Storage Provider可以在选择文件时出现;ShareAction可以在任何应用里被激活,但前提是开发者需要设置Activation Rules,以确定extension需要在合适出现。

  • containing app

尽管苹果开放了extension,但是在iOS中extension并不能单独存在,要想提交到AppStore,必须将extension包含在一个app中提交,并且app的实现部分不能为空,这个包含extension的app就叫containing app。

extension会随着containing app的安装而安装,同时随着containing app的卸载而卸载。

  • host app

能够调起extension的app被称为host app,比如widget的host app就是Today

二、extension和containing app、host app

2.1 extension和host app

extension和host app之间可以通过extensionContext属性直接通信,该属性是新增加的UIViewController类别:

1
2
3
4
5
6
@interface UIViewController(NSExtensionAdditions) <NSExtensionRequestHandling>
// Returns the extension context. Also acts as a convenience method for a view controller to check if it participating in an extension request.
@property (nonatomic,readonly,retain) NSExtensionContext *extensionContext NS_AVAILABLE_IOS(8_0);
@end

实际上extension和host app之间是通过IPC(interprocess communication)实现的,只是苹果把调用接口高度抽象了,我们并不需要关注那么底层的东西。

2.2 containing app和host app

他们之间没有任何直接关系,也从来不需要通信。

2.3 extension和containing app

这二者之间的关系最复杂,纠纠缠缠扯不清关系。

  • 不能直接通信

首先,尽管extension的bundle是放在containing app的bundle中,但是他们是两个完全独立的进程,之间不能直接通信。不过extension可以通过openURL的方式启动containing app(当然也能启动其它app),不过必须通过extensionContext借助host app来实现:

1
2
3
4
5
6
7
8
//通过openURL的方式启动Containing APP
- (void)openURLContainingAPP
{
    [self.extensionContext openURL:[NSURL URLWithString:@"appextension://123"]
                 completionHandler:^(BOOL success) {
                     NSLog(@"open url result:%d",success);
                 }];
}

extension中是无法直接使用openURL的。

  • 可以共享Shared resources

extension和containing app可以共同读写一个被称为Shared resources的存储区域,这是通过App Groups实现的,后文将会详述。

三者间的关系可以通过官网给的两张图片形象地说明:

detailed_communication

app_extensions_container_restrictions

  • containing app能够控制extension的出现和隐藏

通过以下代码,containing app可以让extension出现或隐藏(当然extension也可以让自己隐藏):

1
2
3
4
5
6
7
8
9
10
11
//让隐藏的插件重新显示
- (void)showTodayExtension
{
    [[NCWidgetController widgetController] setHasContent:YES forWidgetWithBundleIdentifier:@"com.wangzz.app.extension"];
}
//隐藏插件
- (void)hiddeTodayExtension
{
    [[NCWidgetController widgetController] setHasContent:NO forWidgetWithBundleIdentifier:@"com.wangzz.app.extension"];
}

三、App Groups

这是iOS8新开放的功能,在OS X上早就可用了。它主要用于同一group下的app共享同一份读写空间,以实现数据共享。

extension和containing app共同读写一份数据是很合理的需求,比如系统的股市应用,widget和app中都需要展示几个公司的股票数据,这就可以通过App Groups实现。

3.1 功能开启

为了便于后续操作,请先确保你的开发者账号在Xcode上处于登录状态。

  • 在app中开启

App Groups位于:

1
TARGETS-->AppExtensionDemo-->Capabilities-->App Groups

找到以后,将App Groups右上角的开关打开,然后选择添加groups,比如我的是group.wangzz,当然这是为了测试随便起得名字,正规点得命名规则应该是:group.com.company.app。

添加成功以后如下图所示:

app_group

  • 在extension中开启

我创建的是widget,target名称为TodayExtension,对应的App Groups位于:

1
TARGETS-->TodayExtension-->Capabilities-->App Groups

开启方式和app中一样,需要注意的是必须保证这里地App Groups名称和app中的相同,即为group.wangzz。

四、extension和containing app数据共享

App Groups给我们提供了同一group内app可以共同读写的区域,可以通过以下方式实现数据共享:

4.1 通过NSUserDefaults共享数据

  • 存数据

通过以下方式向NSUserDefaults中保存数据:

1
2
3
4
5
6
7
- (void)saveTextByNSUserDefaults
{
    NSUserDefaults *shared = [[NSUserDefaults alloc] initWithSuiteName:@"group.wangzz"];
    [shared setObject:_textField.text forKey:@"wangzz"];
    [shared synchronize];
}

需要注意的是:

1.保存数据的时候必须指明group id;

2.而且要注意NSUserDefaults能够处理的数据只能是可plist化的对象,详情见Property List Programming Guide

3.为了防止出现数据同步问题,不要忘记调用[shared synchronize];

  • 读数据

对应的读取数据方式:

1
2
3
4
5
6
7
- (NSString *)readDataFromNSUserDefaults
{
    NSUserDefaults *shared = [[NSUserDefaults alloc] initWithSuiteName:@"group.wangzz"];
    NSString *value = [shared valueForKey:@"wangzz"];
    return value;
}

4.2 通过NSFileManager共享数据

NSFileManager在iOS7提供了containerURLForSecurityApplicationGroupIdentifier方法,可以用来实现app group共享数据。

  • 保存数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (BOOL)saveTextByNSFileManager
{
    NSError *err = nil;
    NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"];
    containerURL = [containerURL URLByAppendingPathComponent:@"Library/Caches/good"];
    NSString *value = _textField.text;
    BOOL result = [value writeToURL:containerURL atomically:YES encoding:NSUTF8StringEncoding error:&err];
    if (!result) {
        NSLog(@"%@",err);
    } else {
        NSLog(@"save value:%@ success.",value);
    }
    return result;
}
  • 读数据
1
2
3
4
5
6
7
8
9
- (NSString *)readTextByNSFileManager
{
    NSError *err = nil;
    NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"];
    containerURL = [containerURL URLByAppendingPathComponent:@"Library/Caches/good"];
    NSString *value = [NSString stringWithContentsOfURL:containerURL encoding:NSUTF8StringEncoding error:&err];
    return value;
}

在这里我试着保存和读取的是字符串数据,但读写SQlite我相信也是没问题的。

  • 数据同步

两个应用共同读取同一份数据,就会引发数据同步问题。WWDC2014的视频中建议使用NSFileCoordination实现普通文件的读写同步,而数据库可以使用CoreData,Sqlite也支持同步。

五、extension和containing app代码共享

和数据共享类似,extension和containing app很自然地会有一些业务逻辑上可以共用的代码,这时可以通过iOS8中刚开放使用的framework实现。苹果在App Extension Programming Guide中是这样描述的:

In iOS 8.0 and later, you can use an embedded framework to share code between your extension and its containing app. For example, if you develop image-processing code that you want both your Photo Editing extension and its containing app to share, you can put the code into a framework and embed it in both targets.

即将framework分别嵌入到extension和containing app的target中实现代码共享。但这样岂不是需要分别要将framework分别copy到extension和containing app的main bundle中?

参考extension和containing app数据共享,我试想能不能将framework只保存一份放在App Groups区域?

5.1 copy framework到App Groups

在app首次启动的时候将framework放到App Groups区域:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- (BOOL)copyFrameworkFromMainBundleToAppGroup
{
    NSFileManager *manager = [NSFileManager defaultManager];
    NSError *err = nil;
    NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"];
    NSString *sorPath = [NSString stringWithFormat:@"%@/Dylib.framework",[[NSBundle mainBundle] bundlePath]];
    NSString *desPath = [NSString stringWithFormat:@"%@/Library/Caches/Dylib.framework",containerURL.path];
    BOOL removeResult = [manager removeItemAtPath:desPath error:&err];
    if (!removeResult) {
        NSLog(@"%@",err);
    } else {
        NSLog(@"remove success.");
    }
    BOOL copyResult = [[NSFileManager defaultManager] copyItemAtPath:sorPath toPath:desPath error:&err];
    if (!copyResult) {
        NSLog(@"%@",err);
    } else {
        NSLog(@"copy success.");
    }
    return copyResult;
}

5.2 使用framework:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (BOOL)loadFrameworkInAppGroup
{
    NSError *err = nil;
    NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"];
    NSString *desPath = [NSString stringWithFormat:@"%@/Library/Caches/Dylib.framework",containerURL.path];
    NSBundle *bundle = [NSBundle bundleWithPath:desPath];
    BOOL result = [bundle loadAndReturnError:&err];
    if (result) {
        Class root = NSClassFromString(@"Person");
        if (root) {
            Person *person = [[root alloc] init];
            if (person) {
                [person run];
            }
        }
    } else {
        NSLog(@"%@",err);
    }
    return result;
}

经过测试,竟然能够加载成功。

需要说明的是,这里只是说那么用是可以成功加载framework,但还面临不少问题,比如如果用户在启动app之前去使用extension,这时framework还没有copy过去,怎么处理;另外iOS的机制或者苹果的审核是否允许这样使用等。

在一切确定下来之前还是乖乖按文档中的方式使用吧。

六、生命周期

extension和普通app的最大区别之一是生命周期。

  • 开始

在用户通过host app点击extension时,系统就会实例化extension应用,这是生命周期的开始。

  • 执行任务

在extension启动以后,开始执行它的使命。

  • 终止

在用户取消任务,或者任务执行结束,或者开启了一个长时后台任务时,系统会将其杀掉。

由此可见,extension就是为了任务而生!

下图来自官方文档,它将生命周期划分的更详细:

app_extensions_lifecycle

通过打印日志发现,Today中的widget在将Today切换到全部或者未读通知时都会被杀掉。

七、 调试

extension和普通app的调试方式差不多,开始调试前先选中extension对应的target,点击run,就会弹出下图所示选择框:

extension_debug

需要选择一个host app,这里选择Today

然后即可和普通app一样调试了,不过我在实际使用过程中,发现有各种奇怪的事情,比如NSLog无法在控制台输出,应该是bug吧。

八、 iOS8应用文件系统

发现iOS8的文件系统发生了变化,新的文件系统将可执行文件(即原来的.app文件)从沙盒中移到了另外一个地方,这样感觉更合理。

  • 测试代码

下述代码用于打印App Groups路径、应用的可执行文件路径、对应的Documents路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)logAppPath
{
    //app group路径
    NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"];
    NSLog(@"app group:\n%@",containerURL.path);
    //打印可执行文件路径
    NSLog(@"bundle:\n%@",[[NSBundle mainBundle] bundlePath]);
    //打印documents
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *path = [paths objectAtIndex:0];
    NSLog(@"documents:\n%@",path);
}
  • containing app执行结果
1
2
3
4
5
6
2014-06-23 19:35:03.944 AppExtensionDemo[7471:365131] app group:
/private/var/mobile/Containers/Shared/AppGroup/89CCBFB1-CA5E-4C7F-80CB-A3EB9E841816
2014-06-23 19:35:03.946 AppExtensionDemo[7471:365131] bundle:
/private/var/mobile/Containers/Bundle/Application/1AC73797-A3BB-4BDE-A647-3D083DA6871A/AppExtensionDemo.app
2014-06-23 19:35:03.948 AppExtensionDemo[7471:365131] documents:
/var/mobile/Containers/Data/Application/E5E6E516-0163-4754-9D10-A5F6C33A6261/Documents
  • extension执行结果
1
2
3
4
5
6
Jun 23 19:37:49 autonavis-iPad com.foogry.AppExtensionDemo.TodayExtension[7638] <Warning>: app group:
  /private/var/mobile/Containers/Shared/AppGroup/89CCBFB1-CA5E-4C7F-80CB-A3EB9E841816
Jun 23 19:37:49 autonavis-iPad com.foogry.AppExtensionDemo.TodayExtension[7638] <Warning>: bundle:
  /private/var/mobile/Containers/Bundle/Application/596717B7-7CB8-4F53-BCD4-380F34ABD30F/AppExtensionDemo.app/PlugIns/com.foogry.AppExtensionDemo.TodayExtension.appex
Jun 23 19:37:49 autonavis-iPad com.foogry.AppExtensionDemo.TodayExtension[7638] <Warning>: documents:
  /var/mobile/Containers/Data/PluginKitPlugin/57581433-3DBD-4930-971F-78D30C150E8A/Documents

由此可见,不管是extension还是containing app,他们的可执行文件和保存数据的目录都是分开存放的,即所有app的可执行文件都放在一个大目录下,保存数据的目录保存在另一个大目录下,同样,AppGroup放在另一个大目录下。

说明

  • 本文用到的demo已经上传到github上。

  • 文中可能有理解有误的地方,还请指出。

参考文档

registerForRemoteNotification in iOS 8,新系统iOS 8下推送注册

有没有发现你的应用在新系统iOS 8下,推送服务不工作了?

嘿嘿,新的iOS 8环境中,远程推送注册已经采用了新的方法,之前(如下)的办法要失效了:

[[UIApplicationsharedApplication] registerForRemoteNotificationTypes:UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound];

如果你有意开始升级你的应用,现在应该为iOS 8环境考虑了,要采用下面新的办法:

        NSSet *categories = nil;

        [[UIApplicationsharedApplication] registerForRemoteNotifications];

        [[UIApplicationsharedApplication] registerUserNotificationSettings:[UIUserNotificationSettingssettingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeBadge|UIUserNotificationTypeSoundcategories:categories]];

如果你还不知道怎么构造 categories,那暂时使用 nil 值吧。

 

iOS 8隐藏功能曝光,支持文本编辑,预览、Siri API等

苹果自从库克接手后,做出了很多改变。如今又有开发者在iOS 8的中发现了之前没有被介绍过的新功能,用户可以更改主题的颜色以及更换字体。苹果或为iOS增加更多的个性化设置。

开发者发现iOS 8中可更改主题颜色和字体(图片来自cnbeta)

通过iOS 8的发布,我们可以看到苹果确实变得更加开放,之前在国内一直饱受诟病的输入法终于得到了解决,允许安装第三方输入法键盘。不过按照苹果以前的惯例,在iOS测试版中存在的一些新功能有着实验性质,在正式版会被取消。

如果对这个新功能感兴趣的用户,只要把iOS 8的UIUseAlternateUI设置为Enable就可以了。

另外,据一位 iOS 开发者在推特上表示,发现了iOS 8测试版中苹果正在测试独立 iTunes 广播应用的证据。此外,苹果似乎也正在测试 iOS 版文本编辑和预览应用。当然,一起被发现的还有 Siri API。这些发现与之前的传言相同,消息称苹果将在 iOS 8系统中增加文本编辑、预览和 iTunes 广播等新应用。当然,这些新功能很有可能被推迟至 iOS 8.1系统中。

我们可以在 Continuity 功能相关的.plist 文件中找到 com.apple.Preview和 com.apple.TextEdit 等代码。对于 Siri 控制其他应用,开发者发现了 UIApplicationLaunchOptionsSiriTaskKey 这样的字符串。最有趣的是,开发者还发现了 Healthbook 字符串。在 WWDC 发布会之前,有消息称苹果将在 iOS 8中预装 Healthbook 应用,这款主打健康和健身数据收集的应用将可以与传言中的 iWatch 互相协作,只是苹果只在发布会上推出了 Health(健康)应用。

iOS 8/Yosemite AirDrop 视频曝光

WWDC 后,围绕着越来越开放的 OS X 和 iOS 系统上,果粉总是有聊不完的话题。演示上那幕 Mac 和 iOS 8 间的 AirDrop 帅爆了。或许它们之间的无缝沟通就成了很多用户在决定是否购买 Mac 上的一个巨型砝码。之前爱应用小编也分别刷了 iOS 8 和 Yosemite,但未实验成功,必须要在 Mac 重装成一台新机时才可能使用。不过并不是所有人都愿意为了看看这个功能而把自己的电脑清掉,今天就跟着 AppleInsider的视频来抢先看看这个超实用功能吧!

http://v.youku.com/v_show/id_XNzIzODgyNzky.html?firsttime=95

目前,咱们只能在 iOS 设备间或是 Mac 间通过 AirDrop 来传输图片等文件和文件夹。视频中,使用方法也与之前很相似。在 iOS 端和 Mac 端都打开 AirDrop,两者就能在 AirDrop 界面上互相找到,之后通过拖拽即可完成文件传输。从 iPhone 上传来的照片会被默认储存在下载文件夹中。功能这么屌,能说服自己下一台电脑换 Mac 咩?

iOS 8将支持多种模式的多任务功能

在前不久的 2014 年 WWDC 上,苹果推出了自己最新的 iOS 系统 iOS 8,但传言中的分屏多任务管理功能却并没有出现。最近,有开发者发现,iOS 8 的分屏多任务管理功能是的确存在的,并且向用户提供了多种分屏模式。开发者发现 iOS 8 系统的 SpringBoard 包含了允许应用在多种尺寸下同屏运行的代码,两个应用可以在 1/4、1/2 或 3/4 的比例下运行。

iOS 8 支持分屏多任务的消息早在今年 5 月就曾出现,消息称 iPad Air 将支持两个应用同时运行,就像是微软 Surface。iOS 8 分屏多任务功能只支持在横屏模式下运行,两个同时运行的应用可以互相分享链接、图片和信息等数据。目前的消息是,多屏多任务可能只支持 iPad Air,随后也有可能支持 iPhone 和 Retina iPad mini。此外,分屏多任务功能还可能支持苹果没有发布的 12.9 英寸 iPad Pro。WWDC 举行之前,《纽约时报》就有报道称分屏多任务功能正在开发中,但不会在 WWDC 上公布。

Source:macrumors

 

iOS 8中隐藏的14个小细节

WWDC 2014主题演讲时间有限,因此苹果不可能毫无遗漏地向开发者展示iOS和新一代桌面操作系统中的每个细节,因此这篇文章继续我们的发现之旅。我们为大家挖掘苹果iOS 8中隐藏的细节改变。虽然iOS 8中一些明显的改观让我们兴奋不已,比如HomeKit iCloud Photo Library以及Metal,但是一些隐藏的小细节也会不经意间带给我们惊喜。

 

伴随着iOS 8发布的主要新功能,Jony Ive和人机界面开发团队也在UI上进行了多处微调整。以下是我们发现的iOS 8中隐藏的14个小细节:

 

SafariPrivate mode

Safari目前有了常规的模式和私人模式。当从私人模式切换时,iOS 8会询问你要保持还是关闭这些tab

 

iCloud菜单中管理Apple ID

与通过iTunes或者App Store管理Apple ID不同,iOS 8iCloud设置中有一个新位置,可让你查看和编辑个人信息。你也可以在此变更密码、信用卡以及为Family Share添加成员。

 

 

Emoji开关变成了笑脸

iOS 8中调出emoji键盘变得更加方便了,用一个笑脸代替了符号。在其他键盘中,iOS 8允许你通过下拉触发新的QuickType键盘,并通过上拉文本框和键盘间的灰色bar返回。

 

Weather appWeather Channel获取数据

一直以来都是雅虎在处理iOS中的天气信息,但是iOS 8打破了这一惯例,转而使用The Weather Channel提供的数据。

 

FaceTime呼叫等待

FaceTime依然没有群体呼叫功能,但苹果为其视频聊天服务添加了呼叫等待功能,方便用户拒接来电,或者结束当前通话以接入新呼叫。

 

以类别组织iBooks

苹果为iBooks添加了一些新的组织功能,可以按照集合或者类别组织电子书。

 

延时拍摄视频

iOS 8新增了延时拍摄的功能以动态地选择间隔时间,可用户完美捕捉落日的余晖和城市的喧嚣。

 

Shared Links中的RSS feeds

Safari现在可在Shared Links feed中添加RSS feeds。在Safari中访问网站,打开分享链接,到订阅中点击“Add Current Site”。

 

Camera计时器

App Store中每款camera app几乎都有自己的计时器,但是苹果直到iOS 8中才落实了这项功能。用户可在app顶部打开计时器,有3s10s两个选项。

 

请求桌面版网页

iOS 8中的Safari有了“Request Desktop Site”的功能,位于地址栏的下方。

 

把所有文本标记为已读

iOS 8中的Messages应用有了“Read All”选项,该选项位于编辑功能的下方,贴心地帮用户摆脱了烦人的通知,你也可以在设置菜单里选择信息储存时间,比如为1年、1月还是永久。

 

恢复已删除的照片

用户可在Recently Deleted中恢复已经此前删除的照片。

 

秒表更加精细

iOS 8中秒表中的StartLap按钮有了细微的灰色阴影。

 

基于位置的解锁

iOS 8隐藏的一个贴心设计是基于位置的解锁方式。该方式类似于常见的快速解锁并打开相机应用,当用户接近苹果零售店时,屏幕左下角会出现Apple Store官方应用,向上滑动应用icon可解锁屏幕并打开应用。目前,该功能似乎并不仅限于苹果自己应用,第三方应用也适用,比如星巴克的应用。

OS X Yosemite支持设备名单公布

苹果在 WWDC 2014 大会上正式发布了 iOS 8 和 OS X Yosemite 系统。新系统的发布意味着可能又有一批旧的硬件设备无法获得新系统的支持。所以不少朋友应该都很担心自己的设备是否能获得新系统的支持吧。

根据苹果日前发布的 OS X Yosemite 硬件要求,目前能够运行 Mavericks 系统的 Mac 硬件设备将能够继续运行 OS X Yosemite,也就是说以下 Mac 设备将可运行 OS X 10.10:

- MacBook Pro:2007 年中及以上
- MacBook Air:2008 年晚些时候及以上
- iMac:2007 年中及以上
- Mac mini:2009 年早些时候及以上
- Mac Pro:2008 年早些时候及以上
- MacBook:2008 年晚些时候一体成型铝合金机,2009年早些时候及以上
- Xserve9:2009 年早些时候

这也意味着苹果 Mac 操作系统对硬件设备的要求已经连续两年没有变化,去年从 Mountain Lion 升级至 Mavericks,苹果的硬件设备要求同样没有变化。

不过在 iOS 社区就没有那么幸运了。根据此前公布的名单,iPhone 4 将无法运行 iOS 8,而此前预测可能无法支持新系统的 iPad 2 还在支持的名单中,只是不知道 A5 芯片的 iPad 2 运行起 iOS 8 来会不会很吃力。

今年秋季 iOS 8 和 OS X Yosemite 将会免费向用户开放下载。Continuity 已经成为苹果开发这两个平台的焦点之一。

iOS 8开放浏览器引擎限制

WWDC 2014上发布的 iOS 8 给人的感觉是更加开放,除了开放第三方输入法、Touch ID 指纹识别传感器,还有一个重要的信息是开放了浏览器引擎限制。这样第三方的浏览器就能够使用苹果公司最新的 WebKit 编程接口,使用 Nitro Javascript 等技术,最终第三方浏览器将可能和 Safari 一样快。

过去,iPhone 浏览器应用需要使用独立于移动版 Safari 浏览器且速度较慢的引擎。就 Chrome for iOS 来说,没有使用自有的引擎,而是 iOS 的 UIWebView,而 Safari 使用的是更快速的 Nitro 引擎。这也是众多第三方浏览器的无奈之举,因为苹果不允许第三方浏览器使用 Nitro 引擎。

Opera Coast 项目负责人休布·克莱豪特(Huib Kleinhout)表示,对 Javascript 的改进只会在浏览大量使用这种技术的网站时才会被人注意到,其他调整将对稳定性的改善起到广泛影响,而且不必继续使用之前的一些变通方式。

这一重大的浏览器开放政策,对 Chrome 和 Opera 等与 Safari 竞争的浏览器来说,都是一件意义重大的事,或许以后 iOS 设备上的 Chrome 和 Safari 的速度将不相上下。你的 iOS 设备上选择什么浏览器呢?

Swift之?和!

Swift语言使用var定义变量,但和别的语言不同,Swift里不会自动给变量赋初始值,也就是说变量不会有默认值,所以要求使用变量之前必须要对其初始化。如果在使用变量之前不进行初始化就会报错:

var stringValue : String 
//error: variable 'stringValue' used before being initialized
//let hashValue = stringValue.hashValue
//                            ^
let hashValue = stringValue.hashValue

上面了解到的是普通值,接下来Optional值要上场了。经喵神提醒,Optional其实是个enum,里面有NoneSome两种类型。其实所谓的nil就是Optional.None, 非nil就是Optional.Some, 然后会通过Some(T)包装(wrap)原始值,这也是为什么在使用Optional的时候要拆包(从enum里取出来原始值)的原因, 也是PlayGround会把Optional值显示为类似{Some "hello world"}的原因,这里是enum Optional的定义:

enum Optional<T> : LogicValue, Reflectable {
    case None
    case Some(T)
    init()
    init(_ some: T)

    /// Allow use in a Boolean context.
    func getLogicValue() -> Bool

    /// Haskell's fmap, which was mis-named
    func map<U>(f: (T) -> U) -> U?
    func getMirror() -> Mirror
}

声明为Optional只需要在类型后面紧跟一个?即可。如:

var strValue : String? 

一旦声明为Optional的,如果不显式的赋值就会有个默认值nil。判断一个Optional的值是否有值,可以用if来判断:

if strValue {
    //do sth with strValue
}

然后怎么使用Optional值呢?文档中也有提到说,在使用Optional值的时候需要在具体的操作,比如调用方法、属性、下标索引等前面需要加上一个?,经喵神指正,”Optional Chaining的问号的意思是询问是否响应后面这个方法,和原来的isResponseToSelector有些类似”,如果是nil值,也就是Optional.None,固然不能响应后面的方法,所以就会跳过,如果有值,就是Optional.Some,可能就会拆包(unwrap),然后对拆包后的值执行后面的操作,比如:

let hashValue = strValue?.hashValue 

strValue是Optional的字符串,如果strValue是nil,则hashValue也为nil,如果strValue不为nil,hashValue就是strValue字符串的哈希值

到这里我们看到了?的两种使用场景:
1.声明Optional值变量
2.用在对Optional值操作中,用来判断是否能响应后面的操作

另外,对于Optional值,不能直接进行操作,否则会报错:

//error: 'String?' does not have a member named 'hashValue'
//let hashValue = strValue.hashValue
//                ^        ~~~~~~~~~

let hashValue = strValue.hashValue

上面提到Optional值需要拆包(unwrap)后才能得到原来值,然后才能对其操作,那怎么来拆包呢?拆包提到了几种方法,一种是Optional Binding, 比如:

if let str = strValue {
    let hashValue = str.hashValue
}

还有一种是在具体的操作前添加!符号,好吧,这又是什么诡异的语法?!

直接上例子,strValue是Optional的String:

let hashValue = strValue!.hashValue

这里的!表示“我确定这里的的strValue一定是非nil的,尽情调用吧” ,比如这种情况:

if strValue {
    let hashValue = strValue!.hashValue
}

{}里的strValue一定是非nil的,所以就能直接加上!,强制拆包(unwrap)并执行后面的操作。 当然如果不加判断,strValue不小心为nil的话,就会出错,crash掉。

考虑下这一种情况,我们有一个自定义的MyViewController类,类中有一个属性是myLabel,myLabel是在viewDidLoad中进行初始化。因为是在viewDidLoad中初始化,所以不能直接声明为普通值:var myLabel : UILabel,因为非Optional的变量必须在声明时或者构造器中进行初始化,但我们是想在viewDidLoad中初始化,所以就只能声明为Optional:var myLabel: UILabel?, 虽然我们确定在viewDidLoad中会初始化,并且在ViewController的生命周期内不会置为nil,但是在对myLabel操作时,每次依然要加上!来强制拆包(在读取值的时候,也可以用?,谢谢iPresent在回复中提醒),比如:

myLabel!.text = "text"
myLabel!.frame = CGRectMake(0, 0, 10, 10)
...

对于这种类型的值,我们可以直接这么声明:var myLabel: UILabel!, 果然是高(hao)大(fu)上(za)的语法!, 这种是特殊的Optional,称为Implicitly Unwrapped Optionals, 直译就是隐式拆包的Optional,就等于说你每次对这种类型的值操作时,都会自动在操作前补上一个!进行拆包,然后在执行后面的操作,当然如果该值是nil,也一样会报错crash掉。

那么!大概也有两种使用场景
1.强制对Optional值进行拆包(unwrap)
2.声明Implicitly Unwrapped Optionals值,一般用于类中的属性

Swift是门新生的语言,我们有幸见证了它的诞生,激动之余也在佩服苹果大刀阔斧的推出一个新的语言替代一个已经比较成熟语言的魄力,今天在知乎日报上看到一个回答是说Swift是一门玩具语言,正当想去吐槽,发现回答已经被删除了。个人认为苹果是很认真的推出Swift的,从Swift的各种细微的设计也能看的出来。

另外这两个小符号就花费了我不少的时间来理解,可能依然会有错误和不妥之处,欢迎大家指正,本文旨在抛砖引玉。除此之外,Swift还有很多很棒的特性,WWDC 2014 会有四五个和Swift语言相关的Video,大家也可以去关注一下。

最后要感谢喵神的纠正了多处有问题的地方,thx, have fun!

主要参考自苹果的官方文档《The Swift Programming Language》,可以在这里下载到。

从iOS 8 SDK开始,你可以使用上swift编写你的应用了,加油。