[Cmoney 菁英软件工程师战斗营] IOS APP 菜鸟开发笔记(2)

前言

从上周末开始到周三,除了学习老师教的观察者模式(Observer Pattern)和几种排序方法外,其他的时间多数在利用Xcode的Playground,来练习Swift的基本语法。此外也将专案做了版本控制,和拆分了大致的专案资料夹架构(用MVVM的模式)。

以下附上目前整理完成的Swift笔记:

资料型态

  • 基本资料型态大致有 Int 、 Double 、 Float 、 Bool

  • 查看型别:

    var str
    str =  "abc"
    print(type(of:str))
    

可选型别(Optional)

  • Optional 本质是一个 enum 型别,里面定义了两种状况。

    1. none : 放 nil
    2. some: 放非 nil 值 ,可用 ! 取出 some 里的值
  • 任何型别只要有加上可选型别(optional type)都可以设置成nil。使用方法为在型别标注後面加上一个问号?,如下:

        // 在宣告变数时 型别标注後面加上一个问号 ?
        var someScore: Int?  // 因为目前尚未指派 所以目前 someScore 会被设置成 nil , 也就是没有值的状态
        someScore = 100
        print(someScore!) //如果确定可选型别的变数内有值,加上惊叹号可取出值
        someScore = nil

​ 如果没有经过这个步骤,不能直接把变数设成 nil , 比如以下这样写会报错:

        var someScore: nil		


空值判断 (if / guard let)

  • guard let 和 if let 的作用大同小异,同样可让 let 後的常数名和 optional 同名,主要的差别在以下几个地方:

    • guard let 的 else { } 程序将在 optional 无值时执行,跟 if let 相反,if let 的 { } 程序将在 optional 有值时执行。
    • guard let 後的常数可在 guard let else { } 後继续使用,if let 後的常数只能在 if let 的 { } 里使用。
    	// newName 可在 guard let else { } 後继续使用   
    	func showName(name: String?) {
           guard let newName = name else { return }
           print("my name is \(newName)")
        }
    
    	//newName 不可在 if let else { } 後继续使用    
    	func showName2(name: String?) {  
    
            if let newName = name {     
                print("my name is \(newName)") 
            }   
            print("my name is \(newName)")
        }
    
  • App 的表单输入页面很适合搭配 guard let 检查栏位。

  • guard let 也可以串接多个 optional,以下为检查表单栏位新增资料的例子:

        func createBook(title: String?, price: Double?, pages: Int?) {
    
           guard let title = title, let price = price, let pages = pages else { 
               return 
           }
           // 以下区块可加入新增 Book 的程序码
           print("\(title) costs $\(price) and has \(pages) pages.")
        }
    

字串(String)

  • 字串相加可以用 + 号 ,也可以用 .append()

  • 字串相等判断用 ==

  • == "" 或属性 .isEmpty判断是否为空字串

  • 属性 .count取得字串长度

  • 分割字串 : split(separator) ,分割後以阵列型态传回

    let str = "ab,cd,ef"
    let arr = str.split(separator:",") //用逗号分割
    
        var a = "a";
        //字串中用 " \(变数、常数或表达式) " 来串接其他型态变数,其他型态与字串不能直接用加号串接!
        var b = "ddd\(a)";    
        print(b) // 印出 ddda

        var c = "cc"
        var e = "e"
        print(c+e); //印出cce

        // u{24}的值为$这个符号, 代表 Unicode 纯量 U+0024
        let dollarSign = "\u{24}"  


        let str2 = "What a lovely day !"
        print(str2.count)  // 印出字元数量:19


        if str4.hasPrefix("It is") {  //如果前缀字串相同
            print("Prefix")
        }
 		if str4.hasSuffix("Sunday") {  //如果後缀字串相同
            print("Suffix")
        }


元组(tuple)

  • 将多个值组合成一个复合值,其内的型别可以不同,以一对小括号()前後包起来,每个值以逗号分隔,范例如下:
        let myInfo = ("Kevin Chang", 25, 178.25)

        let myHeight = myInfo.2  //这边依照顺序的索引值,取出178.25


        // 将上面宣告的 myInfo 分解成三个常数
        let (myName, myAge, myRealHeight) = myInfo        
        print("My name is \(myName) .") // 印出:My name is Kevin Chang .


        //把不需要的用底线 _ 标记
        let (_, _, myTrueHeight) = myInfo 
        print("My height is \(myTrueHeight) .") // 印出:My height is 178.25 .


        //在宣告元组时就个别给里面的值一个名称
        let herInfo = (name:"Jess", age:24, height:160.5)
        print("Her name is \(herInfo.name) . ") // 印出:Her name is Jess .
  • 元组比较大小会依序由左到右逐个比较,直到有两个值不相等为止。而如果所有值都相等,则会将这一对元组称为相等的。

  • Swift 在比较元组的成员时,限制最多只能比较六个成员,如果有七个或七个以上成员则无法比较。

        // true 因为 3 等於 3,但是 apple 小於 bird (依字串的各字元由左至右逐个比较)
        (3, "apple") < (3, "bird")
    

集合型别

​ Swift 提供三种基本的集合型别:ArraySetDictionary来储存集合资料。

  • Array 阵列:按顺序储存资料。

  • Set 集合:没有顺序、不能重复储存资料。

  • Dictionary 字典:没有顺序,键值对 key : value ,也就是可以经由唯一的识别键找到需要的值。

变数宣告

  • var : 宣告一般变数
  • let : 宣告常数

转型

var a = 1.2
var b = Int(a)  // b强转後等於1

空值聚合运算子

  • 即使变数或常数是 nil 也能变出预设值

  • a ?? b , 先判断a是否为nil,如果a有值,不是nil,就会解析a并返回,但如果anil,则返回预设值b

    let defaultColor = "red"
    
    var userDefinedColor: String? // 未指派值 所以预设为 nil
    
    var colorToUse = userDefinedColor ?? defaultColor // 未指派值给 userDefinedColor ,所以会返回 defaultColor
    
    print(colorToUse) // 这边即印出:red
    

区间运算子

  1. 闭区间运算子: a...b,定义一个包含从ab(包括ab)的所有值的区间。b必须大於等於a

  2. 半开区间运算子: a..<b,定义一个从ab但不包括b的区间。b必须大於等於a,但当a等於b时,则不会有值。

  3. 单侧区间运算子: a......a,代表一个只有以一边a为起点或终点,另一边则是无限延伸的区间

    let names = ["Anna", "Alex", "Brian", "Jack"]
    
    for name in names[2...] {  // 从阵列索引值为 2 的值开始依序印出 Brian  Jack
        print(name)
    }
    
    

阵列

  • 宣告阵列
        var arr3 = [Int]()  // 宣告一个型别为 Int 的空阵列
        var anotherArr: [Int] = []  // 宣告另一个型别为 Int 的空阵列
 		var arr : Array<Int> = Array<Int>() 
  • 合并阵列内容

        var threeInts = Array(repeating: 0, count: 3)  // repeating是指定预设值,这边代表阵列为 [0, 0, 0]
    
        // 接着再创建一个 [2,2,2] 的阵列
        var anotherThreeInts = Array(repeating: 2, count: 3)
    
        // 最後将两个阵列合并
        var SixInts = threeInts + anotherThreeInts
        // 会变成 [0,0,0,2,2,2]
    
  • 新增 / 插入 / 删除阵列内容

        var arr6 = ["Apples", "Eggs"]
    
        arr6.append("Milk")  //新增。变成 ["Apples", "Eggs", "Milk"] 
    
        arr6.insert("Rice" ,at: 0) //插入。变成 ["Rice" ,"Apples", "Eggs", "Milk"]
    
        arr6.remove(at:1) //移除。变成 ["Rice", "Eggs", "Milk"]
    
  • 用 for in 遍历 Array 中的值(类似for each)

        var arr7 = ["Rice" ,"Apples", "Eggs", "Milk"]
        for item in arr7 {
            print(item)
        }
        //当同时也需要获得阵列值时 可以使用 enumerated() 方法
        for (index, value) in arr7.enumerated() {
            print("Item \(index + 1): \(value)")
        }
        // 会依序印出:
        // Item 1: Rice
        // Item 2: Apples
    
  • sort 排序。 sort() 会影响原本的阵列内容; sorted() 则会将排序结果回传成另一阵列,不影响原内容。

    	arr.sort() //[1,2,5,7]
    
  • sort(by:) 设定排序方式。

    	arr.sort(by:>)  //[7,5,2,1]
    
  • reverse()reversed() 将阵列反置。

     	arr.reverse()  //[1,2,5,7]
    
  • contains(_:) 检查某元素是否存在於阵列中,并回传 boolean 值。

    	arr.contains(2) // true
    

字典(dictionary)

  • key(键) 必须是唯一且不可重复

  • 用来储存多个相同型别的值。每个值(value)都属於一个唯一的键(key),键作为字典中这个值的识别符号,所有键的型别也必须相同(键与值的型别不一定要相同)。

  • 字典内的值没有顺序,所以需要根据这个键(key)来找到需要的值(value)。宣告字典型别时,使用 Dictionary<Key, Value>

        // 宣告一个字典型别
        var someDict: Dictionary<String, String>

        // 或是这样也可以
        var someAnotherDict: [String:String , String:a, String:b]

条件判断 guard else

  • guard else跟 if else一样,後面都是接结果为 true 或 false 的条件,但 guard 却有以下几点不同之处:

    • guard 喜欢依赖别人,不能没有 else。

    • guard 後专门描述我们希望成立的条件。当条件成立时,程序将离开 guard 搭配的 else { },继续往下执行我们希望条件成立时做的事。

    • 当 guard 的条件不成立时,将执行 else { } 的程序。

    • else { } 的程序执行後,必须离开 guard 所在区块,如此才不会继续往下执行条件成立时要做的事。就像刚刚的例子,我们利用 return 离开 function motherSay。

         guard age > 18, weight > 40 else {  //多重条件
            print("年纪不够大,体重太轻,只能乖乖念书")
            return
         }
      

流程控制

for in 循环

  • 使用for-in遍历一个集合内的所有元素,像是一个数字区间、阵列、字典中的值或是字串内的字元,例子如下:

    for index in 1...3 {
        print(index)
    }
    

while循环

repeat-while循环

  • 类似Java的 do while 循环

switch case

  • swift 的 switch case 不需要写break,执行完第一个成功的case後,就会自动跳出。如果在特殊情况下需要执行紧接着的下一个case内的程序,就要用到 fallthrough。加上 fallthrough後进入到的下一个case,不会对其条件做比对,而是直接执行其内的程序。 比如以下程序码:

        let number7 = 5
        var str4 = ""
    
        switch number7 {
            case 2,3,5,7,11,13,17,19:
                str4 += "It is a prime number. "
                fallthrough
            case 100,200:
                str4 += "Fallthrough once. "
        }
        print(str4) //印出 It is a prime number. Fallthrough once.
    
  • case中比对的情况也可以是一个区间

        let number5 = 0
        var str3: String
    
        switch number5 {
            case 0...10:
                str3 = "几"
            case 11...100:
                str3 = "很多"
            default:
                str3 = "超级多"
        }
    
    	print("我有\(str3)颗苹果")  //印出:我有很多颗苹果
    

版本判断

  • 当不同版本的作业系统提供的API不一样时,在程序中必须判断作业系统版本,确保app可以顺利运作,程序码范例如下:

    //平台名称可以是iOS、macOS或watchOS。版本号可以是大版本号像是iOS 10,或是较小的版本像是macOS 10.12
    
    if #available(平台名称 版本号, ..., *) {   										     
        // 在某个平台或版本下使用特别的 API
    } else {
        // 而其他的平台或版本则使用其他的 API
    }
    
    if #available(iOS 10, macOS 10.12, *) {
        // 在 iOS 使用 iOS 10 的 API
        // 在 macOS 使用 macOS 10.12 的 API
    } else {
        // 使用先前版本的 iOS 和 macOS 的 API
    }
    

存取等级

  • 从宽松到严格依序为:

    1. open : 不同的模组可以继承也能够存取。例如一个定义在framework中的open类别,可以在 APP 中 import framework 然後继承、覆写它

    2. public: 模组内可以继承、覆写;模组外可以看到与使用,但不能继承、覆写。

    3. internal (预设) : 模组内可以继承、覆写;模组外看不到。

    4. fileprivate: 同一个档案内可以存取。

    5. private: 同一类可以存取。

      swift 4 开始,允许 extension 中的程序可以存取原本类别中的 private 等级变数或函式,但 extension 必须与原类别在同一个档案中

函式

  • 建立一个函式要使用func关键字,函式格式如下:
        func 函式名称(参数: 参数型别) -> 返回值型别 {
            内部执行的程序
            return 返回值
        }

      实际写成如下:

        func addOne(number: Int) -> Int  {
            // number 即为被指派参数的常数 只能在函式内部范围内使用
            let n = number + 10
            print(n)
            return n
        }

        addOne(number: 12)  // 呼叫函式传入整数 12  印出 13
      

​ 如果在函式呼叫时,不想要加上 argument label,可以用 _ 代替。范例如下:

 // ... 宣告一个参数数量不固定的函式,传进函式中的参数被转为阵列型态	
 // func addOne(_number: Int ...) 
 
 //  = 後为呼叫时没给值时的预设值	
 // func addOne(_number: Int = 5)  

		func addOne(_number: Int ) -> Int  { 
            // number 即为被指派参数的常数 只能在函式内部范围内使用
            let n = number + 10
            print(n)
            return n
        }
		addOne(12) 

​ // inout关键字,传址, 取出时变数前加 &

列举(enum)

  • swift中的enum可以写function,容易实现状态模式

  • 列举使用case关键字定义成员值,例子如下:

    enum CompassPoint {
        case north
        case south
        case east
        case west
    }
    
    var directionToHead = CompassPoint.west
    
    // 这时已经可以自动推断这个变数的型别为 CompassPoint
    
    directionToHead = .south // 如果要再指派新的值 可以省略列举的型别名称
    
    switch directionToHead {
        case .north:
        print("Lots of planets have a north")
        case .south:
        print("Watch out for penguins") // 这行会被印出
        case .east:
        print("Where the sun rises")
        case .west:
        print("Where the skies are blue")
    }
    

值型别与参考型别

  • Swift 中以记忆体配置的方式不同来说,可以分为值型别(value type)与参考型别(reference type)。值型别会储存实际的值,而参考型别只是储存其在记忆体空间中配置的位置。
  • class和closure(闭包)是参考型别
  • struct和enum是值型别
  • 在 Swift 中,所有的基本型别:整数、浮点数、布林值、字串、阵列和字典,都是值型别,并且背後都是以struct的形式实作。

下半周目标

本周剩下的时间会继续练习後面的语法,并且开始摸索 ARKit 的使用方式,因为我们的 APP 将会使用到 AR 的技术。


<<:  Day 10 - 主动学习 Active Learning

>>:  Gulp browser-sync DAY87

Day2 帐号申请与管理

访问控制(RAM)是什麽? 访问控制(Resource Access Management,RAM)...

day19: High order function

相信写过 javaScript ES6 的大家一定使用过,high order function, ...

[JS] You Don't Know JavaScript [Async & Performance] - Promises

前言 在上一张中我们介绍了使用callback function的目的与缺点,虽然可以帮助我们处理非...

[Day30] 第三十课 Azure学习建议与深入浅出Azure常用服务小结

终於来到第30天了,每天写下来不知不觉就一个月了,记得第一课还在自我介绍, 转眼间已经要第三十课。这...

当心已死前,来看看这篇吧!

撰写这篇时,其实心情已经平复了不少 从 2014 年加入新创团队至今也超过七年了,过程中从未有过长假...