Swift学习总结

2016-07-24  本文已影响33人  阳明先生1208

数据类型


内置数据类型

Swift 提供了非常丰富的数据类型,以下列出了常用了集中数据类型:

Int

一般来说,你不需要专门指定整数的长度。Swift 提供了一个特殊的整数类型Int
,长度与当前平台的原生字长相同:

除非你需要特定长度的整数,一般来说使用Int就够了。这可以提高代码一致性和可复用性。即使是在32位平台上,Int可以存储的整数范围也可以达到-2,147,483,648~2,147,483,647,大多数时候这已经足够大了。

UInt

Swift 也提供了一个特殊的无符号类型UInt,长度与当前平台的原生字长相同:

注意:尽量不要使用UInt,除非你真的需要存储一个和当前平台原生字长相同的无符号整数。除了这种情况,最好使用Int,即使你要存储的值已知是非负的。统一使用Int可以提高代码的可复用性,避免不同类型数字之间的转换,并且匹配数字的类型推断,请参考类型安全和类型推断

浮点数

浮点数是有小数部分的数字,比如3.14159,0.1和-273.15。
浮点类型比整数类型表示的范围更大,可以存储比Int
类型更大或者更小的数字。Swift 提供了两种有符号浮点数类型:

注意:Double精确度很高,至少有15位数字,而Float最少只有6位数字。选择哪个类型取决于你的代码需要处理的值的范围。

布尔值

Swift 有一个基本的布尔(Boolean)类型,叫做Bool。布尔值指逻辑上的值,因为它们只能是真或者假。Swift 有两个布尔常量,true和false。

类型别名

类型别名对当前的类型定义了另一个名字,类型别名通过使用 typealias 关键字来定义。语法格式如下:

typealias Feet = Int
var distance: Feet = 100
print(distance)

类型推断

当你要处理不同类型的值时,类型检查可以帮你避免错误。然而,这并不是说你每次声明常量和变量的时候都需要显式指定类型。
如果你没有显式指定类型,Swift 会使用类型推断(type inference)来选择合适的类型。
当推断浮点数的类型时,Swift 总是会选择Double而不是Float。

可选类型


Swift 的可选(Optional)类型,用于处理值缺失的情况。可选表示"那儿有一个值,并且它等于 x "或者"那儿没有值"。
Swfit语言定义后缀?作为命名类型Optional的简写,换句话说,以下两种声明是相等的:

var optionalInteger: Int?
var optionalInteger: Optional<Int>

当你声明一个可选变量或者可选属性的时候没有提供初始值,它的值会默认为nil。
如果一个可选类型的实例包含一个值,你可以用后缀操作符 !使用操作符!去获取值为nil的可选变量会有运行时错误

可选绑定

使用可选绑定(optional binding)来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量。可选绑定可以用在if和while语句中来对可选类型的值进行判断并把值赋给一个常量或者变量。

var myString:String?
myString = "Hello, Swift!"
if let yourString = myString {
    print("你的字符串值为 - \(yourString)")
}  else  { 
    print("你的字符串没有值")
}

运算符优先级


在一个表达式中可能包含多个有不同运算符连接起来的、具有不同数据类型的数据对象;由于表达式有多种运算,不同的运算顺序可能得出不同结果甚至出现错误运算错误,因为当表达式中含多种运算时,必须按一定顺序进行结合,才能保证运算的合理性和结果的正确性、唯一性。
优先级从上到下依次递减,最上面具有最高的优先级,逗号操作符具有最低的优先级。
相同优先级中,按结合顺序计算。大多数运算是从左至右计算,只有三个优先级是从右至左结合的,它们是单目运算符、条件运算符、赋值运算符。
基本的优先级需要记住:

函数


元组作为函数返回值

函数返回值类型可以是字符串,整型,浮点型等。
元组与数组类似,不同的是,元组中的元素可以是任意类型,使用的是圆括号。
你可以用元组(tuple)类型让多个值作为一个复合值从函数中返回。
下面的这个例子中,定义了一个名为minMax(_:)的函数,作用是在一个Int数组中找出最小值与最大值。

func minMax(array: [Int]) -> (min: Int, max: Int)? {
    var currentMin = array[0]
    var currentMax = array[0]
    for value in array[1..<array.count] {
        if value < currentMin {
            currentMin = value
        } else if value > currentMax {
            currentMax = value
        }
    }
    return (currentMin, currentMax)
}

前面的minMax(:)函数返回了一个包含两个Int值的元组。但是函数不会对传入的数组执行任何安全检查,如果array参数是一个空数组,如上定义的minMax(:)在试图访问array[0]时会触发一个运行时错误。
为了安全地处理这个"空数组"问题,将minMax(_:)函数改写为使用可选元组返回类型,并且当数组为空时返回nil;

可变参数

可变参数可以接受零个或多个值。函数调用时,你可以用可变参数来指定函数参数,其数量是不确定的。
可变参数通过在变量类型名后面加入(...)的方式来定义。

func vari<N>(members: N...) {
    for i in members {
        print(i)
    }
}
vari(members: "Google", "Baidu", "Runoob")

闭包


闭包(Closures)是自包含的功能代码块,可以在代码中使用或者用来作为参数传值。
Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的 匿名函数比较相似。
全局函数和嵌套函数其实就是特殊的闭包。
Swift中的闭包有很多优化的地方:

  1. 根据上下文推断参数和返回值类型
  2. 从单行表达式闭包中隐式返回(也就是闭包体只有一行代码,可以省略return)
  3. 可以使用简化参数名,如$0, $1(从0开始,表示第i个参数...)
  4. 提供了尾随闭包语法(Trailing closure syntax)

sort函数

Swift 标准库提供了名为sort的函数,会根据您提供的用于排序的闭包函数将已知类型数组中的值进行排序。
排序完成后,sort(:)方法会返回一个与原数组大小相同,包含同类型元素且元素已正确排序的新数组,原数组不会被sort(:)方法修改。
sort(_:)方法需要传入两个参数:

尾随闭包

尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。

func someFunctionThatTakesAClosure(closure: () -> Void) {
    // 函数体部分
}
// 以下是不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure(closure: {
    // 闭包主体部分
})
// 以下是使用尾随闭包进行函数调用
someFunctionThatTakesAClosure() {
    // 闭包主体部分
}

枚举


枚举简单的说也是一种数据类型,只不过是这种数据类型只包含自定义的特定数据,它是一组有共同特性的数据的集合。
Swift 的枚举类似于 Objective C 和 C 的结构,枚举的功能为:

// 定义枚举
enum DaysofaWeek {
    case Sunday
    case Monday
    case TUESDAY
    case WEDNESDAY
    case THURSDAY
    case FRIDAY
    case Saturday
}
var weekDay = DaysofaWeek.THURSDAY
weekDay = .THURSDAY
switch weekDay
{
case .Sunday:
    print("星期天")
case .Monday:
    print("星期一")
case .TUESDAY:
    print("星期二")
case .WEDNESDAY:
    print("星期三")
case .THURSDAY:
    print("星期四")
case .FRIDAY:
    print("星期五")
case .Saturday:
    print("星期六")
}

注意: 和 C 和 Objective-C 不同,Swift 的枚举成员在被创建时不会被赋予一个默认的整型值。

枚举相关值

以下实例中我们定义一个名为 Student 的枚举类型,它可以是 Name 的一个相关值(Int,Int,Int,Int),或者是 Mark 的一个字符串类型(String)相关值。

enum Student {
    case Name(String)
    case Mark(Int,Int,Int)
}
var studDetails = Student.Name("Runoob")
var studMarks = Student.Mark(98,97,95)
switch studMarks {
case .Name(let studName):
    print("学生的名字是: \(studName)。")
case .Mark(let Mark1, let Mark2, let Mark3):
    print("学生的成绩是: \(Mark1),\(Mark2),\(Mark3)。")
}

枚举原始值

原始值可以是字符串,字符,或者任何整型值或浮点型值。每个原始值在它的枚举声明中必须是唯一的。
在原始值为整数的枚举时,不需要显式的为每一个成员赋值,Swift会自动为你赋值。
例如,当使用整数作为原始值时,隐式赋值的值依次递增1。如果第一个值没有被赋初值,将会被自动置为0。

enum Month: Int {
    case January = 1, February, March, April, May, June, July, August, September, October, November, December
}
let yearMonth = Month.May.rawValue
print("数字月份为: \(yearMonth)。")

属性


计算属性

类、结构体和枚举可以定义计算属性,计算属性不直接存储值,而是提供一个 getter 来获取值,一个可选的 setter 来间接设置其他属性或变量的值。

class sample {
    var no1 = 0.0, no2 = 0.0
    var length = 300.0, breadth = 150.0
    
    var middle: (Double, Double) {
        get{
            return (length / 2, breadth / 2)
        }
        set(axis){
            no1 = axis.0 - (length / 2)
            no2 = axis.1 - (breadth / 2)
        }
    }
}

var result = sample()
print(result.middle)

如果计算属性的 setter 没有定义表示新值的参数名,则可以使用默认名称 newValue。

属性观察器

属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,甚至新的值和现在的值相同的时候也不例外。
可以为除了延迟存储属性之外的其他存储属性添加属性观察器,也可以通过重载属性的方式为继承的属性(包括存储属性和计算属性)添加属性观察器。

注意:不需要为无法重载的计算属性添加属性观察器,因为可以通过 setter 直接监控和响应值的变化。

可以为属性添加如下的一个或全部观察器:

class Samplepgm {
    var counter: Int = 0{
        willSet(newTotal){
            print("计数器: \(newTotal)")
        }
        didSet{
            if counter > oldValue {
                print("新增数 \(counter - oldValue)")
            }
        }
    }
}
let NewCounter = Samplepgm()

下标脚本


下标脚本语法及应用

语法

下标脚本允许你通过在实例后面的方括号中传入一个或者多个的索引值来对实例进行访问和赋值。
语法类似于实例方法和计算型属性的混合。
与定义实例方法类似,定义下标脚本使用subscript关键字,显式声明入参(一个或多个)和返回类型。
与实例方法不同的是下标脚本可以设定为读写或只读。这种方式又有点像计算型属性的getter和setter:

subscript(index: Int) -> Int {
    get {
        // 用于下标脚本值的声明
    }
    set(newValue) {
        // 执行赋值操作
    }
}

用法

根据使用场景不同下标脚本也具有不同的含义。
通常下标脚本是用来访问集合(collection),列表(list)或序列(sequence)中元素的快捷方式。
你可以在你自己特定的类或结构体中自由的实现下标脚本来提供合适的功能。

下标脚本选项

下标脚本允许任意数量的入参索引,并且每个入参类型也没有限制。
下标脚本的返回值也可以是任何类型。
下标脚本可以使用变量参数和可变参数。
一个类或结构体可以根据自身需要提供多个下标脚本实现,在定义下标脚本时通过传入参数的类型进行区分,使用下标脚本时会自动匹配合适的下标脚本实现运行,这就是下标脚本的重载

struct Matrix {
    let rows: Int, columns: Int
    var print: [Double]
    init(rows: Int, columns: Int) {
        self.rows = rows
        self.columns = columns
        print = Array(count: rows * columns, repeatedValue: 0.0)
    }
    subscript(row: Int, column: Int) -> Double {
        get {
            return print[(row * columns) + column]
        }
        set {
            print[(row * columns) + column] = newValue
        }
    }
}
// 创建了一个新的 3 行 3 列的Matrix实例
var mat = Matrix(rows: 3, columns: 3)

可选链


可选链(Optional Chaining)是一种是一种可以请求和调用属性、方法和子脚本的过程,用于请求或调用的目标可能为nil。
可选链返回两个值:

自动引用计数(ARC)


Swift 使用自动引用计数(ARC)这一机制来跟踪和管理应用程序的内存
通常情况下我们不需要去手动释放内存,因为 ARC 会在类的实例不再被使用时,自动释放其占用的内存。
但在有些时候我们还是需要在代码中实现内存管理。
ARC 功能

类实例之间的循环强引用

下面展示了一个不经意产生循环强引用的例子。例子定义了两个类:Person和Apartment,用来建模公寓和它其中的居民:

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { print("\(name) 被析构") }
}

class Apartment {
    let number: Int
    init(number: Int) { self.number = number }
    var tenant: Person?
    deinit { print("Apartment #\(number) 被析构") }
}

// 两个变量都被初始化为nil
var runoob: Person?
var number73: Apartment?

// 赋值
runoob = Person(name: "Runoob")
number73 = Apartment(number: 73)

// 意感叹号是用来展开和访问可选变量 runoob 和 number73 中的实例
// 循环强引用被创建
runoob!.apartment = number73
number73!.tenant = runoob

// 断开 runoob 和 number73 变量所持有的强引用时,引用计数并不会降为 0,实例也不会被 ARC 销毁
// 注意,当你把这两个变量设为nil时,没有任何一个析构函数被调用。
// 强引用循环阻止了Person和Apartment类实例的销毁,并在你的应用程序中造成了内存泄漏

Swift 提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题:

弱引用和无主引用允许循环引用中的一个实例引用另外一个实例而不保持强引用。这样实例能够互相引用而不产生循环强引用。
对于生命周期中会变为nil的实例使用弱引用。相反的,对于初始化赋值后再也不会被赋值为nil的实例,使用无主引用。

闭包引起的循环强引用

循环强引用还会发生在当你将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了实例。这个闭包体中可能访问了实例的某个属性,例如self.someProperty,或者闭包中调用了实例的某个方法,例如self.someMethod。这两种情况都导致了闭包 "捕获" self,从而产生了循环强引用。

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) 被析构")
    }
    
}

//创建并打印HTMLElement实例
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())

// HTMLElement实例将会被销毁,并能看到它的析构函数打印出的消息
paragraph = nil

HTMLElement 类产生了类实例和 asHTML 默认值的闭包之间的循环强引用。
实例的 asHTML 属性持有闭包的强引用。但是,闭包在其闭包体内使用了self(引用了self.name和self.text),因此闭包捕获了self,这意味着闭包又反过来持有了HTMLElement实例的强引用。这样两个对象就产生了循环强引用。
解决闭包引起的循环强引用:在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用。

类型转换


Swift 语言类型转换可以判断实例的类型。也可以用于检测实例类型是否属于其父类或者子类的实例。
Swift 中类型转换使用 is 和 as 操作符实现,is 用于检测值的类型,as 用于转换类型。
类型转换也可以用来检查一个类是否实现了某个协议。

检查类型

类型检查使用 is 关键字。
操作符 is 来检查一个实例是否属于特定子类型。若实例属于那个子类型,类型检查操作符返回 true,否则返回 false。

向下转型

向下转型,用类型转换操作符(as? 或 as!)
当你不确定向下转型可以成功时,用类型转换的条件形式(as?)。条件形式的类型转换总是返回一个可选值(optional value),并且若下转是不可能的,可选值将是 nil。
只有你可以确定向下转型一定会成功时,才使用强制形式(as!)。当你试图向下转型为一个不正确的类型时,强制形式的类型转换会触发一个运行时错误。

Any和AnyObject的类型转换

Swift为不确定类型提供了两种特殊类型别名:
AnyObject可以代表任何class类型的实例。
Any可以表示任何类型,包括方法类型(function types)。

注意:只有当你明确的需要它的行为和功能时才使用Any和AnyObject。在你的代码里使用你期望的明确的类型总是更好的。

扩展


扩展就是向一个已有的类、结构体或枚举类型添加新功能。
扩展可以对一个类型添加新的功能,但是不能重写已有的功能。
Swift 中的扩展可以:

协议


协议规定了用来实现某一特定功能所必需的方法和属性。
任意能够满足协议要求的类型被称为遵循(conform)这个协议。
类,结构体或枚举类型都可以遵循协议,并提供具体实现来完成协议定义的方法和功能。

协议构造器规定在类中的实现

你可以在遵循该协议的类中实现构造器,并指定其为类的指定构造器或者便利构造器。在这两种情况下,你都必须给构造器实现标上"required"修饰符:

class SomeClass: SomeProtocol {
    required init(someParameter: Int) {
        // 构造器实现
    }
}

protocol tcpprotocol {
    init(aprot: Int)
}

class tcpClass: tcpprotocol {
    required init(aprot: Int) {
    }
}

使用required修饰符可以保证:所有的遵循该协议的子类,同样能为构造器规定提供一个显式的实现或继承实现。
如果一个子类重写了父类的指定构造器,并且该构造器遵循了某个协议的规定,那么该构造器的实现需要被同时标示required和override修饰符:

// 因为遵循协议,需要加上"required"; 因为继承自父类,需要加上"override" 
required override convenience init(no1: Int) { 
    self.init(no1:no1, no2:0) 
}

协议类型

尽管协议本身并不实现任何功能,但是协议可以被当做类型来使用。
协议可以像其他普通类型一样使用,使用场景:

类专属协议

你可以在协议的继承列表中,通过添加class关键字,限制协议只能适配到类(class)类型。
该class关键字必须是第一个出现在协议的继承列表中,其后,才是其他继承协议。格式如下:

protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol { 
    // 协议定义
}

协议合成

Swift 支持合成多个协议,这在我们需要同时遵循多个协议时非常有用。
语法格式如下:
protocol<SomeProtocol, AnotherProtocol>

检验协议的一致性

你可以使用is和as操作符来检查是否遵循某一协议或强制转化为某一类型。

泛型


Swift 提供了泛型让你写出灵活且可重用的函数和类型。
Swift 标准库是通过泛型代码构建出来的。
Swift 的数组和字典类型都是泛型集。
你可以创建一个Int数组,也可创建一个String数组,或者甚至于可以是任何其他 Swift 的类型数据数组。
泛型函数可以访问任何类型,如 Int 或 String。
以下实例是一个泛型函数 exchange 用来交换两个 Int 和 String 值:

func exchange<T>( a: inout T, b: inout T) {
    let temp = a
    a = b
    b = temp
}

这个函数的泛型版本使用了占位类型名字(通常此情况下用字母T来表示)来代替实际类型名(如Int、String或Double)。占位类型名没有提示T必须是什么类型,但是它提示了a和b必须是同一类型T,而不管T表示什么类型。只有 exchange(::)函数在每次调用时所传入的实际类型才能决定T所代表的类型。
另外一个不同之处在于这个泛型函数名后面跟着的占位类型名字(T)是用尖括号括起来的()。这个尖括号告诉 Swift 那个T是 exchange(::)函数所定义的一个类型。因为T是一个占位命名类型,Swift 不会去查找命名为T的实际类型。

Where 语句

类型约束能够确保类型符合泛型函数或类的定义约束。
你可以在参数列表中通过where语句定义参数的约束。
你可以写一个where语句,紧跟在在类型参数列表后面,where语句后跟一个或者多个针对关联类型的约束,以及(或)一个或多个类型和关联类型间的等价(equality)关系。
实例
下面的例子定义了一个名为allItemsMatch的泛型函数,用来检查两个Container实例是否包含相同顺序的相同元素。
如果所有的元素能够匹配,那么返回一个为true的Boolean值,反之则为false。

protocol Container {
    associatedtype ItemType
    mutating func append(item: ItemType)
    var count: Int { get }
    subscript(i: Int) -> ItemType { get }
}

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

func allItemsMatch<C1: Container, C2: Container where C1.ItemType == C2.ItemType, C1.ItemType: Equatable>
    (someContainer: C1, anotherContainer: C2) -> Bool {
    // 检查两个Container的元素个数是否相同
    if someContainer.count != anotherContainer.count {
        return false
    }
    
    // 检查两个Container相应位置的元素彼此是否相等
    for i in 0..<someContainer.count {
        if someContainer[i] != anotherContainer[i] {
            return false
        }
    }
    // 匹配所有项,返回 true
    return true
}

访问控制


访问控制可以限定其他源文件或模块中代码对你代码的访问级别。
你可以明确地给单个类型(类、结构体、枚举)设置访问级别,也可以给这些类型的属性、函数、初始化方法、基本类型、下标索引等设置访问级别。
协议也可以被限定在一定的范围内使用,包括协议里的全局常量、变量和函数。
访问控制基于模块与源文件。
模块指的是以独立单元构建和发布的Framework或Application。在Swift 中的一个模块可以使用import关键字引入另外一个模块。
源文件是单个源码文件,它通常属于一个模块, 源文件可以包含多个类和函数 的定义。
Swift 为代码中的实体提供了三种不同的访问级别:public、internal、private。

public为最高级访问级别,private为最低级访问级别。

上一篇 下一篇

猜你喜欢

热点阅读