Why Foundation
首先上幾張圖炊琉,再引入正文:
概括來(lái)說(shuō),是這么三點(diǎn):
- SDK 中獨(dú)具特色
- 底層無(wú)處不在
- 上層又與我們息息相關(guān),它建立了通用類型和設(shè)計(jì)模式
Foundation 說(shuō):給我一個(gè)支點(diǎn)苔咪,我能撬動(dòng)整個(gè) App锰悼。有個(gè)東西說(shuō)出來(lái)你們可能不信,F(xiàn)oundation 目前很多都開(kāi)始使用了值類型來(lái)實(shí)現(xiàn)团赏。這絕壁是玩命的革命盎恪!
推薦 SE-0069 Mutability and Foundation Value Typess和SE-0086 Drop NS Prefix in Swift Foundation 這兩個(gè)提案舔清,基本闡述了關(guān)于如何改善 Foundation API丝里,包括值語(yǔ)義、命名調(diào)整体谒、遵循標(biāo)準(zhǔn)庫(kù)協(xié)議杯聚、更多類型安全以及更加 Swift 風(fēng)格的功能。
Value Types 和 Reference Types
這里簡(jiǎn)單過(guò)一遍抒痒,為之后鋪墊:
// 值類型
let start = CGPoint(x: 1, y: 2)
var end = start
end.x += 8
關(guān)系圖:
// 引用類型
let data = NSMutableData(withContentsOf: file1)
var otherData = data
otherData.append(NSData(withContentsOf: file2)
關(guān)系圖:
關(guān)于值類型和引用類型孰優(yōu)孰劣幌绍,其實(shí)是看應(yīng)用場(chǎng)景的,并非哪個(gè)更勝一籌故响,而是合適不合適的問(wèn)題傀广。官方對(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ì)象贮聂,沒(méi)有任何意義,而下面的 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
沒(méi)停巷,實(shí)際結(jié)構(gòu)體中保留了一個(gè)類型為 NSData
的指針耍攘,保留對(duì)objective-c 中對(duì)象的引用。那么問(wèn)題來(lái)了畔勤!既然說(shuō)結(jié)構(gòu)體是值類型蕾各,那么var otherData=data
后,otherData和data中指向的內(nèi)存是同一塊嗎庆揪?答案是在你未修改之前是YES式曲!見(jiàn)圖:
那么修改了otherData[0] 的數(shù)據(jù)呢?那么otherData 會(huì)先copy()一份,然后修改吝羞!所以圖是這樣的:
此時(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í)例說(shuō)明
Michael LeHew 介紹了很多恨溜,我抽幾個(gè)值得一談與大家分享下:
1.嵌套枚舉
接下來(lái)基本都是 Objc 符衔、Swift2.x 和 Swift3.0 的比較,前方高能糟袁,注意E凶濉!
//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
沃缘、padPosition
和roundingMode
4個(gè)枚舉。在Swift2.2中橋接過(guò)來(lái)也是相當(dāng)老實(shí)则吟!一對(duì)一也是4個(gè)枚舉槐臀。怎么說(shuō)呢?中規(guī)中矩吧氓仲,盡管并排放在了一起水慨,但是感覺(jué)還是有種距離感。
再來(lái)看看 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)
}
}
原來(lái) Name
是 NSNotification
的內(nèi)嵌結(jié)構(gòu)體砌滞,而上面的extension
不過(guò)是在對(duì) Name
結(jié)構(gòu)體做新增字段擴(kuò)展操作。
類屬性
這個(gè)比較簡(jiǎn)單坏怪,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
下面通過(guò)實(shí)例來(lái)講解
// 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 新類型這里咱不討論
接下來(lái)說(shuō)說(shuō) 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 引用類型。
接下來(lái)看下關(guān)于 Data 的改變祠够,不過(guò)前文說(shuō)到實(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)
}
...
}
這里我們重寫(xiě)了 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)該是這樣的:
此時(shí)我們對(duì) copy 進(jìn)行操作
copy.withUnsafeMutableBytes { (bytes : UnsafeMutablePointer<UInt8>) in
bytes.pointee = 0
}
那么就如前面所說(shuō)皮获,copy 會(huì)復(fù)制一份 NSData
對(duì)象修改,然后將copy中的referencetype 指向新的對(duì)象∥圃現(xiàn)在是這樣的:
Type Safe Access
Swift 同樣有Runtime洒宝,但是與OC的區(qū)別還是蠻大,不管怎么說(shuō)萌京,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ì)陷入無(wú)盡的 as 痛苦深淵中橡庞。再來(lái)看看賦值
而 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 }
}
可以看到所有均為只讀屬性华嘹,你無(wú)法對(duì)它進(jìn)行修改吧趣,增加了安全性。
關(guān)于 Native Enumerations 這里不再展開(kāi),Swift 中的枚舉相當(dāng)強(qiáng)大强挫,不妨看看我翻譯的這篇文章岔霸,幾乎囊括了所有用法。
此外視頻最后簡(jiǎn)單介紹了一些新的橋接改動(dòng)俯渤,我建議可以看專門(mén)的 Session 視頻呆细。這里也不再展開(kāi)。
本文只是一個(gè)概括性的 Session八匠,所以并沒(méi)有任何具體的案例提供絮爷,零零碎碎的趕腳,但是對(duì)于只是當(dāng)做了解而言還是很有價(jià)值的梨树!倘若覺(jué)得喜歡坑夯,請(qǐng)點(diǎn)擊關(guān)注并按下喜歡吧!