iOS- 学习 Swift初见

前言:
本篇文章的目的,在于记录学习过程,敦促自己,方便查看。

练习工具:Playground
学习网站: swift51

本页内容包括

swift 第一句话

   print("Hello,world!")

备注:这行代码就是一个完整的程序。不需要为了输入输出或字符串处理导入一个单独的库。全局作用域中的代码会被自动当作程序的入口点,所以你也不需要main()函数。同样不需要在每个语句结尾写上分号。

简单值

let:声明常量
var:声明变量

一个常量的值,在编译的时候,并不需要有明确的值,但是你只能为它赋值一次。这说明你可以用一个常量来命名一个值,一次赋值就即可在多个地方使用。

var myVariable = 42
myVaruable = 50
let myCConstant = 42

常量或者变量的类型必须和你赋给它们的值一样。然而,你不用明确地声明类型,声明的同时赋值的话,编译器会自动推断类型。在上面的例子中,编译器推断出 myVariable 是一个整数类型(integer)因为它的初始值是整数。

如果初始值没有提供足够的信息(或者没有初始值),那你需要在变量后面声明类型,用冒号分割。

let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble : Double = 70 

【练习】
创建一个常量,显式指定类型为 Float 并指定初始值为 4。

let normalValue : Float = 4

值永远不会被隐式转换为其他类型。如果你需要把一个值转换成其他类型,请显式转换。

let label = "The width is" 
let width = 94
let widthLabel = label + String(width)

【练习】
删除最后一行中的 String,错误提示是什么?

 ❗️ Binary operator '+' cannot be applied to operands of type 'String' and 'Int'
  译: 二进制运算符'+'不能应用于'String'和'Int'类型的操作数

有一种更简单的把值转换成字符串的方法:把值写到括号中,并且在括号之前写一个反斜杠。例如

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

【练习】
使用 () 来把一个浮点计算转换成字符串,并加上某人的名字,和他打个招呼。

let rice : Float = 1.5
let bread: Float = 1.5
let name = "Jack"
let total = "姐姐买了面包和大米,总共\(rice + bread)斤,送给了" + String(name) + "."
print(total)

使用方括号 [] 来创建数组和字典,并使用下标或者键(key)来访问元素。最后一个元素后面允许有个逗号。

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

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

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

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

var shoppingList = [Float]();
var occupations  = [String:String]()
shoppingList = [];
ocupations = [:];

控制流
使用 if 和 switch 来进行条件操作,使用 for-in、 for、 while 和 repeat-while 来进行循环。包裹条件和循环变量括号可以省略,但是语句体的大括号是必须的。

let indivdualScores = [75,43,103,87,12]
var teamScore = 0
for score in indivdualScores {

  if score > 50 {
   teamScore += 3
  }else{
   teamScore += 1
   }
}
print(teamScore)

if 语句中,条件必须是一个布尔表达式——这意味着像 if score { ... } 这样的代码将报错,而不会隐形地与 0 做对比。

❗️'Int' is not convertible to 'Bool'
 译: 'Int'不能转换为'Bool'

你可以一起使用 if 和 let 来处理值缺失的情况。这些值可由可选值来代表。一个可选的值是一个具体的值或者是 nil 以表示值缺失。在类型后面加一个问号来标记这个变量的值是可选的。

var optionalString: String? = "Hello"
print(optionalString == nil)

var optionalName: String? = "John Appleseed"
var greetinig = "Hello!"
if let name = optionalName {
 greetinig = "Hello,\(name)"
}

[练习]
把 optionalName 改成 nil,greeting会是什么?添加一个 else 语句,当 optionalName 是 nil 时给 greeting 赋一个不同的值。

Optionals

- 或者是一个值,或者是没有值
- 没有值时为nil

答:
如果变量的可选值是 nil,条件会判断为 false,大括号中的代码会被跳过。如果不是 nil,会将值解包并赋给 let 后面的常量,这样代码块中就可以使用这个值了。另一种处理可选值的方法是通过使用 ?? 操作符来提供一个默认值。如果可选值缺失的话,可以使用默认值来代替。

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

switch 支持任意类型的数据以及各种比较操作——不仅仅是整数以及测试相等。

 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.")
}

👀备注: 在Swift语法里where关键字的作用跟SQL的where一样, 即附加条件判断。

[练习]:
删除 default 语句,看看会有什么错误?

❗️Switch must be exhaustive
 译: 开关必须是详尽的

注意 let 在上述例子的等式中是如何使用的,它将匹配等式的值赋给常量 x。

运行 switch 中匹配到的子句之后,程序会退出 switch 语句,并不会继续向下运行,所以不需要在每个子句结尾写 break。

你可以使用 for-in 来遍历字典,需要两个变量来表示每个键值对。字典是一个无序的集合,所以他们的键和值以任意顺序迭代结束。

let interstingNumbers = [
    "peime":[2,3,5,7,11,13],
    "Fibonacci":[1,1,2,3,5,8,],
    "Square":[1,4,9,16,25]
]
 var largest = 0
 for (kind,numbers) in interstingNumbers {
  for number in numbers {      
   if number > largest {
       largest = number
   }  
   }   
}
print(largest)

[练习]
添加另一个变量来记录最大数字的种类(kind),同时仍然记录这个最大数字的值。

 var largest = 0
 var largetKind = ""
 for (kind,numbers) in interstingNumbers {

     for number in numbers {
        if number > largest {
             largest = number
             largetKind = kind
         }
     }

 }
 print(largest)
 print(largetKind)

使用 while 来重复运行一段代码直到不满足条件。循环条件也可以在结尾,保证能至少循环一次。

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

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

你可以在循环中使用 ..< 来表示范围。

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

使用 ..< 创建的范围不包含上界,如果想包含的话需要使用 ...。

函数和闭包
使用 func 来声明一个函数,使用名字和参数来调用函数。使用 -> 来指定函数返回值的类型。

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

[练习]
删除 day 参数,添加一个参数来表示今天吃了什么午饭。

 func great(person: String,food: String)-> String{
     return "Hello, \(person). Today I eat an \(food)."
 }
 great(person: "Tom", food: "apple")

默认情况下,函数使用它们的参数名称作为它们参数的标签,在参数名称前可以自定义参数标签,或者使用 _ 表示不使用参数标签。

 func great (_ person: String,on day: String)-> String
 {
     return "Hello, \(person). Today is \(day)."
 }
 great("Jack", on:"Wednesday")

Tuples 元组

将多个不同的值集合成一个数据
(true,"哈哈","woman")(404,Not Found)
 **特点**
 1.可以有任意多个值
 2.不同的值可以是不同类型

使用元组来让一个函数返回多个值。该元组的元素可以用名称或数字来表示。

元组是swift编程语言中唯一的一种复合类型,他可以将指定有限个数的任何类型一次整理为一个对象,元组中的每一种类型都可以是任何的结构体、枚举或类类型,甚至也可以是一个元组以及空元组。元组中的每个元素我们称为一个“分量”

 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,15,25,35,55,45])
 print(statistics)
 print(statistics.2)

[练习]
写一个计算参数平均值的函数。

 func calculateStatistics(scores:[Int])-> Int {

  var sum = 0
  var average = 0;
  for score in scores {
   sum += score
   }
   average = sum / scores.count
   return average
 }

 let averageNum = calculateStatistics(scores: [12,24,64])
 print(averageNum)

函数可以嵌套。被嵌套的函数可以访问外侧函数的变量,你可以使用嵌套函数来重构一个太长或者太复杂的函数。

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

函数是第一等类型,这意味着函数可以作为另一个函数的 返回值

 func makeIncremmenter() ->((Int)->(Int))
 {
    func addOne(number:Int) ->Int{
   return 1 + number
 }
   return addOne;
 }
 var inCrement = makeIncremmenter()
 inCrement(7)
 print(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)

函数实际上是一种特殊的闭包:它是一段能之后被调取的代码。闭包中的代码能访问闭包作用域中的变量和函数,即使闭包是在一个不同的作用域被执行的 - 你已经在嵌套函数的例子中看过了。你可以使用 {} 来创建一个匿名闭包。使用 in 将参数和返回值类型的声明与闭包函数体进行分离。

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

练习:
重写闭包,对所有奇数返回 0。

var oddArr = numbers.map({
     (number:Int)->Int in
     var odd = 0
     if  number % 2 == 0 {
        odd = number
         print("偶数 = \(odd)")
         return odd
     } else {
         print("奇数 = \(number)")
         return number
     }
 })

有很多种创建更简洁的闭包的方法。如果一个闭包的类型已知,比如作为一个代理的回调,你可以忽略参数,返回值,甚至两个都忽略。单个语句闭包会把它语句的值当做结果返回。

 let mappedNumbers = numbers.map({number in 3 * number})
 print(mappedNumbers)

你可以通过参数位置而不是参数名字来引用参数——这个方法在非常短的闭包中非常有用。当一个闭包作为最后一个参数传给一个函数的时候,它可以直接跟在括号后面。当一个闭包是传给函数的唯一参数,你可以完全忽略括号。

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

对象和类

使用 class 和类名来创建一个类。类中属性的声明和常量、变量声明一样,唯一的区别就是它们的上下文是类。同样,方法和函数声明也一样。

 class Shape {

     var numberOfSide = 0

     func simpleDescription() -> String {
         return "A Shape with \(numberOfSide) sides."
     }

 }

【练习】
使用 let 添加一个常量属性,再添加一个接收一个参数的方法。
要创建一个类的实例,在类名后面加上括号。使用点语法来访问实例的属性和方法。

 var shape = Shape()
 shape.numberOfSide = 7
 let simpleDesc = shape.simpleDescription()
 var shapeDesc2 = shape.simpleDescription()

这个版本的 Shape 类缺少了一些重要的东西:一个构造函数来初始化类实例。使用 init 来创建一个构造器。

 class NamedShape {
     var numberOfSides: Int = 0
     var name: String

     init(name: String) {
         self.name = name
     }

     func simpleDescription() -> String {
         return "A shape with \(numberOfSides) sides."
     }
 }

注意 self 被用来区别实例变量 name 和构造器的参数 name。当你创建实例的时候,像传入函数参数一样给类传入构造器的参数。每个属性都需要赋值——无论是通过声明(就像 numberOfSides)还是通过构造器(就像 name)。

如果你需要在删除对象之前进行一些清理工作,使用 deinit 创建一个析构函数。

子类的定义方法是在它们的类名后面加上父类的名字,用冒号分割。创建类的时候并不需要一个标准的根类,所以你可以根据需要添加或者忽略父类。

子类如果要重写父类的方法的话,需要用 override 标记——如果没有添加 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()

[练习]
创建 NamedShape 的另一个子类 Circle,构造器接收两个参数,一个是半径一>个是名称,在子类 Circle 中实现 area() 和 simpleDescription() 方法。

 class Circle:NamedShape {

     var radies: Double
     init(radies: Double,name: String) {
         self.radies = radies
         super.init(name: name)
    }

     func area() -> Double {
         return radies * radies
    }

     override func simpleDescription() -> String {
         return "test ...."
    }
 }

 var test1 = Circle(radies: 1.2, name: "test_name")
 test1.area()
 test1.simpleDescription()

除了储存简单的属性之外,属性可以有 getter 和 setter 。

 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)

在 perimeter 的 setter 中,新值的名字是 newValue。你可以在 set 之后显式的设置一个名字。
注意 EquilateralTriangle 类的构造器执行了三步:

  • 1.设置子类声明的属性值
  • 2.调用父类的构造器
  • 3.改变父类定义的属性值。其他的工作比如调用方法、getters 和 setters 也可以在这个阶段完成。

如果你不需要计算属性,但是仍然需要在设置一个新值之前或者之后运行代码,使用 willSet 和 didSet。写入的代码会在属性值发生改变时调用,但不包含构造器中发生值改变的情况。比如,下面的类确保三角形的边长总是和正方形的边长相同。

 class TriangleAndSquare {

     var square: Square {
         willSet {
             triangle.sideLength = newValue.sideLength
         }
     }

     var triangle: EquilateralTriangle {
         willSet {
             square.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

枚举和结构体
使用 enum 来创建一个枚举。就像类和其他所有命名类型一样,枚举可以包含方法

 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

[练习]
写一个函数,通过比较它们的原始值来比较两个 Rank 值。

 func comparisonTwoValue (value1:Rank,value2:Rank) -> () {
    print(value1)
     var str: String?
     if value1.rawValue < value2.rawValue {
       str = value2.simpleDescription()
    }
    print(str!)
 }

1.默认情况下,Swift 按照从 0 开始每次加 1 的方式为原始值进行赋值,不过你可以通过显式赋值进行改变。

2.在上面的例子中,Ace 被显式赋值为 1,并且剩下的原始值会按照顺序赋值。

3.你也可以使用字符串或者浮点数作为枚举的原始值。使用 rawValue 属性来访问一个枚举成员的原始值。

使用 init?(rawValue:) 初始化构造器来创建一个带有原始值的枚举成员。如果存在与原始值相应的枚举成员就返回该枚举成员,否则就返回 nil。

 if let converteRank = Rank(rawValue: 11) {
    let threeDescription = converteRank.simpleDescription()
    print(threeDescription)
 }

枚举的关联值是实际值,并不是原始值的另一种表达方法。实际上,如果没有比较有意义的原始值,你就不需要提供原始值。

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

[练习]
给 Suit 添加一个 color() 方法,对 spades 和 clubs 返回 “black” ,对 hearts 和 diamonds 返回 “red”

 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"
         }
     }

     func color() ->String
     {
         switch self {
         case .spades:
             return "black"
         case .hearts:
             return "red"
         case .diamonds:
             return "red"
         case .clubs:
             return "black"
  
         }
  
     }

 }
 let hearts = Suit.hearts
 let heartsDescription = hearts.simpleDescription()
 let colora = hearts.color()

注意在上面的例子中用了两种方式引用 hearts 枚举成员:给 hearts 常量赋值时,枚举成员 Suit.hearts 需要用全名来引用,因为常量没有显式指定类型。在 switch 里,枚举成员使用缩写 .hearts 来引用,因为 self 已经是一个 suit 类型,在已知变量类型的情况下可以使用缩写。

如果枚举成员的实例有原始值,那么这些值是在声明的时候就已经决定了,这意味着不同的枚举成员总会有一个相同的原始值。当然我们也可以为枚举成员设定关联值,关联值是在创建实例时决定的。这意味着不同的枚举成员的关联值都可以不同。你可以把关联值想象成枚举成员的寄存属性。例如,考虑从服务器获取日出和日落的时间。服务器会返回正常结果或者错误信息。

 enum ServerResponse {
      case result(String,String)
      case failure(String)
 }

 let success = ServerResponse.result("6:00 am","8:00 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)")
 }

练习:
给 ServerResponse 和 switch 添加第三种情况。

 enum ServerResponse {
     case result(String,String)
      case failure(String)
     case timeOut(String)
 }

 let success = ServerResponse.result("6:00 am","8:00 pm")
 let failure = ServerResponse.failure("Out of cheese.")
 let timeOut = ServerResponse.timeOut("time out")

 switch success {
 case let .result(sunrise, sunset):
     print("Sunrise is at \(sunrise) and sunset is at \(sunset)")
 case let .failure(message):
     print("Failure... \(message)")
 case let .timeOut(infor):
     print("time out... \(infor)")

 }

注意日升和日落时间是如何从 ServerResponse 中提取到并与 switch 的 case 相匹配的。

使用 struct 来创建一个结构体。结构体和类有很多相同的地方,比如方法和构造器。它们之间最大的一个区别就是结构体是传值,类是传引用。

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

练习:
给 Card 添加一个方法,创建一副完整的扑克牌并把每张牌的 rank 和 suit 对应起来。

 struct Card {
     var rank: Rank
     var suit: Suit
     func simpleDescription() -> String {
         return "The \(rank.simpleDescription()) of \(suit.simpleDescription())."
     }
     func allCards() -> [String] {
        var cardsArray = [String]()
         for i in 1...13 {
             let rank = Rank(rawValue: i)
             for j in 0..<4 {
                 let suit = Suit(rawValue: j)
                 let card = suit!.simpleDescription() + " " + rank!.simpleDescription()
                 cardsArray.append(card)
                print(card)
             }
         }
   
         return cardsArray
 }
 }

 let threeOfSpades = Card(rank: .three, suit: .spades)
 let threeOfSpadesDescription = threeOfSpades.allCards()

枚举推荐文章
Swift枚举的全用法

协议和扩展

使用 protocol 来声明一个协议。

 protocol ExampleProtocol {
  // 1.定义属性
   var simpleDescription: String {get}
   var nickName:String{get set}
   // 2.定义方法
     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

[练习]
写一个实现这个协议的枚举。(感觉比较乱 找了一个)

 enum EnumConformToProtocol: ExampleProtocol {
    case First(String), Second(String), Third(String)

     var simpleDescription: String {
         get {
             switch self {
            case let .First(text):
                 return text
             case let .Second(text):
                 return text
             case let .Third(text):
                 return text
           
             }
         }
         set {
             switch self {
             case let .First(text):
                 self = .First(newValue)
             case let .Second(text):
                 self = .Second(newValue)
             case let .Third(text):
                 self = .Third(newValue)
             }
         }
     }
     mutating func adjust() {
         switch self {
         case let .First(text):
             self = .First(text + " (first case adjusted)")
         case let .Second(text):
             self = .Second(text + " (second case adjusted)")
         case let .Third(text):
             self = .Third(text + " (third case adjusted)")
         }
     }
 }

 var enumTest = EnumConformToProtocol.First("FirstVal")
 enumTest.simpleDescription
 enumTest.adjust()
 enumTest.simpleDescription

 enumTest = EnumConformToProtocol.Third("ThirdVal")
 enumTest.simpleDescription
 enumTest.adjust()
 enumTest.simpleDescription


 var e = EnumConformToProtocol.Second("Hello")
 var text = e.simpleDescription
 e.simpleDescription = "Adios"
 text = e.simpleDescription
 e.adjust()
 text = e.simpleDescription

注意声明 SimpleStructure 时候 mutating 关键字用来标记一个会修改结构体的方法。SimpleClass 的声明不需要标记任何方法,因为类中的方法通常可以修改类属性(类的性质)。

 extension Int: ExampleProtocol {
     var simpleDescription: String{
         return "The number \(self)"
     }

     mutating func adjust() {
       self += 42
     }
 }
 print(5.simpleDescription)

你可以像使用其他命名类型一样使用协议名 – 例如 创建一个有不同类型但是都实现一个协议的对象集合。当你处理类型是协议的值时,协议外定义的方法不可用。

【练习】
给Double类型写一个扩展,添加absoluteValue功能 double fabs (double)绝对值

你可以像使用其他命名类型一样使用协议名 – 例如 创建一个有不同类型但是都实现一个协议的对象集合。当你处理类型是协议的值时,协议外定义的方法不可用。

 let protocolValue: ExampleProtocol = a
 print(protocolValue.simpleDescription)
 // print(protocolValue.anotherProperty) // 去掉注释可以看到错误

即使 protocolValue 变量运行时的类型是 simpleClass ,编译器还是会把它的类型当做ExampleProtocol。这表示你不能调用在协议之外的方法或者属性。
推荐文章
Swift-----协议Protocol
Swift学习:协议

协议和扩展
使用采用 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)
 }

练习:
将 printer name 改为 "Never Has Toner" 使 send(job:toPrinter:) 函数抛出错误。

可以使用多个 catch 块来处理特定的错误。参照 switch 中的 case 风格来写 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)
 }

练习:
在 do 代码块中添加抛出错误的代码。你需要抛出哪种错误来使第一个 catch 块进行接收?怎么使第二个和第三个 catch 进行接收呢?

另一种处理错误的方式使用 try? 将结果转换为可选的。如果函数抛出错误,该错误会被抛弃并且结果为 nil。否则的话,结果会是一个包含函数返回值的可选值。

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

使用 defer 代码块来表示在函数返回前,函数中最后执行的代码。无论函数是否会抛出错误,这段代码都将执行。使用 defer,可以把函数调用之初就要执行的代码和函数调用结束时的扫尾代码写在一起,虽然这两者的执行时机截然不同。

 var fridgeIsOpen = false
 let fridgeContent = ["milk", "eggs", "leftovers"]

 func fridgeContains(_ food:String) ->Bool
 {
     fridgeIsOpen = true
    defer {
         fridgeIsOpen = false
     }

    let result = fridgeContent.contains(food)
     return result
 }
 fridgeContains("banana")
 print(fridgeIsOpen)

泛型
在尖括号里写一个名字来创建一个泛型函数或者类型。

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

你也可以创建泛型函数、方法、类、枚举和结构体。

 // 重新实现 Swift 标准库中的可选类型
 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])

练习:
修改 anyCommonElements(::) 函数来创建一个函数,返回一个数组,内容是>两个序列的共有元素。

 <T: Equatable> 和 <T> ... where T: Equatable> 的写法是等价的。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345