那些开发者应该知道但又略显模糊的iOS 8 API
无论你问任何人,他们都会告诉你:WWDC2014是他们近些时日最令人兴奋的一件事,没有硬件发布信息,完全是关于软件和开发者工具的。
2014年iOS 8和OS X Yosemite的公布将会让苹果平台成为今年最具领导力的平台, iOS的扩展性,连续性,SpriteKit增强以及SceneKit,还有Metal,Game HealthKit,HomeKit,Local Authentication和全新的拍照框架。更不用说引人注目的Xcode和Interface Builder,改进后的iTunes Connect、TestFlight、Crash Reports以及CloudKit,当然还有Swift!
还要抱怨吗? 苹果已经慷慨地放宽了对新技术的保密措施,这意味着我们现在就可以讨论那些闪亮的新东西。
本周,我们将探讨下以下列出的功能,分享一些iOS 8中每个人都应该知道的但比较模糊的API。
从现在起,NSHipster将主要使用Swift编写示例代码,当然偶尔会用Objective-C编写。夏天结束的时候,我们希望所有的现有代码样本都能移植到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.class或respondsToSelector:是检查操作系统版本的一个更不错的选择。C和Swift编译器的宏可以
用来有条件地编译基于目标配置的源。(基于target的构建配置,C或者Swift编译的宏可以有条件地编译源代码)
## 新 NSFormatter 子类 ##
Foundation框架中最为严重缺乏就是对数量单位例如质量和长度的处理能力。iOS中8和OS X Yosemite引入了三个新的类--NSEnergyFormatter、NSMassFormatter以及NSLengthFormatter,填 补了这一缺失。这有效地使Foundation框架中NSFormatter子类的数目加倍,以前仅限于NSNumberFormatter、NSDateFormatter以及NSByteCountFormatter。
虽然这些新的格式化类是Foundation框架中的一部分,但是它们主要在HealthKit中使用。
## NSEnergyFormatter ##
NSEnergyFormatter以焦耳和卡路里作为格式化能量单位,焦耳是运动锻炼时用到的单位,卡路里营养学上热量单位。
let energyFormatter = NSEnergyFormatter() energyFormatter.forFoodEnergyUse = true let joules = 10_000.0 println(energyFormatter.stringFromJoules(joules)) // "2.39 Cal"
## NSMassFormatter ##
虽然是物质存在的基本单位,但mass在HealthKit中主要指用户的重量。但还有一句忘记翻译:是的,Mass和weight是不一样的,但是在程序中,这里不是科学课程,所以不要那么迂腐了!)
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比之前做了严格的改进,可及时从离散数据点进行查询,跟踪用户的步数和距离,甚至计算用户爬了多少级楼梯。
令人惊讶的是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 ##
在支持的设备上,CMPedometer对floorsAscended/ floorsDescended的统计可使用CMAltimeter进行扩充,以获得更精细的垂直距离:
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是iOS 8中的新API,CoreMotion中的新功能体现了苹果公司的雄心勃勃的室内导航计划。这些信息将会在本地化导航应用中扮演重要的角色。
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
## CTRubyAnotationRef ##
如果你是一个语言学和文字排版的执着者,那么CoreText框架新增添的部分可能会令你起身欢呼了。
......哦对。不过这个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绘制调用中,但是结果看起来也许会是这样:
ねこ
猫
## 新的日历识别符 ##
有什么比Ruby注释更书呆子气的?iOS 8和OS X Yosemite中添加了新日历标识符。此次更新让Foundation框架更新至最新的CLDR版本。不过,在NSHipsters看来,French Republican Calendar(法国共和历)依然有自己的亮点。
新日历识别符:
- NSCalendarIdentifierCoptic: 又名亚历山大历,之前被Coptic Orthodox Church使用。
- NSCalendarIdentifierEthiopicAmeteMihret:埃塞俄比亚日历,Amete Mihret(公元8世纪左右)
- NSCalendarIdentifierEthiopicAmeteAlem:埃塞俄比亚日历,Amete Alem(公元前5493前后)
- NSCalendarIdentifierIslamicTabular:一个简单的伊斯兰历法表格,在公元622年7月15日星期四的天文时代使用。
- NSCalendarIdentifierIslamicUmmAlQura:在沙特阿拉伯使用的伊斯兰乌姆Qura日历。根据天文计算,而不是表格的行为。
## NSURLCredentialStorage ##
自去年引入NSURLSession后,Foundation的URL载入系统基本上没有太大变化。但是,这个新功能可让你更方便地以异步非闭包的形式获取和设置任务凭证。
import Foundation let session = NSURLSession() let task = session.dataTaskWithURL(NSURL(string: "http://nshipster.com"), completionHandler: { data, response, error in // ... }) let protectionSpace = NSURLProtectionSpace() NSURLCredentialStorage.getCredentialsForProtectionSpace(protectionSpace: protectionSpace, task: task, completionHandler: { credentials in // ... })
## kUTTypeToDoItem ##
对比过最新的API后,人们可能会注意到大量新的UTIs常量,最吸引我的是kUTTypeToDoItem:
import MobileCoreServices kUTTypeToDoItem // "public.to-do-item"
作为一个公众类型,现在iOS和OS X提供了一个统一的方法来共享应用程序之间的任务。如果你碰巧正在开发一个任务管理工具,那么适当地整合这个系统类型应该是你首先要做的工作。(说实话,机会是非常好的,考虑在App Store有多少这样的工具)
## kCGImageMetadataShouldExcludeGPS ##
大多数用户都完全不知道用手机拍摄的大部分照片,包含了全球定位系统(GPS)的元数据。因为这个小细节,无数个人的隐私遭受侵犯。最新的图片 I/O框架又为CGImageDestination提供一个方便的新选项:kCGImageMetadataShouldExcludeGPS,这确实 是你所期望的东西。
@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: "http://nshipster.com")) webView.loadRequest(request)
## NSQualityOfService ##
苹果框架概念基础中将不再过分强调线程这个概念。这对开发者确实一件好事。
以下这种趋势的变化在最新的API也应用于NSOperation。新的qualityOfService属性替换了ThreadPriority。这些新的语义允许应用程序推迟非关键工作,以确保始终如一的用户体验。
该NSQualityOfService枚举定义了以下值:
- UserInteractive:在实现图形密集型相关工作时使用UserInteractive QoS,比如滚动或动画。
- UserInitiated:在实现用户精确请求请求相关工作时使用UserInitiated QoS,但不要求精确到毫秒,比如动画。例如,如果用户打开email app马上查看邮件。
- Utility:Utility QoS用于执行已经由用户请求自动发生的任务。例如,电子邮件应用程序可以被配置为每隔5分钟自动检查邮件。如果系统是非常有限的资源,而电子邮件检查被推迟几分钟这也是被允许的。
- Background: Background QoS用于执行用户可能甚至都没有意识到正在发生的工作,比如email app可能使用它来执行索引搜索。
Quality of Service贯穿了IOS 8和OS X Yosemite整个Foundation,所以好好利用这一新功能吧。
## LocalAuthentication ##
最后,一个iOS 8最值得期待的功能:LocalAuthentication。自从iPhone5S引入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); }