WWDC2016 Session 207 - What’s New in Foundation for Swift

Why Foundation

首先上幾張圖,再引入正文:


圖1.png
圖2.png

概括來說,是這么三點(diǎn):

  • SDK 中獨(dú)具特色
  • 底層無處不在
  • 上層又與我們息息相關(guān),它建立了通用類型和設(shè)計(jì)模式

Foundation 說:給我一個(gè)支點(diǎn),我能撬動(dòng)整個(gè) App。有個(gè)東西說出來你們可能不信,F(xiàn)oundation 目前很多都開始使用了值類型來實(shí)現(xiàn)。這絕壁是玩命的革命??!

推薦 SE-0069 Mutability and Foundation Value TypessSE-0086 Drop NS Prefix in Swift Foundation 這兩個(gè)提案,基本闡述了關(guān)于如何改善 Foundation API,包括值語義、命名調(diào)整、遵循標(biāo)準(zhǔn)庫協(xié)議、更多類型安全以及更加 Swift 風(fēng)格的功能。

Value Types 和 Reference Types

這里簡單過一遍,為之后鋪墊:

// 值類型
let start = CGPoint(x: 1, y: 2)
var end = start
end.x += 8

關(guān)系圖:


Screen Shot 2016-06-24 at 9.06.38 PM.png
// 引用類型
let data = NSMutableData(withContentsOf: file1)
var otherData = data
otherData.append(NSData(withContentsOf: file2)

關(guān)系圖:


Screen Shot 2016-06-24 at 9.06.44 PM.png

關(guān)于值類型和引用類型孰優(yōu)孰劣,其實(shí)是看應(yīng)用場(chǎng)景的,并非哪個(gè)更勝一籌,而是合適不合適的問題。官方對(duì)此也表示Neither is better—just used in different ways。另外舉了幾個(gè)例子,讓我們一睹為快:

//OperationQueue.main

class OperationQueue : NSObject { 
  class var main: OperationQueue
}

//URLSession.delegate
public protocol URLSessionDataDelegate : URLSessionTaskDelegate { 
      optional public func urlSession(_ session: URLSession,
                                                dataTask: URLSessionDataTask,
                                                didReceive data: Data)
}

這里 main 是一個(gè)單例對(duì)象,旨在讓所有使用者操作同一個(gè)對(duì)象,所以假若使用值類型,那么各個(gè)持有的就是不同的對(duì)象,沒有任何意義,而下面的 session也是一樣的道理。

而對(duì)于 Date 則是一個(gè) struct 類型,因此是一個(gè)值類型,看下定義:

public struct Date : Comparable, Equatable { 
    private var _time : Double
}

因?yàn)槲覀兏P(guān)心的是內(nèi)容的存儲(chǔ),所以用值類型更合適一些。對(duì)了 Data 也是結(jié)構(gòu)體類型:

public struct Data : Equatable, Hashable, RandomAccessCollection, MutableCollection{
    /// The Objective-C bridged type of `Data`.
    public typealias ReferenceType = NSData
}

注意到NSData沒,實(shí)際結(jié)構(gòu)體中保留了一個(gè)類型為 NSData 的指針,保留對(duì)objective-c 中對(duì)象的引用。那么問題來了!既然說結(jié)構(gòu)體是值類型,那么var otherData=data后,otherData和data中指向的內(nèi)存是同一塊嗎?答案是在你未修改之前是YES!見圖:

Screen Shot 2016-06-24 at 9.20.36 PM.png

那么修改了otherData[0] 的數(shù)據(jù)呢?那么otherData 會(huì)先copy()一份,然后修改!所以圖是這樣的:

Screen Shot 2016-06-24 at 9.25.03 PM.png

此時(shí) otherData 和 data 各自擁有一個(gè) class NSData 實(shí)例,你修改你的,我處理我的,進(jìn)水不犯河水。

那么目前有哪些新的值類型呢?這里我列出一些:

  • AffineTransform
  • CharacterSet
  • Data
  • Date
  • DateComponent
  • DateInterval (新類型)
  • Decimal (有改動(dòng))
  • IndexPath
  • IndexSet
  • Measurement (新類型)
  • Notification
  • PersonNameComponents
  • URL
  • URLComponents
  • URLRequest
  • URLQueryItem
  • UUID

API 改動(dòng)實(shí)例說明

Michael LeHew 介紹了很多,我抽幾個(gè)值得一談與大家分享下:

1.嵌套枚舉

接下來基本都是 Objc 、Swift2.x 和 Swift3.0 的比較,前方高能,注意?。?/p>

//Objc
typedef NS_ENUM(NSUInteger, NSNumberFormatterStyle) { ... }
typedef NS_ENUM(NSUInteger, NSNumberFormatterBehavior) { ... }
typedef NS_ENUM(NSUInteger, NSNumberFormatterPadPosition) { ... }
typedef NS_ENUM(NSUInteger, NSNumberFormatterRoundingMode) { ... }
// Swift 2.2
public enum NSNumberFormatterStyle : UInt { ... }
public enum NSNumberFormatterBehavior : UInt { ... }
public enum NSNumberFormatterPadPosition : UInt { ... }
public enum NSNumberFormatterRoundingMode : UInt { ... }

首先 Objective-C 中定義了的4個(gè)枚舉類型,實(shí)際上它們都屬于 NSNumberFormatter類型,但是籠統(tǒng)概括到一個(gè)枚舉又不是很合適,于是最終還是定義了style、behavior、padPositionroundingMode4個(gè)枚舉。在Swift2.2中橋接過來也是相當(dāng)老實(shí)!一對(duì)一也是4個(gè)枚舉。怎么說呢?中規(guī)中矩吧,盡管并排放在了一起,但是感覺還是有種距離感。

再來看看 Swift3:

// Swift 3
public class NumberFormatter { 
  public enum style { ... } 
  public enum behavior { ... } 
  public enum padPosition { ... } 
  public enum roundingMode { ... }
}

可以看到 NumberFormatter 類下內(nèi)嵌了4個(gè)枚舉,style等和 NumberFormatter的關(guān)系一目了然。

強(qiáng)類型的字符串枚舉

Foundation 中定義了很多 NSString 類型的字符串常量對(duì)象,如下:

NSString *const NSProcessInfoThermalStateDidChangeNotification;
NSString *const NSTaskDidTerminateNotification;
NSString *const NSCalendarDayChangedNotification;

NSString *const NSURLIsRegularFileKey;
NSString *const NSURLCreationDateKey;
NSString *const NSURLVolumeMaximumFileSizeKey;

現(xiàn)在 Objective-C 別名了新類型替換掉這些礙眼的 NSString類型。

typedef NSString *NSNotificationName NS_EXTENSIBLE_STRING_ENUM;
NSNotificationName *const NSProcessInfoThermalStateDidChangeNotification;
NSNotificationName *const NSTaskDidTerminateNotification;
NSNotificationName *const NSCalendarDayChangedNotification;

NSURLResourceKey *const NSURLIsRegularFileKey;
NSURLResourceKey *const NSURLCreationDateKey;
NSURLResourceKey *const NSURLVolumeMaximumFileSizeKey;

請(qǐng)注意 NS_EXTENSIBLE_STRING_ENUM 修飾符,它的作用是在橋接到 Swift 中時(shí)可進(jìn)行枚舉擴(kuò)展。

// Objective-C 中我們新增一個(gè)NSNotificationName 的常量是這樣的
extern NSNotificationName const MyUserBecameActiveNotification;

// 而Swift 3 是這樣的
public extension Notification.Name { 
  public static let userLoggedOut = Notification.Name("UserLoggedOut")
}
let n = Notification(name: .userLoggedOut, object: nil)

看到這里是不是對(duì) Notification.Name 又詫異了?為此我特地看了下聲明:

extension NSNotification {
    public struct Name : RawRepresentable, Equatable, Hashable, Comparable {
        public init(_ rawValue: String)
        public init(rawValue: String)
    }
}

原來 NameNSNotification 的內(nèi)嵌結(jié)構(gòu)體,而上面的extension不過是在對(duì) Name結(jié)構(gòu)體做新增字段擴(kuò)展操作。

類屬性

這個(gè)比較簡單,Objective-C 新增了一個(gè)名為 class 的特性,Objective-C 的類對(duì)象其實(shí)使用了 getter 方法間接得到:

// Objective-C (conventional class properties)
@interface NSUserDefaults
+ (NSUserDefaults *)standardUserDefaults;
@end

// 而現(xiàn)在Objective-C 支持類屬性拉,完全可以這么做
@interface NSUserDefaults
@property (class, readonly, strong) standardUserDefaults;
@end

所以嘍,swift 也做出了相應(yīng)改動(dòng):

// Swift 2.2
public class NSUserDefaults { 
  public class func standardUserDefaults() -> NSUserDefaults
}

// Swift 3 (大部分而言)
public class UserDefaults { 
    public class var standardUserDefaults: UserDefaults
}

關(guān)于新的值類型

  • Date
  • Measurement
  • URLComponents
  • Data

下面通過實(shí)例來講解

// Swift 2.2
func whenToLeave() -> NSDate { ... }
let date = whenToLeave()//?
let reminder = date.dateByAddingTimeInterval(-5.0 * 60.0)//?

// Swift 3
func whenToLeave() -> Date { ... }
var date = whenToLeave().addTimeInterval(-5.0 * 60.0)

由于 NSDate類型是引用類型,所以在?和?處實(shí)際分配了兩次內(nèi)存。另外使用 Date 的好處在于它支持時(shí)間比較,就像這樣:

func whenToLeave() -> Date { ... }
let when = whenToLeave().addingTimeInterval(-5.0 * 60.0)
if Date() < when {
timer = Timer(fireDate: when, interval: 0, repeats: false) {
 print("Almost time to go!")
}
 RunLoop.main.add(timer, forMode: .commonModes)
} else {
 print("You're late!")}

Measurement 新類型這里咱不討論

接下來說說 URLComponents

var template = URLComponents()//?
template.scheme = "https"
template.host = "www.apple.com"
template.path = "/shop/buy-mac"
template.queryItems = [URLQueryItem(name: "step", value: "detail")]
var urls = Array<URLComponents>()
for product in ["MacBook", "MacBook Pro"]  {
    var components = template//?
    components.queryItems!.append(URLQueryItem(name: "product", value: product))
    urls.append(components)
}

首先? template 是一個(gè)值類型,那么在?中賦值之后,components在未修改之前它們確實(shí)是指向同一片內(nèi)存區(qū)域的,但是一旦修改,不好意思,copy()一份,各自打理!不妨你看看 URLComponents 定義,內(nèi)部同樣有一個(gè) ReferenceType 指向Objective-C 引用類型。

接下來看下關(guān)于 Data 的改變,不過前文說到實(shí)際內(nèi)部還是引用了一個(gè) NSData Objective-C 對(duì)象,因此我們先構(gòu)造一個(gè)OC 類,繼承自 NSData。

class AllOnesData : NSData { 
  override func getBytes(_ buffer: UnsafeMutablePointer<Void>, length: Int) {
       memset(buffer, 1, length)
   }
       ...
}

這里我們重寫了 getBytes 方法對(duì)傳入指針指向的內(nèi)存區(qū)域做了初始化為1的操作,也就是memset(buffer, 1, length)。

現(xiàn)在我們定義一個(gè) Date 值類型對(duì)象 ones,這樣 let ones = Data(reference: AllOnesData(length: 5)),緊接著我們將 ones 賦值給 copy,var copy = ones,這是的關(guān)系圖應(yīng)該是這樣的:

Screen Shot 2016-06-24 at 11.23.07 PM.png

此時(shí)我們對(duì) copy 進(jìn)行操作

copy.withUnsafeMutableBytes { (bytes : UnsafeMutablePointer<UInt8>) in
   bytes.pointee = 0
}

那么就如前面所說,copy 會(huì)復(fù)制一份 NSData對(duì)象修改,然后將copy中的referencetype 指向新的對(duì)象。現(xiàn)在是這樣的:

Screen Shot 2016-06-24 at 11.25.11 PM.png

Type Safe Access

Swift 同樣有Runtime,但是與OC的區(qū)別還是蠻大,不管怎么說,Swift 中也存在很多條件在運(yùn)行時(shí)才得以確定。

// Swift 2.2
let url = NSURL.fileURL(withPath: "/my-special-file")
let keys = [NSURLCreationDateKey, NSURLIsRegularFileKey, NSURLVolumeMaximumFileSizeKey]
let values = try url.resourceValues(forKeys: keys)// values的類型為[String,AnyObject]

上面想要實(shí)現(xiàn)的是傳入三個(gè)keys值,然后取到對(duì)應(yīng)的資源,也就是一個(gè)key對(duì)應(yīng)各自的數(shù)據(jù),values 是一個(gè)類型為[String,AnyObject]的字典類型,取值和賦值操作:

if values[NSURLIsRegularFileKey] as! Boolean { ... }
if let maxSize = (values[NSURLVolumeMaximumFileSizeKey] as? Int) { ... }

var newValues = values
newValues[NSURLIsRegularFileKey] = false
newValues[NSURLCreationDateKey] = "Two Days Ago"
try url.setResourceValues(newValues)

取值時(shí)你會(huì)陷入無盡的 as 痛苦深淵中。再來看看賦值

而 Swift3 提供的方式是這樣的:

// Swift 3
let url = URL(fileURLWithPath: "/my-special-file")
let keys : Set<URLResourceKey> = [.creationDateKey,
                                  .isRegularFileKey,
                                  .volumeMaximumFileSizeKey]
let values = try url.resourceValues(forKeys: keys)

此時(shí)values的類型是struct URLResourceValues。類型明確,這樣就可以摒棄太多的 as 操作了。 那么struct URLResourceValues又是如何定義呢?

public struct URLResourceValues {
   ...
   public var creationDate: Date? { get set }
   public var isRegularFile: Bool? { get }
   public var volumeMaximumFileSize: Int? { get }
   ...
   public var allValues: [URLResourceKey : AnyObject] { get }
}

可以看到所有均為只讀屬性,你無法對(duì)它進(jìn)行修改,增加了安全性。

關(guān)于 Native Enumerations 這里不再展開,Swift 中的枚舉相當(dāng)強(qiáng)大,不妨看看我翻譯的這篇文章,幾乎囊括了所有用法。

此外視頻最后簡單介紹了一些新的橋接改動(dòng),我建議可以看專門的 Session 視頻。這里也不再展開。

本文只是一個(gè)概括性的 Session,所以并沒有任何具體的案例提供,零零碎碎的趕腳,但是對(duì)于只是當(dāng)做了解而言還是很有價(jià)值的!倘若覺得喜歡,請(qǐng)點(diǎn)擊關(guān)注并按下喜歡吧!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容