iOS装13-swift

swift概述

swift 可以开发iOS, macOS, watchOS, 和 tvOS apps 。

swift目前版本3.1

Xcode版本8.3.2

官方The Swift Programming Language (Swift 3.1)

电子书下载

swift语法学习可以新建一个playground项目

Swift快速预览

swift打印hellow world不需要main函数 不需要import include,只需要一句

print("Hello, world!")

简单的值

用let声明一个常量用var来声明一个变量。

let a: Int = Int(10.0)
var c = 20
var b: Double = 1

如果将浮点型赋值给整型 必须用构造的形式,Int ,如果将整型赋值给浮点型,不需要用构造,直接赋值

另外如果不做任何处理就把nil赋值给常量或变量是不允许的,必须在=前面加上?

let aa0: String? = nil //yes
let aa0: String = nil //no

字符串声明与拼接

let label = "The width is"
let widthLabel = label + String(c)
let helloString = "the var c = \(c) ;"

数组必须是指定的特殊类型,字典的key必须是hashable类型的,比如Int、String、Double、Float

var array = ["a","b","c"]
array[0] = "hh"
var dic = [22:111,33:22]

声明一个空的数组

var aaa = [String]() //yes
var aaa = []         //no

数组添加一个元素

aaa.append("aaa") //yes
aaa[0] = "aaa"  //no

数组删除一个元素

aaa.remove(at: 0)

修改一个数组的变量的值为空数组

aaa = []  

数组遍历

for a in aaa{
    print("\(a)")
}

声明一个空的字典

var aaa = [String: Float]() //yes
var aaa = [:]         //no

字典添加一个元素

dicEmpty.updateValue("0000", forKey: 0)  //yes
dicEmpty[1] = "222"  //yes

字典删除一个元素

dicEmpty.removeValue(forKey: 1)

字典遍历

for (a,b) in dicEmpty{
    print("\(a) \(b)")
}

控制流

用if 和switch 做条件判断,用for in ,for ,while ,repeat-while来做作循环

先来看一个没啥变化的

let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
    if score > 50 {
        teamScore += 3
    } else {
        teamScore += 1
    }
}
print(teamScore)

可以通过let a = 的形式来判断一个变量是否为nil,下面两种形式同样效果

let aa0: String? = nil
if let a = aa0 {
    print("my name  = \(a)")
}
if aa0 != nil {
    print("my name  = \(a)")
}

两个??可以用来表示如果值为nil使用默认值

let nickName: String? =nil
let fullName: String = "quanGe"
let str = "hi \(nickName??fullName)"

switch在swift中无需使用break,程序会自动跳出,如果不想跳出可以用链接使用,另外条件也可以使用let作判断条件,示例如下

let vegetable = "red pepper"
switch vegetable {
case "celery":
    print("Add some raisins and make ants on a log.")
case "cucumber", "watercress":
    print("That would make a good tea sandwich.")
case let x where x.hasSuffix("pepper"):
    print("Is it a spicy \(x)?")
default:
    print("Everything tastes good in soup.")
}

while单纯使用没什么变化,多了一个repeat,while的用法,

var m = 2
repeat {
    m *= 2
} while m < 100
print(m)

for in的时候相当于之前oc中的for(int i=0;i<5;i++)。这里需要注意..<不包含后面的值,...包含后面的值,注意是三个.

for i in 0...5{
    print("\(i)")
}

函数和闭包

普通函数的返回值和输入参数写法与oc有所不同

func greet(person: String, day: String) -> String {
    return "Hello \(person), today is \(day)."
}
greet(person: "Bob", day: "Tuesday")

可以不写第一个参数的label,或者重命名label

func greet(_ person: String, on day: String) -> String {
    return "Hello \(person), today is \(day)."
}
greet("John", on: "Wednesday")

返回值可以不是一个值,可以是一个组合

func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) {
    var min = scores[0]
    var max = scores[0]
    var sum = 0
    
    for score in scores {
        if score > max {
            max = score
        } else if score < min {
            min = score
        }
        sum += score
    }
    
    return (min, max, sum)
}
let statistics = calculateStatistics(scores: [5, 3, 100, 3, 9])
print(statistics.sum)
print(statistics.2)

函数参数可以是不固定参数

func sumOf(numbers: Int...) -> Int {
    var sum = 0
    for number in numbers {
        sum += number
    }
    return sum
}
sumOf()
sumOf(numbers: 42, 597, 12)

函数可以嵌套函数

func returnFifteen() -> Int {
    var y = 10
    func add() {
        y += 5
    }
    add()
    return y
}
returnFifteen()

函数可以作为返回值来返回

func makeIncrementer() -> ((Int) -> Int) {
    func addOne(number: Int) -> Int {
        return 1 + number
    }
    return addOne
}
var increment = makeIncrementer()
increment(7)

函数可以用另一个函数作为参数

func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
    for item in list {
        if condition(item) {
            return true
        }
    }
    return false
}
func lessThanTen(number: Int) -> Bool {
    return number < 10
}
var numbers = [20, 19, 7, 12]
hasAnyMatches(list: numbers, condition: lessThanTen)

block的使用{ in }

numbers.map({ (number: Int) -> Int in
    let result = 3 * number
    return result
})

当变量的类型可以确定的情况下可以省略

numbers.map({ number in 3 * number
})

你可以用数字来替代变量。当block当作仅有的一个参数时()可以省略

let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers)

类和对象

类中的property和上面的变量一样声明。常量用let。创建一个对象用类名加上(),如果init函数有其他参数,可以在类名后面的()里面加上参数。访问一个类的property和方法都用.

class EquilateralTriangle: NamedShape {
    var sideLength: Double = 0.0
    
    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 3
    }
    
    var perimeter: Double {
        get {
            return 3.0 * sideLength
        }
        set {
            sideLength = newValue / 3.0
        }
    }
    
    override func simpleDescription() -> String {
        return "An equilateral triangle with sides of length \(sideLength)."
    }
}
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
print(triangle.perimeter)
triangle.perimeter = 9.9
print(triangle.sideLength)

willSetdidSet可以在property的值发生改变之前或之后调用,除了初始化值以外。

class TriangleAndSquare {
    var triangle: EquilateralTriangle {
        willSet {
            square.sideLength = newValue.sideLength
        }
    }
    var square: Square {
        willSet {
            triangle.sideLength = newValue.sideLength
        }
    }
    init(size: Double, name: String) {
        square = Square(sideLength: size, name: name)
        triangle = EquilateralTriangle(sideLength: size, name: name)
    }
}
var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
print(triangleAndSquare.square.sideLength)
print(triangleAndSquare.triangle.sideLength)
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
print(triangleAndSquare.triangle.sideLength)

当需要的东西可以是nil时,你可以在操作方法属性脚标之前写?,当?之前的东西是nil时,后面的操作就忽略了,如果你不加,但是你的值是nil,则会crash掉哦。

枚举和Structures

enum来创建一个枚举。与oc不同的是,枚举也可以有方法。

enum Rank: Int {
    case ace = 1
    case two, three, four, five, six, seven, eight, nine, ten
    case jack, queen, king
    func simpleDescription() -> String {
        switch self {
        case .ace:
            return "ace"
        case .jack:
            return "jack"
        case .queen:
            return "queen"
        case .king:
            return "king"
        default:
            return String(self.rawValue)
        }
    }
}
let ace = Rank.ace
let aceRawValue = ace.rawValue

利用init?(rawValue:)的形式创建一个枚举对象,例如

if let convertedRank = Rank(rawValue: 3) {
    let threeDescription = convertedRank.simpleDescription()
}

struct和class用法一样,唯一不同的是struct类型的对象赋值给另外一个变量的时候是拷贝,而class类型的对象则是引用计数加一

协议和扩展

利用protocol来声明一个协议 与oc不同的是struct和class 枚举都可以继承协议,只有class在修改实例变量的时候不需要mutating,其他均需要,所以一般如果协议中要定义某个方法时,如果这个协议不只针对class,最好加上mutating

protocol ExampleProtocol {
    var simpleDescription: String { get }
    mutating func adjust()
}
class SimpleClass: ExampleProtocol {
    var simpleDescription: String = "A very simple class."
    var anotherProperty: Int = 69105
    func adjust() {
        simpleDescription += "  Now 100% adjusted."
    }
}
var a = SimpleClass()
a.adjust()
let aDescription = a.simpleDescription
 
struct SimpleStructure: ExampleProtocol {
    var simpleDescription: String = "A simple structure"
    mutating func adjust() {
        simpleDescription += " (adjusted)"
    }
}
var b = SimpleStructure()
b.adjust()
let bDescription = b.simpleDescription

extension 和oc的category用法一样,用来扩展已有的类,加上一些方法

extension Int: ExampleProtocol {
    var simpleDescription: String {
        return "The number \(self)"
    }
    mutating func adjust() {
        self += 42
    }
}
print(7.simpleDescription)

错误捕捉

如果你要自定义一些错误枚举类型,需要继承自Error协议

enum PrinterError: Error {
    case outOfPaper
    case noToner
    case onFire
}

throw可以抛出异常,用throws来标记一个函数可以抛出异常

func send(job: Int, toPrinter printerName: String) throws -> String {
    if printerName == "Never Has Toner" {
        throw PrinterError.noToner
    }
    return "Job sent"
}

你可以使用do-catch来捕获异常,在do的代码块中,你把try放在可以抛异常的函数前面。在catch的代码块中,如果你不指定一个特别的名字,那么自动为error。

do {
    let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
    print(printerResponse)
} catch {
    print(error)
}

当然你也可以写不同的代码catch代码块来捕捉不同的错误。例如

do {
    let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")
    print(printerResponse)
} catch PrinterError.onFire {
    print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
    print("Printer error: \(printerError).")
} catch {
    print(error)
}

范型

比如有个这样的需求,写一段代码可以构造出任意类型的数组,用oc可以很容易,直接用NSArray就可以,但是在swift中,类型一经指定不可改变。这就需要范型

func makeArray<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] {
    var result = [Item]()
    for _ in 0..<numberOfTimes {
        result.append(item)
    }
    return result
}
makeArray(repeating: "knock", numberOfTimes:4)

范型可以应用在class、struct、枚举、方法、函数中

// Reimplement the Swift standard library's optional type
enum OptionalValue<Wrapped> {
    case none
    case some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .none
possibleInteger = .some(100)

在代码块之前写where可以加一些过滤条件例如需要这种类型必须继承自某协议或者两种类型相同、或者需要这种类型有个特定的父类

func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Bool
    where T.Iterator.Element: Equatable, T.Iterator.Element == U.Iterator.Element {
        for lhsItem in lhs {
            for rhsItem in rhs {
                if lhsItem == rhsItem {
                    return true
                }
            }
        }
        return false
}
anyCommonElements([1, 2, 3], [3])

语言指南

基础

swift中Int对应整型,Double和Float是浮点类型,Bool是布尔类型,String文本类型。可用容器有Array、Set、Dictionary。

相对于oc,swift加入了一个新的类型 tuples.即函数可以返回多个值。

swift也支持有缺省值的类型,也就是说一个变量可以有值x或者酒没有值。这就像oc中使用nil一样,不过在swift中不仅仅对class对于其他类型枚举struct也同样适用。

打印

oc中用nslog,swift中用print

注释

单行注释用// 多行使用/**/

分号

一般可以不写如果在同一行中写多句代码则需要分号;分开

整型

swift中可以使用8位16位和32位的格式。如UInt8、UInt16、UInt32、如果想获取最小值最大值可以这样let minValue = UInt8.min

swift中如果是32位手机里Int是Int32,UInt是UInt32。在64位手机里Int是Int64位,UInt是UInt64

swift中10进制不加任何前缀,二进制加0b,八进制加0o,16进制加0x

如果给一个已知的类型重命名可以用typealias例如typealias AudioSample = UInt16

布尔类型

swift中布尔类型的值是true和false

容器

swift中提供三种类型的容器分别是array、set、dictionary

swift中非常清楚容器中对象的类型,也就是说你不能往容器里存放不允许的类型的数据。

如果你创建了一个array或者set或者dictionary,并且赋值给一个变量,则都是可以编辑的,往里面添加对象删除对象,但是如果赋值给常量,则不可编辑。

array

var someInts = [Int]() //创建一个数组
someInts.append(3)      //添加一个元素
someInts = []           //清空但是还是[Int]类型

创建一个数组并给初始值

var threeDoubles = Array(repeating: 0.0, count: 3)

两个数组合并

var anotherThreeDoubles = Array(repeating: 2.5, count: 3)
var sixDoubles = threeDoubles + anotherThreeDoubles

用一个数组来初始化一个数组

var shoppingList: [String] = ["Eggs", "Milk"]
var shoppingList = ["Eggs", "Milk"]

isEmpty来判断是否是空。添加一个元素可以用append或者+=或者insert at

var shoppingList = ["Eggs", "Milk"]
if shoppingList.isEmpty {
    print("The shopping list is empty.")
} else {
    print("The shopping list is not empty.")
}
shoppingList.append("Flour")
shoppingList += ["Baking Powder"]
shoppingList += ["Chocolate Spread", "Cheese", "Butter"]
shoppingList[4...6] = ["Bananas", "Apples"]
shoppingList.insert("Maple Syrup", at: 0)
let mapleSyrup = shoppingList.remove(at: 0)

set

当你需要容器里的对象有序或者你希望容器里的对象是唯一的可以考虑用set。

set容器里的对象类型必须是hash类型的,即if a == b与 a.hashValue == b.hashValue.等价。在swift中基础类型,String、Int、Double、Bool都默认是可以hash的,可以存储在set中或者作为dictionary的key值。枚举默认也是可以hash的。

var letters = Set<Character>()
letters.insert("a")
letters = []
var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]
favoriteGenres.insert("Jazz")
if let removedGenre = favoriteGenres.remove("Rock") {
    print("\(removedGenre)? I'm over it.")
} else {
    print("I never much cared for that.")
}
for genre in favoriteGenres {
    print("\(genre)")
}

set的两个对象之间四种操作结果

Dictionaries

var namesOfIntegers = [Int: String]()
namesOfIntegers[16] = "sixteen"
namesOfIntegers = [:]
var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
if airports.isEmpty {
    print("The airports dictionary is empty.")
} else {
    print("The airports dictionary is not empty.")
}
airports["LHR"] = "London Heathrow"
for (airportCode, airportName) in airports {
    print("\(airportCode): \(airportName)")
}
for airportCode in airports.keys {
    print("Airport code: \(airportCode)")
}
for airportName in airports.values {
    print("Airport name: \(airportName)")
}
let airportCodes = [String](airports.keys)
let airportNames = [String](airports.values)

控制流

if、 while、repeat while、switch等 与oc不同的是

switch中做条件的可以是字符串类型也可以是Tuples(组合),oc中必须是数字

let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a", "A":
    print("The letter A")
default:
    print("Not the letter A")
}

let approximateCount = 62
let countedThings = "moons orbiting Saturn"
let naturalCount: String
switch approximateCount {
case 0:
    naturalCount = "no"
case 1..<5:
    naturalCount = "a few"
case 5..<12:
    naturalCount = "several"
case 12..<100:
    naturalCount = "dozens of"
case 100..<1000:
    naturalCount = "hundreds of"
default:
    naturalCount = "many"
}

let somePoint = (1, 1)
switch somePoint {
case (0, 0):
    print("\(somePoint) is at the origin")
case (_, 0):
    print("\(somePoint) is on the x-axis")
case (0, _):
    print("\(somePoint) is on the y-axis")
case (-2...2, -2...2):
    print("\(somePoint) is inside the box")
default:
    print("\(somePoint) is outside of the box")
}
//也可以使用let where做条件判断
let yetAnotherPoint = (1, -1)
switch yetAnotherPoint {
case let (x, y) where x == y:
    print("(\(x), \(y)) is on the line x == y")
case let (x, y) where x == -y:
    print("(\(x), \(y)) is on the line x == -y")
case let (x, y):
    print("(\(x), \(y)) is just some arbitrary point")
}

swift除了支持continue break 之外还支持fallthrough向下执行一个case。

let integerToDescribe = 5
var description = "The number \(integerToDescribe) is"
switch integerToDescribe {
case 2, 3, 5, 7, 11, 13, 17, 19:
    description += " a prime number, and also"
    fallthrough
default:
    description += " an integer."
}
print(description)
// Prints "The number 5 is a prime number, and also an integer."

guard else 和if(!)一样,不同的是必须guard和else一起使用

func greet(person: [String: String]) {
    guard let name = person["name"] else {
        return
    }
    
    print("Hello \(name)!")
    
    guard let location = person["location"] else {
        print("I hope the weather is nice near you.")
        return
    }
    
    print("I hope the weather is nice in \(location).")
}
 
greet(person: ["name": "John"])
// Prints "Hello John!"
// Prints "I hope the weather is nice near you."
greet(person: ["name": "Jane", "location": "Cupertino"])
// Prints "Hello Jane!"
// Prints "I hope the weather is nice in Cupertino."

检查api是否可用

if #available(iOS 10, macOS 10.12, *) {
    // Use iOS 10 APIs on iOS, and use macOS 10.12 APIs on macOS
} else {
    // Fall back to earlier iOS and macOS APIs
}

类和结构体

swift中类和结构体的共同点:

1、可以定义属性properties

2、可以定义方法

3、可以用脚标(subscript)的方式访问

4、可以定义初始化方法

5、可以给现有的类 进行扩展

6、可以遵守某个协议

不同点:

1、类可以继承

2、子类和父类转化及运行时判断类的类型

3、类可以定义析构函数

4、类支持引用计数

类是引用类型

与值类型的不同的是,引用类型在赋值给常量或者变量或者函数传参数的时候的时候并没有拷贝。

唯一符号

swift中用===来判断是是同一个实例,用!==来判断不是同一个实例

使用建议

如果你的对象中的成员都是基本类型的数据,或者对象不需要从其他类继承属性或者方法 都应该使用结构体

在swift中String、Array、Dictionary都是用结构体来实现的,这就意味着在赋值给常量或者变量,或者函数传参数的时候都是拷贝。这和oc中的NSString、NSArray、NSDictionaray是不同的,oc是以类实现的,在赋值的时候都是引用。

属性(Properties)

swift中的let 和var声明的常量和变量就是 oc中的属性。swift支持关键字lazy,用来支持不用的时候不初始化,用的时候才初始化,更节省内存。例如

class DataImporter {
    /*
     DataImporter is a class to import data from an external file.
     The class is assumed to take a non-trivial amount of time to initialize.
     */
    var filename = "data.txt"
    // the DataImporter class would provide data importing functionality here
}
 
class DataManager {
    lazy var importer = DataImporter()
    var data = [String]()
    // the DataManager class would provide data management functionality here
}
 
let manager = DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")

swift支持在属性将要改变或者已经改变的时候做一些事情

class StepCounter {
    var totalSteps: Int = 0 {
        willSet(newTotalSteps) {
            print("About to set totalSteps to \(newTotalSteps)")
        }
        didSet {
            if totalSteps > oldValue  {
                print("Added \(totalSteps - oldValue) steps")
            }
        }
    }
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// About to set totalSteps to 200
// Added 200 steps
stepCounter.totalSteps = 360
// About to set totalSteps to 360
// Added 160 steps
stepCounter.totalSteps = 896
// About to set totalSteps to 896
// Added 536 steps

类变量

有两个关键字可以实现类变量,static和class。那么什么时候该用什么呢?struct和enum用static。类中一般也用static,但是如果你想让子类重载该变量的get方法时必须用class。

struct SomeStructure {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 1
    }
}
enum SomeEnumeration {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 6
    }
}
class SomeClass {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 27
    }
    class var overrideableComputedTypeProperty: Int {
        return 107
    }
}

在Swift中class、struct和enum都是可以实现protocol的。那么如果我们想在protocol里定义一个类型域上的方法或者计算属性的话,应该用哪个关键字呢?答案是使用class进行定义

Methods 方法

结构体和枚举是值类型的,在默认情况下,如果属性属于值类型的变量则是不允许修改的,如果你非要修改的话,必须加上mutating

struct Point {
    var x = 0.0, y = 0.0
    mutating func moveBy(x deltaX: Double, y deltaY: Double) {
        x += deltaX
        y += deltaY
    }
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveBy(x: 2.0, y: 3.0)
print("The point is now at (\(somePoint.x), \(somePoint.y))")
// Prints "The point is now at (3.0, 4.0)"

Subscripts 脚标

类、结构体、枚举都支持利用脚标来访问比如

struct TimesTable {
    let multiplier: Int
    subscript(index: Int) -> Int {
        return multiplier * index
    }
}
let threeTimesTable = TimesTable(multiplier: 3)
print("six times three is \(threeTimesTable[6])")

继承

只有类可以继承,如果想重载父类的方法可以用override关键字

class Train: Vehicle {
    override func makeNoise() {
        print("Choo Choo")
    }
}

init

一般默认就是没有参数的init,和自定义的和省略参数名称的。

struct Celsius {
    var temperatureInCelsius: Double
    init(fromFahrenheit fahrenheit: Double) {
        temperatureInCelsius = (fahrenheit - 32.0) / 1.8
    }
    init(fromKelvin kelvin: Double) {
        temperatureInCelsius = kelvin - 273.15
    }
    init(_ celsius: Double) {
        temperatureInCelsius = celsius
    }
}
let bodyTemperature = Celsius(37.0)

如果在类中没有写自定义的初始化函数,则用默认的初始化函数可以将所有属性初始化默认值

class ShoppingListItem {
    var name: String?
    var quantity = 1
    var purchased = false
}
var item = ShoppingListItem()

如果在类中没有写自定义的初始化函数,还可以用属性初始化函数来进行初始化

struct Size {
    var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)

推荐初始化函数(Designated) 和 快捷初始化函数(Convenience)

在oc中,如果是推荐初始化函数可以使用NS_DESIGNATED_INITIALIZER来修饰,比如NSArray

- (instancetype)init NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithObjects:(const ObjectType _Nonnull [_Nullable])objects count:(NSUInteger)cnt NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;

这里面这三个都是推荐使用的初始化函数,你用哪个都可以。也就是说推荐函数可能不是一个。我总结了以下几个原则:

1、在创建一个实例的时候尽量使用推荐的初始化函数

2、在快捷初始化函数中一定要调用某个推荐的初始化函数,以保证对象的所有的属性都进行了初始化

3、如果是重载的父类的推荐初始化函数,如果在子类也就是本类中也是推荐初始化函数,则需要调用父类的本初始化函数,如果在本类中是快捷的初始化函数,也就是不带NS_DESIGNATED_INITIALIZER关键字,则需要调用本类中的一个推荐初始化函数。

4、如果不想让其他人使用快捷初始化函数初始化对象,比如AFNetworking中的AFNetworkReachabilityManager,不想让使用者通过init初始化则在后面加上关键字NS_UNAVAILABLE, 并且返回nil

- (instancetype)init NS_UNAVAILABLE
{
    return nil;
}

在swift中与oc相反,推荐初始化函数不需要任何写关键字,而快捷初始化函数则要在前面加上关键字convenience

class Food {
    var name: String
    init(name: String) {
        self.name = name
    }
    convenience init() {
        self.init(name: "[Unnamed]")
    }
}

如果是重载的父类的初始化函数则需要在前面加关键字override

class Vehicle {
    var numberOfWheels = 0
    var description: String {
        return "\(numberOfWheels) wheel(s)"
    }
}

class Bicycle: Vehicle {
    override init() {
        super.init()
        numberOfWheels = 2
    }
}

如果想让子类必须重载某个函数则需要在前面加上required关键字,而子类在重载的时候同样需要写关键字required,而不是override

class SomeClass {
    required init() {
        // initializer implementation goes here
    }
}
class SomeSubclass: SomeClass {
    required init() {
        // subclass implementation of the required initializer goes here
    }
}

Deinitialization 反初始化函数

顾名思义,就是在对象释放的时候调用deinit函数。在deinit函数里你可能需要做一些清理的工作,比如你在某个函数里打开了某个文件,在这里一定要关闭。

自动引用计数ARC

swift使用了ARC来管理内存。在swift中使用weak或者unowned来处理两个类之间的循环引用的问题.如果要解决闭包中循环使用的问题,则需要在使用前先前置使用列表,列表中用weakunowned来修饰。

class HTMLElement {
    
    let name: String
    let text: String?
    
    lazy var asHTML: () -> String = {
        [unowned self] in
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name) />"
        }
    }
    
    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }
    
    deinit {
        print("\(name) is being deinitialized")
    }
    
}

自判断链接(Optional Chaining )

swift中允许某个对象的值为nil,在执行的时候如果值为nil则后面的所有操作则失效,不会闪退。前提是写的时候这样写var residence: Residence?

class Person {
var residence: Residence?
}

class Residence {
var numberOfRooms = 1
}

if let roomCount = john.residence?.numberOfRooms {
    println("John's residence has \(roomCount) room(s).")
} else {
    println("Unable to retrieve the number of rooms.")
}

错误捕捉

var vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8
do {
    try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
} catch VendingMachineError.invalidSelection {
    print("Invalid Selection.")
} catch VendingMachineError.outOfStock {
    print("Out of Stock.")
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
    print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
}

类型转换

在swift中,如果判断一个对象是否是某种类型可以用is,如果将父类的对象转换成子类的对象可以用as!或者as?,如果你觉得转换会失败则用as?,如果你确认必定会转换成功则用as!

另外swift中提供两个特殊的关键字AnyAnyObject,其中Any可以代表所有的swift中的类型,AnyObject可以代表任何类型的对象。

var things = [Any]()
 
things.append(0)
things.append(0.0)
things.append(42)
things.append(3.14159)
things.append("hello")
things.append((3.0, 5.0))
things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))
things.append({ (name: String) -> String in "Hello, \(name)" })

扩展(Extensions)

swift中的Extensions有点和oc中的categories相似,只不过Extensions没有名字。其作用是对现有的类进行扩展,详细有:

1、添加属性和计算属性

2、定义对象的方法

3、提供一些新的初始化函数

4、定义脚标

5、定义和使用嵌套类型

6、让现有的类遵守某协议

extension SomeType {
    // new functionality to add to SomeType goes here
}

extension SomeType: SomeProtocol, AnotherProtocol {
    // implementation of protocol requirements goes here
}

extension Int {
    mutating func square() {
        self = self * self
    }
}
var someInt = 3
someInt.square()

extension Int {
    subscript(digitIndex: Int) -> Int {
        var decimalBase = 1
        for _ in 0..<digitIndex {
            decimalBase *= 10
        }
        return (self / decimalBase) % 10
    }
}
746381295[0]
// returns 5
746381295[1]
// returns 9

协议(protocol)

简介

在swift中类、结构体、和枚举都可以实现协议。

类型的方法或者计算属性

与oc不同的还有,协议里可以声明类的方法或者结构体枚举的方法,而不是对象的方法,其中类用class,其他的用static

还可以在协议里写计算属性哦,记得不是存储属性。

初始化函数

协议里也可以写初始化函数,如果类遵守了这种协议需要在初始化函数前面加关键字required

protocol SomeProtocol {
    init(someParameter: Int)
}
class SomeClass: SomeProtocol {
    required init(someParameter: Int) {
        // initializer implementation goes here
    }
}

作为参数适用

另外协议还可以作为类型来使用:

1、作为函数或者方法或者初始化函数的参数或者返回值

2、作为变量、常量、属性的的类型

3、容器里的数组、字典、或者其他容器的里的成员的类型

扩展中使用协议

如果类中已经实现了协议里的方法,但是没有遵守协议,可以这样写

struct Hamster {
    var name: String
    var textualDescription: String {
        return "A hamster named \(name)"
    }
}
extension Hamster: TextRepresentable {}

协议类型的容器

let things: [TextRepresentable] = [game, d12, simonTheHamster]
for thing in things {
    print(thing.textualDescription)
}

继承

在swift中协议可以继承,一个一协议可以继承多个协议

protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
    // protocol definition goes here
}

只有类才可以使用的协议

你可以在协议后面的继承列表中第一个位置写关键字class,来表明这个协议只给类使用,结构体和枚举是不允许使用的

protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
    // class-only protocol definition goes here
}

协议中的不必实现的方法

在oc中如果某些方法不是必须实现的,可以在前面加上关键字optional,在swift中如果要实现这种功能必须要在协议和不必实现的方法前面加上@objc

@objc protocol CounterDataSource {
    @objc optional func increment(forCount count: Int) -> Int
    @objc optional var fixedIncrement: Int { get }
}

协议的扩展

在swift中协议也是可以扩展的

extension RandomNumberGenerator {
    func randomBool() -> Bool {
        return random() > 0.5
    }
}

泛型

swift中泛型是为了对象解决类型不一样,但是处理的代码都一样的问题,如果这些对象属于不同的类,而这些类不是继承自同一个基类,相互之间没有任何关系,那么可以用泛型来解决问题。比如一个交换函数,可以交换字符串、整型等等。使用起来先用<T>声明,然后用T来代表类型。

func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
}

struct Stack<Element>: Container {
    // original Stack<Element> implementation
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
    // conformance to the Container protocol
    mutating func append(_ item: Element) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Element {
        return items[i]
    }
}

作用域

在开始讲swift的作用域之前我们要先了解一个关键词module,也就是模块。这跟git中的submodule有点类似,在iOS中一个可以发布的模块,比如一个framework或者一个程序,总之他们可以被其他的模块用import来访问。另外一个关键字是entity,是指属性、变量、常量、方法、函数等,都可以成为entity,实体。

swift中的作用域有五种:

1、openpublic可以定义实体在本模块中使用,可以在其他模块通过import使用。至于这两者区别在于open可以在其他module中被继承和重载,而public则不可以。

2、internal只能在本模块中使用。

3、fileprivate只能在本文件中使用

4、private作用于某个类,比如class A中如果变量a是private那么除了A类以外的类都不能访问了。另外如果在类中变量定义成了private类型的,它的扩展中是访问不了的,需要定义为fileprivate。

默认作用域

默认是internal即只能在本模块中使用,也有特别的,如元组(Tuple),如果元组返回两个元素一个为private一个internal,则最终这个元组的访问级别是private。

其他注意事项

1、子类的访问级别不能高于父类,如果父类类的访问级别是internal,则子类类的作用域不能是public,至于成员变量、方法、初始化函数、也就是实体,可以通过重载的方式改变为高的。

2、如果你定一个public访问级别的协议,那么这个协议里的所有实体默认是public,这和其他的类型不同,其他的类型例如class是public级别的,但是它的实体却默认是internal

Written on May 3, 2017