Why Foundation
首先上几张图,再引入正文:
概括来说,是这么三点:
- SDK 中独具特色
- 底层无处不在
- 上层又与我们息息相关,它建立了通用类型和设计模式
Foundation 说:给我一个支点,我能撬动整个 App。有个东西说出来你们可能不信,Foundation 目前很多都开始使用了值类型来实现。这绝壁是玩命的革命啊!
推荐 SE-0069 Mutability and Foundation Value Typess和SE-0086 Drop NS Prefix in Swift Foundation 这两个提案,基本阐述了关于如何改善 Foundation API,包括值语义、命名调整、遵循标准库协议、更多类型安全以及更加 Swift 风格的功能。
Value Types 和 Reference Types
这里简单过一遍,为之后铺垫:
// 值类型
let start = CGPoint(x: 1, y: 2)
var end = start
end.x += 8
关系图:
// 引用类型
let data = NSMutableData(withContentsOf: file1)
var otherData = data
otherData.append(NSData(withContentsOf: file2)
关系图:
关于值类型和引用类型孰优孰劣,其实是看应用场景的,并非哪个更胜一筹,而是合适不合适的问题。官方对此也表示Neither is better—just used in different ways。另外举了几个例子,让我们一睹为快:
//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
是一个单例对象,旨在让所有使用者操作同一个对象,所以假若使用值类型,那么各个持有的就是不同的对象,没有任何意义,而下面的 session
也是一样的道理。
而对于 Date
则是一个 struct
类型,因此是一个值类型,看下定义:
public struct Date : Comparable, Equatable {
private var _time : Double
}
因为我们更关心的是内容的存储,所以用值类型更合适一些。对了 Data
也是结构体类型:
public struct Data : Equatable, Hashable, RandomAccessCollection, MutableCollection{
/// The Objective-C bridged type of `Data`.
public typealias ReferenceType = NSData
}
注意到NSData
没,实际结构体中保留了一个类型为 NSData
的指针,保留对objective-c 中对象的引用。那么问题来了!既然说结构体是值类型,那么var otherData=data
后,otherData和data中指向的内存是同一块吗?答案是在你未修改之前是YES!见图:
那么修改了otherData[0] 的数据呢?那么otherData 会先copy()一份,然后修改!所以图是这样的:
此时 otherData 和 data 各自拥有一个 class NSData
实例,你修改你的,我处理我的,进水不犯河水。
那么目前有哪些新的值类型呢?这里我列出一些:
- AffineTransform
- CharacterSet
- Data
- Date
- DateComponent
- DateInterval (新类型)
- Decimal (有改动)
- IndexPath
- IndexSet
- Measurement (新类型)
- Notification
- PersonNameComponents
- URL
- URLComponents
- URLRequest
- URLQueryItem
- UUID
API 改动实例说明
Michael LeHew 介绍了很多,我抽几个值得一谈与大家分享下:
1.嵌套枚举
接下来基本都是 Objc 、Swift2.x 和 Swift3.0 的比较,前方高能,注意!!
//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个枚举类型,实际上它们都属于 NSNumberFormatter
类型,但是笼统概括到一个枚举又不是很合适,于是最终还是定义了style
、behavior
、padPosition
和roundingMode
4个枚举。在Swift2.2中桥接过来也是相当老实!一对一也是4个枚举。怎么说呢?中规中矩吧,尽管并排放在了一起,但是感觉还是有种距离感。
再来看看 Swift3:
// Swift 3
public class NumberFormatter {
public enum style { ... }
public enum behavior { ... }
public enum padPosition { ... }
public enum roundingMode { ... }
}
可以看到 NumberFormatter
类下内嵌了4个枚举,style
等和 NumberFormatter
的关系一目了然。
强类型的字符串枚举
Foundation 中定义了很多 NSString
类型的字符串常量对象,如下:
NSString *const NSProcessInfoThermalStateDidChangeNotification;
NSString *const NSTaskDidTerminateNotification;
NSString *const NSCalendarDayChangedNotification;
NSString *const NSURLIsRegularFileKey;
NSString *const NSURLCreationDateKey;
NSString *const NSURLVolumeMaximumFileSizeKey;
现在 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;
请注意 NS_EXTENSIBLE_STRING_ENUM
修饰符,它的作用是在桥接到 Swift 中时可进行枚举扩展。
// Objective-C 中我们新增一个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)
看到这里是不是对 Notification.Name
又诧异了?为此我特地看了下声明:
extension NSNotification {
public struct Name : RawRepresentable, Equatable, Hashable, Comparable {
public init(_ rawValue: String)
public init(rawValue: String)
}
}
原来 Name
是 NSNotification
的内嵌结构体,而上面的extension
不过是在对 Name
结构体做新增字段扩展操作。
类属性
这个比较简单,Objective-C 新增了一个名为 class
的特性,Objective-C 的类对象其实使用了 getter
方法间接得到:
// Objective-C (conventional class properties)
@interface NSUserDefaults
+ (NSUserDefaults *)standardUserDefaults;
@end
// 而现在Objective-C 支持类属性拉,完全可以这么做
@interface NSUserDefaults
@property (class, readonly, strong) standardUserDefaults;
@end
所以喽,swift 也做出了相应改动:
// Swift 2.2
public class NSUserDefaults {
public class func standardUserDefaults() -> NSUserDefaults
}
// Swift 3 (大部分而言)
public class UserDefaults {
public class var standardUserDefaults: UserDefaults
}
关于新的值类型
- Date
- Measurement
- URLComponents
- Data
下面通过实例来讲解
// 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
类型是引用类型,所以在❶和❷处实际分配了两次内存。另外使用 Date 的好处在于它支持时间比较,就像这样:
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 是一个值类型,那么在❷中赋值之后,components在未修改之前它们确实是指向同一片内存区域的,但是一旦修改,不好意思,copy()一份,各自打理!不妨你看看 URLComponents
定义,内部同样有一个 ReferenceType
指向Objective-C 引用类型。
接下来看下关于 Data 的改变,不过前文说到实际内部还是引用了一个 NSData
Objective-C 对象,因此我们先构造一个OC 类,继承自 NSData
。
class AllOnesData : NSData {
override func getBytes(_ buffer: UnsafeMutablePointer<Void>, length: Int) {
memset(buffer, 1, length)
}
...
}
这里我们重写了 getBytes
方法对传入指针指向的内存区域做了初始化为1的操作,也就是memset(buffer, 1, length)
。
现在我们定义一个 Date
值类型对象 ones,这样 let ones = Data(reference: AllOnesData(length: 5))
,紧接着我们将 ones 赋值给 copy
,var copy = ones
,这是的关系图应该是这样的:
此时我们对 copy 进行操作
copy.withUnsafeMutableBytes { (bytes : UnsafeMutablePointer<UInt8>) in
bytes.pointee = 0
}
那么就如前面所说,copy 会复制一份 NSData
对象修改,然后将copy中的referencetype 指向新的对象。现在是这样的:
Type Safe Access
Swift 同样有Runtime,但是与OC的区别还是蛮大,不管怎么说,Swift 中也存在很多条件在运行时才得以确定。
// 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]
上面想要实现的是传入三个keys值,然后取到对应的资源,也就是一个key对应各自的数据,values 是一个类型为[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)
取值时你会陷入无尽的 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)
此时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 }
}
可以看到所有均为只读属性,你无法对它进行修改,增加了安全性。
关于 Native Enumerations 这里不再展开,Swift 中的枚举相当强大,不妨看看我翻译的这篇文章,几乎囊括了所有用法。
此外视频最后简单介绍了一些新的桥接改动,我建议可以看专门的 Session 视频。这里也不再展开。
本文只是一个概括性的 Session,所以并没有任何具体的案例提供,零零碎碎的赶脚,但是对于只是当做了解而言还是很有价值的!倘若觉得喜欢,请点击关注并按下喜欢吧!