i​OS 8

随便去问任何人,他们都会告诉你 WWDC2014 是近年来最为激动的回忆。 整个大会没有发布任何新硬件,它是一次史无前例的软件开发者盛宴!

仅是 iOS 8 和 OS X Yosemite 的发布就能让 2014 成为苹果平台划时代的一年,加上 Extension,Continuity,SpriteKit 改进,iOS SceneKit,Metal,HealthKit,Local Authentication 和全新的照片框架。更不用说,Xcode 和 Interface Builder 的明显改观,重新设计的 iTunes Connect,TestFlight,崩溃报告和 CloudKit。当然还有 oh yeah-Swift。

更棒的是?苹果放松了她的保密协定,也就是说我们可以现在就公开讨论这些崭新的玩具!

这周,我们将拨开 iOS 8 的云雾,探讨一些所有人都应该知道新 API。

从现在开始 NSHipster 将主要使用 Swift 编写样例代码。夏天结束之前,我们希望能将全部的现存代码转换为 Swift,并且提供可以切换语言的选项。


NSProcessInfo -isOperatingSystemAtLeastVersion

忘记[[UIDevice currentDevice] systemVersion]NSFoundationVersionNumber吧, 现在可以用NSProcessInfo -isOperatingSystemAtLeastVersion来确定系统版本。

import Foundation

let yosemite = NSOperatingSystemVersion(majorVersion: 10, minorVersion: 10, patchVersion: 0)
NSProcessInfo().isOperatingSystemAtLeastVersion(yosemite) // false

值得注意的是,在做兼容性测试的时候还是应该使用SomeClass.classrespondsToSelector:。 Swift 和 C 中的编译器宏可以用来根据不同生成配置和目标来选择代码。

新的 NSFormatter 子类

Foundation 中严重缺失的一项功能就是不能处理重量和长度单位转换。在 iOS 8 和 OS X Yosemite 中,引进了三个新类NSEnergyFormatterNSMassFormatterNSLengthFormatter来弥补这一缺失。

这使得NSFormatter子类的数量翻了一倍, 之前只有NSNumberFormatterNSDateFormatterNSByteCountFormatter

虽然这些都是 Foundation 的子类,但是它们主要都是在 HealthKit 当中使用。

NSEnergyFormatter

NSEnergyFormatter使用焦作为能量的原始单位,当处理健康信息时,则使用卡.

let energyFormatter = NSEnergyFormatter()
energyFormatter.forFoodEnergyUse = true

let joules = 10_000.0
println(energyFormatter.stringFromJoules(joules)) // "2.39 Cal"

NSMassFormatter

虽然质量是物质存在的基本单位, 在 HealthKit 中,它主要指的是身体重量.

let massFormatter = NSMassFormatter()
let kilograms = 60.0
println(massFormatter.stringFromKilograms(kilograms)) // "132 lb"

NSLengthFormatter

NSFormatter的最后一个新子类是NSLengthFormatter. 我们可以把它想象为MKDistanceFormatter的加强版。

let lengthFormatter = NSLengthFormatter()
let meters = 5_000.0
println(lengthFormatter.stringFromMeters(meters)) // "3.107 mi"

CMPedometer

沿着 iOS 8 的健康路线, CMStepCounter被重新设计了. CMPedometer作为它的改良版本不仅可以即时获取离散的点数据,并且可以同时跟踪脚步和距离,甚至计算总共爬了多少级楼梯。

M7 芯片真是功能强大.

import CoreMotion

let lengthFormatter = NSLengthFormatter()
let pedometer = CMPedometer()
pedometer.startPedometerUpdatesFromDate(NSDate(), withHandler: { data, error in
    if !error {
        println("Steps Taken: \(data.numberOfSteps)")

        let distance = data.distance.doubleValue
        println("Distance: \(lengthFormatter.stringFromMeters(distance))")

        let time = data.endDate.timeIntervalSinceDate(data.startDate)
        let speed = distance / time
        println("Speed: \(lengthFormatter.stringFromMeters(speed)) / s")
    }
})

CMAltimeter

在支持的设备上,CMAltimeter可以让CMPedometerfloorsAscendedfloorsDescended数据更加精准:

import CoreMotion

let altimeter = CMAltimeter()
if CMAltimeter.isRelativeAltitudeAvailable() {
    altimeter.startRelativeAltitudeUpdatesToQueue(NSOperationQueue.mainQueue(), withHandler: { data, error in
        if !error {
            println("Relative Altitude: \(data.relativeAltitude)")
        }
    })
}

CLFloor

CLFloor的引入展示了苹果进军室内导航的宏伟计划,楼层信息将扮演着重要的角色。

import CoreLocation

class LocationManagerDelegate: NSObject, CLLocationManagerDelegate {
    func locationManager(manager: CLLocationManager!, didUpdateLocations locations: AnyObject[]!) {
        let location: CLLocation? = locations[0] as? CLLocation
        if let floor: CLFloor? = location?.floor {
            println("Current Floor: \(floor?.level)")
        }
    }
}

let manager = CLLocationManager()
manager.delegate = LocationManagerDelegate()
manager.startUpdatingLocation()

HKStatistics

作为一个框架,HealthKit 包含着大量的子类和常量。要想全部理解,HKStatistics是一个很好的开始。

HealthKit 管理着所有的生理信息,例如:心率,卡路里摄入量,血氧等等,并且通过统一的 API 聚合在一起。

下面这个例子演示了如何从一天的连续数据中,挖掘和获取单独的数据:

import HealthKit

let collection: HKStatisticsCollection? = ...
let statistics: HKStatistics? = collection!.statisticsForDate(NSDate())
for item: AnyObject in statistics!.sources {
    if let source = item as? HKSource {
        if let quantity: HKQuantity = statistics!.sumQuantityForSource(source) {
            if quantity.isCompatibleWithUnit(HKUnit.gramUnitWithMetricPrefix(.Kilo)) {
                let massFormatter = NSMassFormatter()
                let kilograms = quantity.doubleValueForUnit(HKUnit.gramUnitWithMetricPrefix(.Kilo))
                println(massFormatter.stringFromKilograms(kilograms))
            }

            if quantity.isCompatibleWithUnit(HKUnit.meterUnit()) {
                let lengthFormatter = NSLengthFormatter()
                let meters = quantity.doubleValueForUnit(HKUnit.meterUnit())
                println(lengthFormatter.stringFromMeters(meters))
            }

            if quantity.isCompatibleWithUnit(HKUnit.jouleUnit()) {
                let energyFormatter = NSEnergyFormatter()
                let joules = quantity.doubleValueForUnit(HKUnit.jouleUnit())
                println(energyFormatter.stringFromJoules(joules))
            }
        }
    }
}

NSHipster 将会在未来探讨更多的 HealthKit,敬请关注!

NSStream +getStreamsToHostWithName

在许多方面,WWDC 2014 也是苹果查漏补遗的一年,比如给NSStream添加了新的 initializer(再也不用调用CFStreamCreatePairWithSocketToHost了),这就是:+[NSStream getStreamsToHostWithName:port:inputStream:outputStream:]

var inputStream: NSInputStream?
var outputStream: NSOutputStream?

NSStream.getStreamsToHostWithName(hostname: "nshipster.com",
                                      port: 5432,
                               inputStream: &inputStream,
                              outputStream: &outputStream)

NSString -localizedCaseInsensitiveContainsString

这又是一个NSString小而实用的修缮:

let string: NSString = "Café"
let substring: NSString = "É"

string.localizedCaseInsensitiveContainsString(substring) // true

CTRubyAnnotationRef

好吧,此Ruby非彼Ruby. . 这是用来给亚洲文字添加注音符号的.

@import CoreText;

NSString *kanji = @"猫";
NSString *hiragana = @"ねこ";

CFStringRef furigana[kCTRubyPositionCount] =
    {(__bridge CFStringRef)hiragana, NULL, NULL, NULL};

CTRubyAnnotationRef ruby =
    CTRubyAnnotationCreate(kCTRubyAlignmentAuto, kCTRubyOverhangAuto, 0.5, furigana);

无可否认的是,文档中并没有很清晰的描述具体如何将它整合进入你剩下的CoreText中,但是结果如下:

ねこ

新的日历识别符

iOS 8 和 OS X 中这些新的日历识别符使得 Fundation 跟上了CLDR的步伐:

  • NSCalendarIdentifierCoptic: 亚历山大日历, 科普特正教使用.
  • NSCalendarIdentifierEthiopicAmeteMihret: 埃塞俄比亚日历, Amete Mihret
  • NSCalendarIdentifierEthiopicAmeteAlem: 埃塞俄比日历, Amete Alem
  • NSCalendarIdentifierIslamicTabular: 一个简单的伊斯兰星历.
  • NSCalendarIdentifierIslamicUmmAlQura: 沙特阿拉伯伊斯兰日历.

NSURLCredentialStorage

自从去年NSURLSession的引入之后,Foundation 的 URL 载入系统并没有太大的改变。但是,新的NSURLCredentialStorage可以让你更加方便地以移步,非闭包的方式获取和存储密码。

import Foundation

let session = NSURLSession()
let task = session.dataTaskWithURL(NSURL(string: "https://nshipster.com"), completionHandler: { data, response, error in
    // ...
})

let protectionSpace = NSURLProtectionSpace()
NSURLCredentialStorage.getCredentialsForProtectionSpace(protectionSpace: protectionSpace, task: task, completionHandler: { credentials in
    // ...
})

kUTTypeToDoItem

在比较过最新的 API 之后,你可能会注意到大量的新UTI常量。其中,kUTTypeToDoItem引起了我的注意:

import MobileCoreServices

kUTTypeToDoItem // "public.to-do-item"

作为一个公共类型,iOS 和 OS X 现在提供了统一的方式让 App 之间共享任务。如果你碰巧正在开发一个任务管理工具,正确的整合好这个系统类型应该成为你的首要任务。

kCGImageMetadataShouldExcludeGPS

许多用户完全不知道他们用手机拍摄的大部分照片都包含了 GPS 元数据。更是有数不清的人因为这一个小细节泄露了自己的隐私。

最新的图片 I/O 框架中加入了一个新的选项CGImageDestination: kCGImageMetadataShouldExcludeGPS让你方便的控制是否包含 GPS 元数据。

@import UIKit;
@import ImageIO;
@import MobileCoreServices;

UIImage *image = ...;
NSURL *fileURL = [NSURL fileURLWithPath:@"/path/to/output.jpg"];
NSString *UTI = kUTTypeJPEG;
NSDictionary *options = @{
                          (__bridge id)kCGImageDestinationLossyCompressionQuality: @(0.75),
                          (__bridge id)kCGImageMetadataShouldExcludeGPS: @(YES),
                          };

CGImageDestinationRef imageDestinationRef =
CGImageDestinationCreateWithURL((__bridge CFURLRef)fileURL,
                                (__bridge CFStringRef)UTI,
                                1,
                                NULL);

CGImageDestinationAddImage(imageDestinationRef, [image CGImage], (__bridge CFDictionaryRef)options);
CGImageDestinationFinalize(imageDestinationRef);
CFRelease(imageDestinationRef);

WTF_PLATFORM_IOS

#define WTF_PLATFORM_IOS已经从 JavaScriptCore 中移除.

WKWebView

UIWebView已死. WKWebView万岁.

WKWebView提供了 Safari 级别的性能,并且在UIWebView的基础上提供了更多的配置选项:

import WebKit

let preferences = WKPreferences()
preferences.javaScriptCanOpenWindowsAutomatically = false

let configuration = WKWebViewConfiguration()
configuration.preferences = preferences

let webView = WKWebView(frame: self.view.bounds, configuration: configuration)
let request = NSURLRequest(URL: NSURL(string: "https://nshipster.com"))
webView.loadRequest(request)

NSQualityOfService

线程这个概念已经在苹果的框架中被系统性的忽略。这对于开发者而言是件好事。

沿着这个趋势,NSOperation中新的qualityOfService的属性取代了原来的threadPriority。通过它可以推迟那些不重要的任务,从而让用户体验更加流畅。

NSQualityOfService枚举定义了以下值:

  • UserInteractive:和图形处理相关的任务,比如滚动和动画。
  • UserInitiated:用户请求的任务,但是不需要精确到毫秒级。例如,如果用户请求打开电子邮件 App 来查看邮件。
  • Utility:周期性的用户请求任务。比如,电子邮件 App 可能被设置成每五分钟自动检查新邮件。但是在系统资源极度匮乏的时候,将这个周期性的任务推迟几分钟也没有大碍。
  • Background:后台任务,用户可能并不会察觉对这些任务。比如,电子邮件 App 对邮件进行引索以方便搜索。

Quality of Service 将在 iOS 8 和 OS X Yosemite 中广泛的应用,所以留意所有能利用它们的机会。

LocalAuthentication

最后,最令人期待的 iOS 8 新功能之一:LocalAuthentication。自从 iPhone 5S 加入 TouchID,开发者就对它的应用前景垂涎三尺。

想象一下,只要有 CloudKit 和 LocalAuthentication,创建新账号的烦恼讲不复存在。只需要扫描一下你的手就搞定了!

LocalAuthentication 以LAContext的方式工作,验证声明的规格,然后返回是否验证成功。整个过程中,用户的生物信息都被安全的储存在硬件当中。

LAContext *context = [[LAContext alloc] init];
NSError *error = nil;

if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
                         error:&error])
{
    [context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
            localizedReason:NSLocalizedString(@"...", nil)
                      reply:^(BOOL success, NSError *error) {
        if (success) {
            // ...
        } else {
            NSLog(@"%@", error);
        }
    }];
} else {
    NSLog(@"%@", error);
}

虽然这些天每个人都在讨论 Swift,但是作为一个开发者你更应该关注的是这些 iOS 8 和 OS X Yosemite 的新 API。它们可以让你实实在在的一些事。

如果你想接着探索,dive into the iOS 7.1 to 8.0 API diffs可以让你领会这些变化的重要性。当然,4000 多的新 API,很多只是细微的改变或者将方法改为属性,但是,它们值得拥有!

作者 Mattt
Mattt

Mattt (@mattt) is a writer and developer in Portland, Oregon. He is the founder of NSHipster and Flight School, and the creator of several open source libraries, including AFNetworking and Alamofire.

翻译者
David Liu

混迹于美帝的工程师,户外运动爱好者。

下一篇文章

诊断结合了逻辑与分析来得出一个结论。这是最纯粹的科学和工程学,也是人类最有力的推理。对于我们开发者来说,我们通过代码通知后续代码的生产,创建了一个在过去半个世纪里呈几何级数发展的技术的正反馈循环。尤其对于我们的 Objective-C 开发者来说,最有效的诊断来自 Clang。