美文网首页
A swift tour

A swift tour

作者: 后尘L | 来源:发表于2017-07-27 11:35 被阅读27次
    import UIKit
    

    1. 简单值

    行尾可以不用分号

    print("Hello, world!")
    

    全局域中的代码,用来作为程序的入口,所以,不需要main()方法

    使用let定义常量,使用var定义变量。
    常量赋值,可以稍后进行。

    var myVariable = 42
    myVariable = 50
    let myConstant = 42
    let anotherConstant: Int
    anotherConstant = 50
    

    常量和变量赋值时的数据类型,必须和定义时的数据类型相同。
    但是,你不必每次定义常量和变量时,都指定数据类型,swift会根据数据推导出相应的数据类型,然后指定给常量或变量。
    如果初始值没有足够信息,或者没有初始值,就需要在变量后指定数据类型,中间用冒号分隔。

    let implicitInteger = 70
    let implicitDouble = 70.0
    let explicitDouble: Double = 70
    let explicitFloat: Float = 4
    

    变量不会自动转换成其它类型,如果需要把变量切换成另一个类型,明确地强制转换

    let label = "The width is "
    let width = 94
    let widthLabel = label + String(width)
    //let widthLabel = label + width    // error, width不会自动转换成String类型,需要明确手动转换
    

    把数值包含在字符串中的另一种更简便方法是,把数值写在圆括号中,然后在圆括号前添加一个反斜线

    let apples = 3
    let oranges = 5
    let appleSummary = "I have \(apples) apples."
    let fruitSummary = "I have \(apples + oranges) pieces of fruit."
    

    使用双三引号显示多行文本。暂时使用不了,可能是版本不支持该语法。

    //let quotation = """
    //Even though there's whitespace to the left,
    //the actual lines aren't indented.
    //Except for this line.
    //Double quotes (") can appear without being escaped.
    //
    //I still have \(apples + oranges) pieces of fruit.
    //"""
    

    使用方括号创建数组和字典,通过在方括号中写入index或key来访问其中的元素。最后一个元素后面允许有个逗号。

    var shoppingList = ["catfish", "water", "tulips", "blue paint"]
    shoppingList[1] = "bottle of water"
    var occupations = [
        "Malcolm": "Captain",
        "Kaylee": "Mechanic",
    ]
    occupations["Jayne"] = "Public Relations"
    

    创建空数组或字典,使用初始化语法。

    let emptyArray = [String]()
    let emptyDictionary = [String: Float]()
    

    如果类型信息可以被推断出来,你可以用[]和[:]来定义空数组和空字典。就像你声明变量或者给函数传参数时一样。

    shoppingList = []
    occupations = [:]
    

    2. 控制流

    使用if和switch进行条件判断,使用for-in,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)
    

    在if语句中,条件必须是布尔表达式。这意味着像if score {}这样的语句是一个错误,而不会隐式地与0比较。

    可以一起使用if和let,作用于可能缺失的值,会隐式地将值与nil进行判断,不相等时执行if语句体。这些值被叫做可选值,一个可选值可能包含数据,也可能包含一个nil,nil表示值缺失。在数值类型后添加?,可以标记一个数值为可选的。

    var optionalString: String? = "Hello"
    print(optionalString == nil)
    var optionalName: String? = "John Appleseed"
    optionalName = nil
    var greeting = "Hello!"
    if let name = optionalName {
        greeting = "Hello, \(name)"
    } else {
        greeting = "Hello, nil"
    }
    

    如果optional值是nil,那么条件判断值是false,if语句体会跳过不执行。否则,可选值会被赋值给let后面的常量,常量值可以在if语句体中被访问。
    另一种处理可选值的方法是,使用??操作符为可选值提供默认值。如果可选值缺失,则使用默认值。

    let nickName: String? = nil
    let fullName: String = "John Appleseed"
    let informalGreeting = "Hi \(nickName ?? fullName)"
    

    switch支持所有数据类型,以及各种比较操作。不仅仅是整数以及测试相等。

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

    注意上面let是怎么使用的,let会将匹配项赋值给常量使用
    执行完匹配项的代码后,程序自动退出switch模块,而不会自动进入switch的next case,所以没必要明确地在每个case中添加break。

    使用for-in遍历字典中的元素,为每个元素提供一组用圆括号包裹的值,以匹配每个key-value对。字典中的内容是乱序的。

    let interestingNumbers = [
        "Prime": [2, 3, 5, 7, 11, 13],
        "Fibonacci": [1, 1, 2, 3, 5, 8],
        "Square": [1, 4, 9, 16, 25]
    ]
    var largest = 0
    

    必须使用双引号,不能使用单引号

    var type = ""
    for (kind, numbers) in interestingNumbers {
        for number in numbers {
            if number > largest {
                type = kind
                largest = number
            }
        }
    }
    print("kind is " + type + ", largest is " + String(largest))
    

    使用while重复执行循环体,直到条件变化。条件判断可以在循环体后面,这样可以保证循环体至少执行一次。

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

    可以使用..<来产生一组index,然后在循环体中使用,不包含upper值。使用...包含upper值。

    var total = 0
    for i in 0..<4 {
        total += i
    }
    print(total)
    

    3. 函数和闭包

    使用func定义函数。参数需要指定类型,类型与变量名间以冒号分隔。返回值在参数列表后,以->与参数列表分隔。

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

    调用函数需要为arguments传递lable,默认情况下使用params名字作为arguments的label。另外,函数定义时,在param前可自定义argument label,或者在param名字前写一个_,表示该argument不需要lable。

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

    可以使用tuple作为函数的返回值,以一次返回多个数值。tuple的元素可以通过name或number引用。

    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 hasAnyMatched(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]
    hasAnyMatched(list: numbers, condition: lessThanTen)
    

    函数其实是一种特殊的闭包 —— 可以稍后执行的代码块。 闭包中的代码对闭包定义时所在作用域中有访问权限的变量和函数有访问权限,即使闭包执行时在另一个作用域。 编写闭包时,可以是匿名的,把代码用大括号包裹起来,然后用in把参数和返回值与函数体分隔开。

    numbers.map({ (number: Int) -> Int in
        // return 0 for all odd number
        if number % 2 != 0 {
            return 0
        }
        let result = 3 * number
        return result
    })
    

    有几种方法可以更简洁地编写闭包。 如果闭包的类型已知,比如作为回调函数,可以忽略参数类型、返回类型,或both。 单个语句的闭包会返回语句的结果。

    let mappedNumbers = numbers.map( {number in 3 * number} )
    print(mappedNumbers)
    //let fourTimes = numbers.map( {3 * number} )    // not work
    //print(fourTimes)
    

    可以通过数字而非名字访问参数。在特别短的闭包中时,这种特性会更加有用。当一个闭包作为函数最后一个参数时,它可以直接跟在圆括号后边。当闭包是函数的唯一参数时,可以完全去掉圆括号。

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

    4. 对象和类

    使用class跟随一个类名和创建类。 类中属性的定义写法与变量或常量相同,只不过属性的上下文是类。 同理,类中方法与函数写法也相同。

    class Shape {
        var numberOfSides = 0
        let pi = 3.14
        func simpleDescription() -> String {
            return "A shape with \(numberOfSides) sides."
        }
        func getArea(length: Double) -> Double {
            return pi * length * length
        }
    }
    

    在类名后添加一对圆括号,即可创建类的实例。 使用.引用实例的属性和方法。

    var shape = Shape()
    shape.numberOfSides = 7
    var shapeDescription = shape.simpleDescription()
    

    上面的类定义中没有构造器,构造器使用init
    每一个属性都需要赋予一个值,或者在定义时,或者在构造函数中
    使用deinit定义销毁器,当对象被回收时,执行该操作,可以在这里做一些清理操作

    class NamedShape {
        var numberOfSides: Int = 0
        var name: String
        
        init(name: String) {
            self.name = name
        }
        
        func simpleDescription() -> String {
            return "A shape with \(numberOfSides) sides."
        }
    }
    

    子类在类名后跟随父类的类名,中间用冒号分隔。
    子类复写父类方法,需要标记override。 没有标记就复写的话,编译器会报错。 编译器同样会检查override的方法在父类中是否有这个方法。

    class Square: NamedShape {
        var sideLength: Double
        
        init(sideLength: Double, name: String) {
            self.sideLength = sideLength
            super.init(name: name)
            numberOfSides = 4
        }
        
        func area() -> Double {
            return sideLength * sideLength
        }
        
        override func simpleDescription() -> String {
            return "A square with sides of length \(sideLength)."
        }
    }
    let test = Square(sideLength: 5.2, name: "my test square")
    test.area()
    test.simpleDescription()
    

    除了存储简单的属性外,属性可以有getter和setter
    在perimeter的setter中,新值被隐式命名为newValue。可以在set之后的圆括号中明确指定新值的名字。

    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)
    

    如果你不需要计算属性,但是仍然需要提供一些代码,在属性值改变时执行,使用willSet和didSet。在这里提供的代码,每次属性值在构造函数外被改变时,都会执行。

    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。否则,?后边的内容会被执行。两种情况下,整个表达式的值都是可选值。

    let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
    let sideLength = optionalSquare?.sideLength
    

    5. 枚举和结构体

    使用enum创建枚举类型。 像类和其它命名类型一样,枚举类型也包含方法。
    默认下,swift为枚举值的原始值从0开始赋值,后续依次加1。 不过,可以代码中指定某个枚举项的值。 下面的例子中,ace被赋值为1,其它枚举项依次按顺序被赋值。
    可以使用string或float作为枚举类型原始值的类型。 使用rawType属性可以访问某个枚举项的原始值。

    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:)返回一个指定原始值对应的枚举值。如果枚举类型中有对应原始值,就返回对应枚举项,否则返回nil。

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

    枚举类型的枚举项是真实的值,不是原始值的另一种表达方式。 实际上,如果没有有意义的原始值,就不必指定。 这种不指定原始值的情况下,不需要为枚举类型指定类型。

    enum Suit {
        case spades, hearts, diamonds, clubs
        
        func simpleDescription() -> String {
            switch self {
            case .spades:
                return "spades"
            case .hearts:
                return "hearts"
            case .diamonds:
                return "diamonds"
            case .clubs:
                return "clubs"
            }
        }
    }
    let hearts = Suit.hearts
    let heartsDescription = hearts.simpleDescription()
    

    如果一个枚举包含原始值,那么这些原始值是在定义时指定的,所以某个枚举项的每个实例的原始值都是相同的。 另一种方法是让枚举项实例的数值在创建枚举项实例时指定,这样枚举类型的同一个枚举项的不同实例可以有不同的数值。

    enum ServerResponse {
        case result(String, String)
        case failure(String)
    }
    
    let success = ServerResponse.result("6:00 am", "8:09 pm")
    let failure = ServerResponse.failure("Out of cheese.")
    
    switch success {
    case let .result(sunrise, sunset):
        print("Sunrise is at \(sunrise) and sunset is at \(sunset)")
    case let .failure(message):
        print("Failure... \(message)")
    }
    

    使用struct创建结构体。 结构体支持很多类似class的操作,比如方法和初始化。 结构体和class最大的不同是,结构体在代码中传递时总是会重新复制,而类在传递时是传递引用。

    struct Card {
        var rank: Rank
        var suit: Suit
        
        func simpleDescription() -> String {
            return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
        }
    }
    let threeOfSpades = Card(rank: .three, suit: .spades)
    let threeOfSpadesDescription = threeOfSpades.simpleDescription()
    

    6. 协议和扩展

    使用protocol定义协议

    protocol ExampleProtocol {
        var simpleDescription: String { get }
        mutating func adjust()
    }
    
    // class, enumeration, struct都能继承protocol
    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
    

    结构体中方法上的mutating标记表示该方法修改了结构体。 类中的方法不需要这种标记因为类中的方法总会修改类。

    struct SimpleStructure: ExampleProtocol {
        var simpleDescription: String = "A simple structure"
        mutating func adjust() {
            simpleDescription += " (adjusted)"
        }
    }
    var b = SimpleStructure()
    b.adjust()
    let bDescription = b.simpleDescription
    

    enum中怎么定义变量

    //enum SimpleEnum: ExampleProtocol {
    //    case one, two
    //    
    //    var simpleDescription: String = "A simple enum"
    //    func adjust() {
    //        simpleDescription += " enum adjusted."
    //    }
    //}
    //var oneEnum = SimpleEnum.one
    //oneEnum.adjust()
    //let cDescription = oneEnum.simpleDescription
    

    使用extension可以为已存在的类型添加功能,比如添加方法,属性计算。 可以为别处已存在的类型适配protocol,已存在类型甚至可以是从library和framework中引用的。

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

    protocol类型的使用方法与Java中接口相同,可以定义变量。 可以定义一组值,具有不一致的子类型,但都遵循接口规范。 访问方法和属性只能访问接口中定义的方法和属性。

    let protocolValue: ExampleProtocol = a
    print(protocolValue.simpleDescription)
    

    7. 错误处理

    使用Error protocol的子类型表示错误

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

    使用throw抛出异常,使用throws标记一个方法which可以抛出异常。 如果在方法中抛出了一个异常,方法会立即返回,而且调用方法的代码处理这个异常。

    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,除非你另有指定。另行指定时,使用let。
    可以使用catch提供多种异常捕获,就像在switch中使用case一样。

    do {
        let printerResponse = try send(job: 1040, toPrinter: "Never Has Toner")
        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)
    }
    

    另一种处理异常的方法是在结果前加上try?,以把结果转换成可选的。 如果函数抛出了异常,异常会被忽略,而结果将是nil。 否则,结果是可选的,包含有函数的返回值。

    let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
    let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")
    

    使用defer,里面的代码块中的代码,在所有代码后且在方法返回前执行。 无论方法是否抛出错误,defer代码块都会执行。 可以在defer中写一些设置和清理代码。

    var fridgeIsOpen = false
    let fridegContent = ["milk", "eggs", "leftovers"]
    
    func fridgeContents(_ food: String) -> Bool {
        fridgeIsOpen = true
        defer {
            fridgeIsOpen = false
        }
        
        let result = fridegContent.contains(food)
        return result
    }
    fridgeContents("banana")
    print(fridgeIsOpen)
    

    8. 泛型

    在三角括号中写入一个名字,可以创建一个泛型函数或类型。

    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)
    // repeating是自定义的label,返回值[Item]表示每一项都是Item的数组
    
    // 泛型可以用于函数、方法、类、枚举、结构体
    enum OptionalValue<Wrapped> {
        case one
        case some(Wrapped)
    }
    var possibleInteger: OptionalValue<Int> = .one
    possibleInteger = .some(100)
    

    使用where在body之前限定一系列前提条件,比如,需要类型实现一个接口,需要两个类型相同,需要一个类有一个指定的父类。

    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])
    

    <T: Equatable><T> ... where T: Equatable 作用相同。

    相关文章

      网友评论

          本文标题:A swift tour

          本文链接:https://www.haomeiwen.com/subject/zbzakxtx.html