美文网首页iOS Developer
iOS Apprentice中文版-从0开始学iOS开发-第二十

iOS Apprentice中文版-从0开始学iOS开发-第二十

作者: Billionfan | 来源:发表于2017-08-14 01:08 被阅读234次

    在数组的方括号内必须写上类型,或者在Array后面的尖括号<>内写上类型。

    对于字典而言,你一共需要提供两个类型,一个用于键,而另一个用于值。

    Swift要求所有的变量和常量都必须有值。你可以在声明它们的时候给它们指定一个值,也可以通过init方法给它们分配值。

    有时,你需要一个变量可以没有值,这种情况,你需要将变量声明为可选型:

    var checklistToEdit: Checklist?
    

    你不能直接使用这种类型的变量;你必须在使用它们之前,侦测一下其中是否有值,这个行为就叫做可选型解包:

    if let checklist = checklistToEdit {
      // “checklist” now contains the real object
    } else {
      // the optional was nil
    }
    

    下面例子中的变量age就是一个可选型,因为没有任何保证说字典中存在一个名为"Jony Ive"的键,所以age的类型是Int?,而不是Int:

    if let age = dict["Jony Ive"] {
    // 使用age变量
    }
    

    如果你100%的确定字典中存在一个叫做"Jony Ive"的键的话,那么你就可以对age变量进行强制解包:

    var age = dict["Jony Ive"]!
    

    你使用感叹号来通知Swift,‘这个可选型不会为nil,我用我的名誉打赌!’,当然,如果你错了的话,这个变量的值为nil,那么app就会挂掉,你也就名誉扫地了,所以你在使用强制解包的时候一定要小心。

    另一种稍微安全点的强制解包方式叫做可选型链接。例如,下面的语句会在navigationController为nil时把app挂掉。

    navigationController!.delegate = self
    

    但是像这样做则不会把app挂掉:

    navigationController?.delegate = self
    

    位于问号后面的任何东西,都会在navigationController为nil时把它忽视掉。这个使用问号强制解包的语句等价于下面的语句:

    if navigationController != nil {
      navigationController!.delegate = self
    }
    

    在声明可选型的时候,也可以用感叹号来代替问号,这样就是一个隐式解包可选型了:

    var dataModel: DataModel!
    

    这样的变量会带来潜在的危险,因为你可以向使用常规变量那样直接使用它,并不需要先解包。如果它的值为空,那么app就挂了,而常规变量为空时,编译器会提示你怎么做。

    可选型平时被包裹起来,以避免app崩溃,但是使用了感叹号以后,就解除了可选型的安全级别。

    然而,有时使用隐式解包可选型比使用可选型要方便一些。当你无法给一个变量初始值,也无法用init方法对其初始化的时候,你就会需要到这种隐式解包可选型。

    如果你给了一个变量一个值后,就不应该在使它为nil,如果一个变量可以从有值变为nil,那么你最好还是使用用问号声明的可选型。

    方法与函数(Methods and functions)

    你已经学习过这样的一种对象了,它是所有app的基础组成部分,同时具有数据和功能。实例变量及常量提供数据,方法提供功能。

    当你调用一个方法,app就会跳转到方法中,逐条的执行其中的语句,当方法中最后一条语句被执行完毕后,app就会会到之前离开的地方:

    let result = performUselessCalculation(314)
    print(result)
    ...
    func performUselessCalculation(_ a: Int) -> Int {
      var b = Int(arc4random_uniform(100))
      var c = a / 2
      return (a + b) * c
    }
    

    方法经常会返回一个值给调用者,比如一个计算结果或者从一个集合中找到的一个对象。返回值的类型会写在->符号的后面。在上面的例子中,返回值的类型是Int。如果不存在->这个符号,那么就是说这个方法不返回任何值。

    方法就是属于某一特定对象的函数,Swift中也存在独立的函数,比如print()或者arc4random_uniform()。

    函数和方法的工作原理一样,一个可重复使用的功能块,但是函数不属于任何对象。像这种函数也被称为自由函数或者全局函数。

    下面是一些关于方法的例子:

    // 这个方法没有返回值及参数的方法
    override func viewDidLoad()
    
    // 这个方法有一个slider参数,但是一样没有返回值
    // 关键字@IBAction意味着这个方法可以被连接到界面建造器的控件上
    @IBAction func sliderMoved(_ slider: UISlider)
    
    // 这个方法没有参数,但是有一个Int型的返回值
    func countUncheckedItems() -> Int
    
    // 这个方法有两个参数,cell和item,但是没有返回值
    // 注意一下,第一个参数有一个外部名称for,而第二个参数有一个外部名称with
    func configureCheckmarkFor(for cell: UITableViewCell,
                               with item: ChecklistItem)
    
    // 这个方法有两个参数, tableView和section. 并且有一个Int型的返回值。
    // 第一个参数前的下划线代表这个参数没有外部名称。
    override func tableView(_ tableView: UITableView,
                            numberOfRowsInSection section: Int) -> Int
    
    // 这个方法有两个参数, tableView和indexPath.
    // 问号代表它返回一个为可选型的IndexPath对象。
    override func tableView(_ tableView: UITableView,
                        willSelectRowAt indexPath: IndexPath) -> IndexPath?
    

    在一个对象上调用一个方法,语法是object.method(parameters)。例如:

    // Calling a method on the lists object:
    lists.append(checklist)
    // Calling a method with more than one parameter:
    tableView.insertRows(at: indexPaths, with: .fade)
    

    你可以把调用方法想象为从一个对象向另一个对象传递消息:“嗨 lists,我从checklist对象中向你发送了append的消息。”

    你调用消息所属的对象被称为消息的接收者。

    从同一个对象中调用方法非常常见,下面的例子中,loadChecklists()调用了sortChecklists()。它们都是DataModel对象中的成员:

    class DataModel {
    fun loadChecklists() {
    ...
    sortChecklists()
    }
    fun sortChecklists() {
    ...
      }
    }
    

    有时你会写为下面这个样子:

    fun loadChecklists() {
    ...
    self.sortChecklists()
    }
    

    关键字self清晰的表明了DataModel对象自己是这个消息的接受者。

    ⚠️:在我们的课程中,调用方法的时候,我省略了self关键字,因为并不是必须要这样做。Object-C开发者会非常乐意在每个地方都写上self,所以你也许会见到它在Swift中也被大量使用。到底写与不写,这是程序员间可以引发战争的一个话题,但是无论如何,app并不是太关心这点。

    在一个方法的内部,你也可以使用self关键字来引用这个对象自己:

    @IBAction fund cancel() {
    delegate?.itemDetailViewControllerDidCancel(self)
    }
    

    这里cancel()方法将对象自身的引用发送给delegate,所以delegate知道谁发送了这个itemDetailViewControllerDidCancel()消息。

    同时注意一下这里的可选型链接。这个delegate属性是个可选型,所以它可以为nil。在调用方法前使用一个问号来确保delegate为nil时,app不会挂掉。

    方法经常会具有一个或多个参数,所以你可以让它们接收不同数据源上的数据工作。一个被限定了数据源的方法,可能不会非常有价值。看看下面的sumValuesFormArray()方法,它没有参数:

    class MyObject {
      var numbers = [Int]()
      fun sunValuesFromArray() ->Int{
      var total = 0
      for number in numbers {
       total += number
      }
      return total
     }
    }
    

    这里,numbers是一个实例变量。方法sumValuesFromArray()被这个实例变量绑定死了,如果这个变量不存在,那么这个方法就没用了。

    假设你在这个app中添加了第二数组,也想要应用上面的计算,那么其中一个方法是把这个方法复制一遍,重新命名为一个新的方法来处理这个新的数组。这样做确实可行,但是你也从此和聪明绝缘了。

    另一个好一点的选择是,给这个方法一个参数,使得你可以传送任何你想要计算的数组,这样,这个方法就从实例变量中解放出来了:

    func sumValues(from array: [Int])-> Int {
      var total = 0
      for number in array {
      total += number
      }
      return total
    }
    

    现在你可以用任何整数型的数组作为它的参数了。

    这并不是说方法不应该使用实例变量,只是说你想要一个方法的应用更加广泛,那么给它一个参数是个很好的选择。

    方法的参数经常会有两个名字,一个外部名称,一个内部名称,例如:

    fun downloadImage(for searchResult: SearchResult,withTimeout timeout: TimeInterval,andPlaceOn button: UIButton) {
    ...
    }
    

    这个方法有三个参数:searchResult,timeout和button。这些是内部名称,你在方法的内部用这些名称来调用参数。

    方法的外部名称是方法名称的一部分。所以这个方法的全名是downloadImage(for,withTimeout,andPlaceOn),Swift中的方法名称经常会特别的长。

    调用这个方法的时候,你需要使用外部名称:

    downloadImage(for:result,withTimeout:10,andPlaceButton)
    

    有时,你会看到一个方法它的第一个参数没有外部名称,取而代之的是一个下划线:

    override func tableView(_ tableView: UITableView,numberOfRowsInSection section: Int)-> Int
    

    这种情况经常出现在委托方法中,它是Object-C的遗留物,第一个参数的内部和外部名称都会被包含在方法名称中,比如在Object-C中downloadImage()方法的全名会是downloadImageForSearchResult。像这样的命名方式,以后会非常少见。如果是在Object-C中,这个方法的名称会是tableViewTableVIew,非常古怪是吧,而Swift 中,以下划线代替外部名称时,方法名称中就可以省略这个参数的外部名称,在Swift中,这个方法的全名是tableView(numberOfRowsInSection)。这样是不是容易明白多了?Swift在对方法命名时更加灵活,但它还是会保留一些旧的惯例。

    在一个方法的内部,你可以做以下事情:

    1、创建局部变量或者常量

    2、进行基本的数学运算,比如加减乘除

    3、将一个新的值放入变量(局部变量或实例变量)

    4、调用其他方法

    5、使用if或者switch作出判断

    6、用for或者while进行循环处理

    7、返回一个值给调用者

    让我们来看看if和for语句的更多细节。

    作出判断(Making decisions)

    if语句的基本结构是这个样子的:

    if count == 0 {
    text = "No Items"
    } else if count == 1 {
    text = "1 Item"
    } else {
    text = "\(count) Items"
    }
    

    if后面的表达式称之为条件。如果条件为真,那么if后面花括号内的语句会被执行。如果没有一个条件为真,那么最后一个else后面的花括号内的语句会被执行。

    你使用比较运算符来对两个值进行比较:

    == 等于
    != 不等于
    < 小于
    <= 小于等于
    大于 >
    大于等于 >=

    使用等于操作时,被比较的两个对象仅在相等时返回true,比如:

    let a = "Hello,world"
    let b = "Hello," + "world"
    print(a == b) //打印结果为true
    

    这个和Object-C有所不同,在Object-C中,必须两个对象是内存中的同一个实例,才会返回为true。而Switf中的==操作,仅仅是比较对象的值,而不管它在内存中是不是同一个对象,如果在Swift中像做这个操作的话,需要使用运算符 ===,三个等号。

    你还可以使用逻辑操作符来连接两个表达式:

    && 与操作,a && b必须在a和b都为true时才返回true
    ||或操作符,a || b当a,b其中之一为true时,返回true

    还有逻辑非操作符!,它的作用是将原本的true转为false,原本的false转为true。(不要和可选型弄混了,逻辑非操作符出现在对象的前面,而可选型的感叹号出现在对象的后面)

    可以使用括号()来对表达式分组:

    if ((this && that) || (such && so)) && !other {
    ...
    }
    

    它读作:

    if ((this and that) or (such and so)) and not other {
    ...
    }
    

    为了看起来更加清晰一些,我们写的有层次一点:

    if (
      (this and that)
      or
      (such and so)
    )
    and
      (not other)
    

    当然,你弄的越复杂,越难记清楚自己在做什么!

    Swift中还有一种非常强大的结构,可以用来做出判断,那就是switch语句:

    switch condition {
      case value1:
        //语句
      case value2:
        //语句
      case value3:
        //语句
      default:
        //语句
    

    它的效果和多个if else的效果是一致的,上面的代码等同于:

    if condition == value1 {
      //语句
    } else if condition == value2 {
      //语句
    }else if condition == value3 {
      //语句
    } else {
      //语句
    }
    

    相较之下,switch在这种情况中更加便利,而且意思清晰。而且Swift版的switc比Object-C版的更加强大。例如,你可以使用区间范围:

    switch difference {
      case 0:
        title = "Perfect!"
      case 1..<5:
        title = "You almost had it!"
      case 5..<10:
        title = "Pretty good!"
      default:
        title = "Not even close..."
    

    这里的..<是半开区间操作符。它可以创建两个值之间的区间,其中的值都是不重复的,半开区间1..<5等价于闭区间1...4。

    你会在后面的课程中见到switch语句的实际用例。

    注意一下,if语句中的reture会比方法中的returen更早的返回:

    fun divide(_ a: Int, by b: Int) ->Int {
    if b == 0 {
      print("不能除以0")
      return 0
    }
    return a / b
    }
    

    对于没有返回值的方法而言,if中的return甚至可以结束掉方法:

    fun performDifficultCalculation(list: [Double]) {
    if list.count < 2 {
        print("样本过少")
        return
      }
    //这里执行复杂的运算
    }
    

    在这个例子中,return的意思就是“我们退出方法吧”。任何return后面的语句都会被忽略掉。

    你也可以把上面的方法写成下面这个样子:

    fun performDifficultCalculation(list: [Double]) {
    if list.count < 2 {
        print("样本过少")
      } else {
    //这里执行复杂的运算
     }
    }
    

    像这种只有两种可能的情况下,上面两个方法的作用一样,使用哪个都可以,我个人比较喜欢第二种。

    有时你会看到下面这个样子的代码:

    fun someMethod() {
      if condition1 {
        if condition2 {
          if condition3 {
            //语句
          } else {
            //语句
         }
      } else {
      //语句
     } else {
      //语句
     }
    }
    

    这种代码非常难读,我喜欢将它们重构为下面这个样子:

    fun someMethod() {
     if !condition1 {
    //语句
    }
    
    if !condition2 {
    //语句
    }
    
    if !condition3 {
    //语句
    }
    
    //语句
    }
    

    这两段代码的作用其实是一样的,但是后一种更加容易理解。(注意一下,第二种写法中使用了!逻辑非来转换了表达式的意思)

    Swift中有一种特殊的语句,guard来帮助你处理这种复杂的情况,用guard重写一下上面的方法就是:

    fun someMethod() {
      guard condition1 else {
      //语句
      return
      }
    
    guard condition21 else {
      //语句
      return
      }
    
    ...
    

    你要自己尝试这些方法,比较看看哪种可读性最好,哪种看起来最好,这样慢慢的你就会很有经验了。

    循环(Loops)

    你之前已经见识过了,如何用for in来历遍一个数组:

    for item in items {
    if !item.checked {
    count += 1
    }
    }
    

    也可以写作:

    for item in items where !item.checked {
      count += 1
    }
    

    for in中的语句会对每个items数组中的对象执行一遍。

    注意一下,变量item的仅在for语句中有效,你不能在外面引用它,它的生命期比局部变量还要短。

    有些语言,也包括Swift 2,中的for语句是这个样子的:

    for var i = 0;i<5;++i {
    print(i)
    }
    

    当你运行这个代码,会得到如下结果:

    0
    1
    2
    3
    4
    

    然而,在Swift 3种,这种for循环已经被抛弃了,取而代之的是,你可以直接使用区间范围,就像下面这样:

    for i in 0 ... 4 {
    print(i)
    }
    

    顺便说一下,也可写作:

    for i in stride(from: 0,to: 5,by: 1) {
    print(i)
    }
    

    stride函数创建了一个专门的对象来代表从1到5,每次增加1。如果你只想要偶数,你可以把by参数改为2。如果你给by参数一个负数的话,那么stride就可以实现倒着数的功能。

    for语句并不是唯一的执行循环的语句,另一个非常强大的循环结构就是while语句:

    while something is true {
    //语句
    }
    

    while语句会一直保持循环,知道条件为false为止。还可以使用下面这种形式:

    repeat {
     //语句
    } while something is true
    

    在这种情况中,条件是在语句执行后才判断的,所以括号内的语句至少也会被执行一次。

    你可以使用while语句重写一下循环Checklists中的对象:

    var count = 0
    var i = 0
    while i < items.count {
     let item = items[i]
     if !item.checked {
     count += 1
     }
     i += 1
    }
    

    这些循环结构的作用大致相同,只是看起来有些不一样。每一种都可以使你重复执行一段语句,直到条件不符合为止。

    然而,使用while会比for in要看起来复杂一些,所以大多数时候,我们都会使用for in。

    使用for in、while、repeat并没有什么不同,只是可读性上有所区别。

    ⚠️:上面例子中的item.count和count是两种不同的东西,只是名字一样。item.count中的count是数组items中的属性用于返回数组中元素的个数;后面的一个count是一个局部变量,用于对没有激活对勾符号的item对象计数。

    就你可以在方法中使用return退出方法一样,你可以使用break来提前退出循环:

    var found = false
    for item in array {
      if item == searchText {
        found = true
        break
      }
    }
    

    这个例子中,for语句在数组中循环,直到找到第一个与searchText的值相当的值后,将found设置为true,然后退出循环,不再查看数组中剩下的对象。因为你已经找到了你想要的东西,所以没有必要把整个数组都循环完毕。

    还存在一个contiue语句,和break的作用正好相反。它的作用是立即跳到下一个迭代中,当你使用contiue时,你的意思就是“目前这个item已经结束了,我们去看看下一个吧!”

    在函数编程中,循环经常会被map,filter或者reduce替代。它们是一些操作集合的函数,对集合中每一个元素执行一段代码,并且返回一个新的集合作为结果。

    例如,在数组上使用filter,会保留符合某些条件的元素。比如要得到未激活对勾符号的ChecklistItem对象,你可以这样写:

    var uncheckedItems = items.filter { item in !item.checked}
    

    这样写比循环看起来要简单多了。函数编程是一个非常大的话题,所以在这里我们不会展开太多。

    对象(Objects)

    将功能和数据结合在一起的可重用单元,都是对象。

    数据是由对象中的实例变量和实例常量组成。我们经常以对象的属性形式引用它们。功能由对象的方法提供。

    在你的Swift程序中,你使用过已存在的对象,比如String,Array,Date,UITableView,以及你自己创建的对象。

    定义一个新的对象,你需要一个新的Swift文件,比如MyObject.swift,并且包含一个类(class)。比如:

    class MyObject {
     var text: String
     var count = 0
     let maximum = 100
     
    init() {
        text = "Hello World"
     }
    
    fun doSomething() {
      //语句
     }
    }
    

    在class的花括号内,你添加了属性(实例变量和实例常量)和方法。

    属性有两种类型:

    1、存储属性,它们通常是实例变量和实例常量。

    2、计算属性,不存储东西,而是执行某些逻辑

    下面是一个关于计算属性的例子:

    var indexOfSelectedChecklist: Int {
      get {
        return UserDefaults.standard().integerForKey("ChecklistIndex")
      }
     set {
       UserDefaults.standard().set(newValue,forKey: "ChecklistIndex")
     }
    }
    

    indexOfSelectedChecklist属性并不存储一个值,取而代之的是,每次有人使用这个属性时,它执行get或者set内的代码。另一个选择是,分别写一个setIndexOfSelectedChecklist()和getIndexOfSelectedChecklist()方法,但是这样读起来不是很好。

    关键字@IBOutlet的意思是,这个属性可以被界面建造器中的用户接口元素引用,比如label和button。这种属性通常都被声明为weak和可选型。类似的,@IBAction关键字被用于和用户交互时被触发的方法。

    这里有三种类型的方法:

    1、实例方法

    2、类方法

    3、init方法

    你已经知道了方法就是属于某一个对象的函数。调用这种类型的方法你首先需要一个这个对象的实例:

    let myInstance = MyObject()  //创建一个对象的实例
    myInstance.doSomething()   //调用方法
    

    你也可以创建一个类方法,这样就可以在没有实例的情况下使用这个方法。事实上,类方法经常被当作“工厂”方法使用,用来创建新的实例:

    class MyObject {
    ...
    class fun makeObject(text: String)-> MyObject {
        let m = MyObject()
        m.text = text
        return m
      }
    }
    
    let MyInstance = MyObject.makeObject(text: "Hello world")
    

    init方法,或者叫做初始化设置,在创建一个新的对象实例的过程中被使用。你也可以使用init方法来取代上面的那个工厂方法:

    class MyObject {
    ...
    init(text: String) {
        self.text = text
     }
    }
    

    init方法的主要目的是将对象中的实例变量填满。任何没有值的实例变量和实例常量都必须在init方法中被给予一个值。

    Swift不允许变量或者常量没有值(可选型例外),并且init方法是你给变量或者常量赋值的最后一次机会。

    对象可以拥有一个以上的init方法;具体使用哪一个要依据具体情况而定。

    例如,一个UITableViewController,从故事模版中自动被读取时,使用init?(coder)初始化,手工从nib文件中读取时,使用init(nibName,bundle)初始化,或者没有从故事模版和nib文件中构造时,使用init(style)初始化。有时你会用到这个,而有时你会用到那个。

    当对象不再被使用时,你可以提供一个deinit方法。在对象被破坏掉前调用它。

    顺便说一下,class并不是Swift中唯一定义对象的方法。还存在其他类型的对象,比如structs和enums。你会在后面学到这些,所以在这里,我们就点到为止了。

    协议(Protocols)

    一个协议就是一组方法名称的列表:

    protocol MyProtocol {
      func someMethod(value: Int)
      func anotherMethod()-> String
    }
    

    协议就类似于工作列表。它列出了你的公司中每个具体职位的工作。

    但是列表自己本身并不工作,它仅仅是打印出来给大家看的东西。所以你需要雇佣具体的员工来完成列表上的工作。而这些员工,就是具体的对象。

    对象需要被指明自己需要遵守的协议:

    class MyObject: MyProtocol {
     ...
    }
    

    这样,这个对象就需要完成协议中列出的所有方法。(否则,就炒了它)

    此时,你就可以引用这个对象,同时还有协议:

    var m1: MyObject = MyObject()
    var m2: MyProtocol = MyObject()
    

    对于代码中任何使用m2变量的部分,它是否是MyObject对象并不重要。m2类型是MyProtocol,不是MyObject。

    所有你的代码看到的是,m2是某个遵守MyProtocol协议的对象,但是具体是什么样的对象并不重要。

    换而言之,你并不关心你雇用的员工,是不是兼职其他工作,只要他和你需要的东西不冲突,你就可以雇佣他。

    我们已经快速的回顾了一遍你所遇到的Swift语法知识。在结束了这些理论之后,是时候开始我们的app开发了。

    相关文章

      网友评论

        本文标题:iOS Apprentice中文版-从0开始学iOS开发-第二十

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