建议读一遍swift blog来了解OC与swift不同以及swift版本变动细节
OC id -> Swift Any
优点,不需要手动装箱
使得OC API更加灵活
OC | Swift2 | Swift3 |
---|---|---|
id | AnyObject | Any |
NSArray * | [AnyObject] | [Any] |
NSDictionary * | [NSObject:AnyObject] | [AnyHashable:Any] |
NSSet * | Set |
Set |
需要注意的而是swift2不再提供隐式转换,因此NS桥接的需要你显示转换
Overriding methods and conforming to protocols
命名符合OC的规范,id->Any
// Swift 2
class Foo: NSObject, NSCopying {
override func isEqual(_ x: AnyObject?) -> Bool { ... }
func copyWithZone(_ zone: NSZone?) -> AnyObject { ... }
}
// Swift 3
class Foo: NSObject, NSCopying {
override func isEqual(_ x: Any?) -> Bool { ... }
func copy(with zone: NSZone?) -> Any { ... }
}
Untyped Collections
隐式桥接不存在了,需要使用as,swift3导入的cocoa接受了Any/AnyHashable,因此对于集合也可以采用[AnyHashable:Any]
// Swift 2
struct State {
var name: String
var abbreviation: String
var population: Int
var asPropertyList: [NSObject: AnyObject] {
var result: [NSObject: AnyObject] = [:]//也可以使用NSDictionary
// Implicit conversions turn String into NSString here…
result["name"] = self.name
result["abbreviation"] = self.abbreviation
// …and Int into NSNumber here.
result["population"] = self.population
return result
}
}
let california = State(name: "California",
abbreviation: "CA",
population: 39_000_000)
NSNotification(name: "foo", object: nil,
userInfo: california.asPropertyList)
// Swift 3
struct State {
var name: String
var abbreviation: String
var population: Int
// Change the dictionary type to [AnyHashable: Any] here...
var asPropertyList: [AnyHashable: Any] {
var result: [AnyHashable: Any] = [:]
// No implicit conversions necessary, since String and Int are subtypes
// of Any and AnyHashable
result["name"] = self.name
result["abbreviation"] = self.abbreviation
result["population"] = self.population
return result
}
}
let california = State(name: "California",
abbreviation: "CA",
population: 39_000_000)
// ...and you can still use it with Cocoa API here
Notification(name: "foo", object: nil,
userInfo: california.asPropertyList)
The AnyHashable Type
Any类型可以持有任何类型,但是Dictionary、Set需要Hasble,AnyHashble是swift3提出的hashable的超类,任何一个可hash的类型都实现了Anyhashble协议,比如String、Int
Explicit Conversion for Unbridged Contexts
在一些限定的场合swift不能直接桥接C和OC的设计,比如id*,这个时候api将会显示UnsafePointer
// ObjC
@interface Foo
- (void)updateString:(NSString **)string;
- (void)updateObject:(id *)obj;
@end
// Swift
func interactWith(foo: Foo) -> (String, Any) {
var string = "string" as NSString // explicit conversion
foo.updateString(&string) // parameter imports as UnsafeMutablePointer
let finishedString = string as String
var object = "string" as AnyObject
foo.updateObject(&object) // parameter imports as UnsafeMutablePointer
let finishedObject = object as Any
return (finishedString, finishedObject)
}
此外,OC的协议是类协议,不能用结构体、枚举或其他轻量级的通用类型遵守OC的协议
AnyObject Member Lookup
Any没有AnyObject的查找行为艺术,因此不能动态的向Any发送消息,但是AnyObject可以,此时需要转换
// Swift 2
func foo(x: NSArray) {
// Invokes -description by magic AnyObject lookup
print(x[0].description)
}
// Swift 3
func foo(x: NSArray) {
// Result of subscript is now Any, needs to be coerced to get method lookup
print((x[0] as AnyObject).description)//也可以转换到你期望的指定类型as!NSOjbect
}
Swift Value Types in Objective-C
Any可以持有任何的结构体、枚举、元组或者其他的类型,OC的id在swift3等价于any,在swift2中需要手动封箱或者转入类,swift3则不需要
// Swift 2
struct CreditCard { number: UInt64, expiration: NSDate }
let PaymentMade = "PaymentMade"
// We can't attach CreditCard directly to the notification, since it
// isn't a class, and doesn't bridge.
// Wrap it in a Box class.
class Box {
let value: T
init(value: T) { self.value = value }
}
let paymentNotification =
NSNotification(name: PaymentMade,
object: Box(value: CreditCard(number: 1234_0000_0000_0000,
expiration: NSDate())))
// Swift 3
let PaymentMade = Notification.Name("PaymentMade")
// We can associate the CreditCard value directly with the Notification
let paymentNotification =
Notification(name: PaymentMade,
object: CreditCard(number: 1234_0000_0000_0000,
expiration: Date()))
需要注意的是swift3中对于常见的结构体类型将会桥接作为透明对象而不是cocoa对象,Int、UInt、Double、Bool会桥接为NSNumber,Int8,UInt16则仅仅桥接为透明对象。如果遇到了unrecognized selector sent to _SwiftValue
问题,它表示OC尝试唤醒一个方法在swift 值类型上,此时我们需要手动管理
swift Any持有任一类型包括Optional,尽管OC api要求是nonull id,Optional在未解包的情况下也可以作为参数传递,会导致runtime error而不是编译错误。swift3.0.1+Xoce8.1解决了上述问题,为了避免兼容问题,不要依赖于透明对象因为未来的swift可能会桥接到固定类型
Working with JSON in Swift
JSONSerialization Foundation framework
Extracting Values from JSON
JSONSerialization类方法jsonObject返回Any类型并且扔出异常如果data不能解析
import Foundation
let data: Data // received from a network request, for example
let json = try? JSONSerialization.jsonObject(with: data, options: [])
json顶层对象一般为字典或者数组,我们可以使用as?+if进行判断转换
// Example JSON with object root:
/*
{
"someKey": 42.0,
"anotherKey": {
"someNestedKey": true
}
}
*/
if let dictionary = jsonWithObjectRoot as? [String: Any] {
if let number = dictionary["someKey"] as? Double {
// access individual value in dictionary
}
for (key, value) in dictionary {
// access all key / value pairs in dictionary
}
if let nestedDictionary = dictionary["anotherKey"] as? [String: Any] {
// access nested dictionary values by key
}
}
// Example JSON with array root:
/*
[
"hello", 3, true
]
*/
if let array = jsonWithArrayRoot as? [Any] {
if let firstObject = array.first {
// access individual object in array
}
for object in array {
// access all objects in array
}
for case let string as String in array {
// access only string values in array
}
}
Creating Model Objects from Values Extracted from JSON
假设有个饭店的model
import Foundation
struct Restaurant {
enum Meal: String {
case breakfast, lunch, dinner
}
let name: String
let location: (latitude: Double, longitude: Double)
let meals: Set
}
来自sever的JSON数据
{
"name": "Caffè Macs",
"coordinates": {
"lat": 37.330576,
"lng": -122.029739
},
"meals": ["breakfast", "lunch", "dinner"]
}
Writing an Optional JSON Initializer
extension Restaurant {
init?(json: [String: Any]) {
guard let name = json["name"] as? String,
let coordinatesJSON = json["coordinates"] as? [String: Double],
let latitude = coordinatesJSON["lat"],
let longitude = coordinatesJSON["lng"],
let mealsJSON = json["meals"] as? [String]
else {
return nil
}
var meals: Set = []
for string in mealsJSON {
guard let meal = Meal(rawValue: string) else {
return nil
}
meals.insert(meal)
}
self.name = name
self.coordinates = (latitude, longitude)
self.meals = meals
}
}
Writing a JSON Initializer with Error Handling
enum SerializationError: Error {
case missing(String)
case invalid(String, Any)
}
extension Restaurant {
init(json: [String: Any]) throws {
// Extract name
guard let name = json["name"] as? String else {
throw SerializationError.missing("name")
}
// Extract and validate coordinates
guard let coordinatesJSON = json["coordinates"] as? [String: Double],
let latitude = coordinatesJSON["lat"],
let longitude = coordinatesJSON["lng"]
else {
throw SerializationError.missing("coordinates")
}
let coordinates = (latitude, longitude)
guard case (-90...90, -180...180) = coordinates else {
throw SerializationError.invalid("coordinates", coordinates)
}
// Extract and validate meals
guard let mealsJSON = json["meals"] as? [String] else {
throw SerializationError.missing("meals")
}
var meals: Set = []
for string in mealsJSON {
guard let meal = Meal(rawValue: string) else {
throw SerializationError.invalid("meals", string)
}
meals.insert(meal)
}
// Initialize properties
self.name = name
self.coordinates = coordinates
self.meals = meals
}
}
Interactive Playgrounds
关于这里可以去看我的简文playground正确使用姿势
Literals in Playgrounds
Xcode7.1开始支持字面量,主要用于颜色、图片、文件
Strings in Swift 2
关于这里可以去看我的简文你真的懂swift string吗?
Increasing Performance by Reducing Dynamic Dispatch
swift允许重写超类的方法和属性,这就需要在运行时间接的访问并且执行间接调用。这个技术叫做动态派发,这项技术增加了语言表达的复杂性和大量的runtime消耗在间接的使用上。下面介绍三种方式消除动态派发:final、private、WholeModule Optimization
请看下面的代码
class ParticleModel {
var point = ( 0.0, 0.0 )
var velocity = 100.0
func updatePoint(newPoint: (Double, Double), newVelocity: Double) {
point = newPoint
velocity = newVelocity
}
func update(newP: (Double, Double), newV: Double) {
updatePoint(newP, newVelocity: newV)
}
}
var p = ParticleModel()
for i in stride(from: 0.0, through: 360, by: 1.0) {
p.update((i * sin(i), i), newV:i*1000)
}
就像上面写的,编译器发出动态派发调用
- 调用p 的 update
- 调用p 的 updatePoint
- 获取p 的 point元祖属性
- 获取p 的 速率
此处使用动态派发的原因在于ParticleModel的子类可能通过可计算属性重写point、velocity,也重写update、updatePonit
动态派发调用的实现是通过查找method table然后执行间接调用。着相对于直接调用的速度肯定是慢的。
Use final when you know that a declaration does not need to be overridden
final关键字能够限制class、method、property不被重写。能够安全的使得编译器取消动态派发。point、velocity、updatePoint不进行动态派发,直接访问,update进行动态派发
class ParticleModel {
final var point = ( x: 0.0, y: 0.0 )
final var velocity = 100.0
final func updatePoint(newPoint: (Double, Double), newVelocity: Double) {
point = newPoint
velocity = newVelocity
}
func update(newP: (Double, Double), newV: Double) {
updatePoint(newP, newVelocity: newV)
}
}
final修饰类的时候,表明不能被子类化,因此指明函数、属性都是final
final class ParticleModel {
var point = ( x: 0.0, y: 0.0 )
var velocity = 100.0
// ...
}
Infer final on declarations referenced in one file by applying the private keyword.
使用private关键字,限制在当前文件内,如果当前文件内没有对该class的重写,那么编译器就会推断它不适用动态派发
class ParticleModel {
private var point = ( x: 0.0, y: 0.0 )
private var velocity = 100.0
private func updatePoint(newPoint: (Double, Double), newVelocity: Double) {
point = newPoint
velocity = newVelocity
}
func update(newP: (Double, Double), newV: Double) {
updatePoint(newP, newVelocity: newV)
}
}
与final一样,private也可以修饰class
Use Whole Module Optimization to infer final on internal declarations.
internal默认的访问控制权限表明仅能够在模块可见。swift编译文件是模块独立的,无法确定internal声明在不同的文件是否被重写了。但是如果整个模块的优化是开启的,所有的模块同时编译、能够允许编译器将internal推断它的可见性
public class ParticleModel {
var point = ( x: 0.0, y: 0.0 )
var velocity = 100.0
func updatePoint(newPoint: (Double, Double), newVelocity: Double) {
point = newPoint
velocity = newVelocity
}
public func update(newP: (Double, Double), newV: Double) {
updatePoint(newP, newVelocity: newV)
}
}
var p = ParticleModel()
for i in stride(from: 0.0, through: times, by: 1.0) {
p.update((i * sin(i), i), newV:i*1000)
}
Nullability and Objective-C
其实对应的就是可选值
- !表示非空
- ?可空