这种Swift语言也具有很多这种脚本语法的特点,

作者: 编程  发布:2019-11-21

Swift是苹果推出的一个比较新的语言,它除了借鉴语言如C#、Java等内容外,好像还采用了很多JavaScript脚本里面的一些脚本语法,用起来感觉非常棒,作为一个使用C#多年的技术控,对这种比较超前的语言非常感兴趣,之前也在学习ES6语法的时候学习了阮一峰的《ECMAScript 6 入门》,对JavaScript脚本的ES6语法写法叹为观止,这种Swift语言也具有很多这种脚本语法的特点,可以说这个Swift在吸收了Object C的优点并摒弃一些不好的东西外,同时吸收了大量新一代语言的各种特点,包括泛型、元祖等特点。我在学习Swift的时候,发现官方的语言介绍文章(The Swift Programming Language)还是非常浅显易懂,虽然是英文,不过代码及分析说明都很到位,就是内容显得比较多一些,而我们作为技术人员,一般看代码就很好了解了各种语法特点了,基于这个原因,我对官网的案例代码进行了一个摘要总结,以代码的方式进行Swift语言的语法特点介绍,总结一句话就是:快看Sample代码,速学Swift语言。

1、Swift 2.0 带来哪些新变化

  • 常规变化:

    • 1、OS X 10.11、iOS 9 和 watchOS 2 SDK 采纳了一些 Objective-C 的特性用来提高 Swift 的编程体验, 如可空性、类型化集合和一些别的特性。

    • 2、编译器对冗余的协议一致性,未被使用的绑定值以及可以设为常量的变量这些情况目前会给予警告或报错。

    • 3、修复了跨文件协议遵循时符号不可见或者重复的错误。

    • 4、Swift 语言的调用约定更加智能,能够理解 API 所发生的变化和 Swift 所给出的警告。

    • 5、便利的可失败构造器(failable initializer)可以先返回 nil,而不必首先调用 self.init。这是有利的一 面,但指定了构造器在返回 nil 前仍要给所有字段初始化。

    • 6、find 函数改名为 indexOfsort 则变成了 sortInPlacesorted 变成了 sort

    • 7、String.toInt() 重名为 Int(String) 的可失败构造器,因为构造器语法更适合类型转换。

      • String 类型不再遵循 SequenceType,可以使用 .characters.utf8.utf16 对应字符集的运算。允许对泛型添加公共扩展。

      • 字符串长度长度计算由 count(String) 变为 String.characters.count

      • 字符串裁剪由 code.substringToIndex(advance(code.startIndex, 6)) 变为 let endIndex = code.startIndex.advancedBy(6) code.substringToIndex(endIndex)

    • 8、标准库中重构了很多泛型的全局函数(如 mapfiltersort),采用协议扩展方式增加这些方法。这个好处是对于其他的关联类型能很好的适配。

      • 非泛型类类型可以继承泛型类(强制类型参数固定)。

      • 修复了 Swift 中泛型需求打印时 “T==T” 的错误。

      • 在泛型函数中声明了类型参数但是在函数中没有使用时将产生一个编译时错误,例如:

      • func foo<T> () {} // error:generic parameter ’T’ is not used in function signature

    • 9、基本上可以使用 enum SomeEnum<T,U,V> 来声明 multi-payload 风格的枚举,这样就能正常运行。这用来提示未完成的指令寄存器(IR)引发的错误。

      • 在 Objective-C 的枚举类型导入到 Swift 时,已经废弃的枚举元素将不会影响可用元素的使用,这个可能需要 Swift 中一些枚举名称的改变。

      • 从 C 中导入的枚举类型都表示为 RawRepresentable,这包括哪些没有被声明为 NS_ENUMNS_OPTIONS 枚举值,所有这些枚举类型中的 value 属性都需要重名为 rawValue.

    • 10、方法和函数现在使用同样的参数命名规则了,我们可以用 “_” 符号来省略一个外部的参数名,为了简化使用,用来指定参数名的简化符号 “#” 被移除,因为 Swift 为默认参数提供了特殊的规则:

      • 声明:

            func printFunction(str:String, newline:Bool)
            func printMethod(str:String, newline:Bool)
            func printFunctionOmitParameterName(str:String, _newline:Bool)
        
      • 调用:

            printFunction("hello", newline:true)
            printMethod("hello", newline:true)
            printFunctionOmitParameterName("hello", true)
        
    • 11、条件循环语句 do/while 循环被重名为 repeat/while。关键字 do 目前用来引入一个新的作用域(这对新引进的错误处理和 defer 关键字很重要)。

          Swift 1.2:
      
              do {
                  ...
              } while <condition>
      
          Swift 2.0:
      
              repeat {
                  ...
              } while <condition>
      
    • 12、打印语句的改变,在 Swift1 中,有 println()print() 两个在控制台打印语句的方法,前者是换行打印,后者是连行打印。在 Swift 2 中,println() 已成为过去,取而代之的是他俩的结合体。

          Swift 1.2:
      
              func print(<stuff to print>)
              func println(<stuff to print>)
      
          Swift 2.0:
      
              func print(<stuff to print>, appendNewline:Bool = true)
      
          如果你想做连行打印,现在需要这样写:
      
              print("我要换行!", appendNewline: true)
      
    • 13、Swift 的文本注释(doc comments)换成了 Markdown 语法格式,与 Playgrounds 统一(Playgrounds 注释格式源于功能有限的 reStructured Text)。

          参数纵览语法:
      
              ‐ Parameters:
                  ‐ x:...
                  ‐ y:...
      
          单独参数语法:
      
              ‐ parameterx:...
              ‐ parametery:..
      
          返回值:
      
              ‐ returns:...
      
          其他需要在 QuickHelp 中高亮的语法字段,可以参考 Markdown 语法。
      
    • 14、在 Swift 中增加了 @objc(propertyName) 属性,当该属性导入到 Objective-C 时可以采用这个 propertyName 作为 getter/setter 访问器的默认名,例如:

          class MyClass:NSObject {
      
              // Objective‐C 属性被命名为 “theProperty”
              @objc(theProperty) property:String
      
              // Objective‐Cgetter 访问器被命名为 “theProperty”
              // Objective‐Csetter 访问器被命名为 “setTheProperty:”
          }
      
    • 15、注册通知由

              var types = UIUserNotificationType.Badge | UIUserNotificationType.Sound | UIUserNotificationType.Alert
              var acceptAction = UIMutableUserNotificationAction()
      
              acceptAction.identifier = "ACCEPT_IDENTIFIER"
              acceptAction.title = "Accept"
              acceptAction.activationMode = UIUserNotificationActivationMode.Foreground
              acceptAction.destructive = false
              acceptAction.authenticationRequired = false
      
              var inviteCategory = UIMutableUserNotificationCategory()
      
              inviteCategory.identifier = "INVITE_CATEGORY"
              inviteCategory.setActions([acceptAction], forContext: UIUserNotificationActionContext.Default)
              inviteCategory.setActions([acceptAction], forContext: UIUserNotificationActionContext.Minimal)
      
              var categories = NSSet(object: inviteCategory)
              var mySettings = UIUserNotificationSettings(forTypes: types, categories: categories as Set<NSObject>)
      
              UIApplication.sharedApplication().registerUserNotificationSettings(mySettings)
              UIApplication.sharedApplication().registerForRemoteNotifications()
      
          修改为:
      
              let acceptAction = UIMutableUserNotificationAction()
      
              acceptAction.identifier = "ACCEPT_IDENTIFIER"
              acceptAction.title = "Accept"
              acceptAction.activationMode = UIUserNotificationActivationMode.Foreground
              acceptAction.destructive = false
              acceptAction.authenticationRequired = false
      
              let inviteCategory = UIMutableUserNotificationCategory()
      
              inviteCategory.identifier = "INVITE_CATEGORY"
              inviteCategory.setActions([acceptAction], forContext: UIUserNotificationActionContext.Default)
              inviteCategory.setActions([acceptAction], forContext: UIUserNotificationActionContext.Minimal)
      
              let categories = NSSet(object: inviteCategory) as! Set<UIUserNotificationCategory>
              let mySettings = UIUserNotificationSettings( forTypes: [.Alert, .Badge, .Sound], categories: categories)
      
              UIApplication.sharedApplication().registerUserNotificationSettings(mySettings)
              UIApplication.sharedApplication().registerForRemoteNotifications()
      
  • 内部的可见性:

    • 这解决了单元测试中的一个较大的难点。以前的做法:

      • Swift 文件包含在 test target 中。现在不同的模块中有重复的类的定义,出现无法将 “X” 转换为 “X” 这样非常可怕的错误,有时会无法执行特定的测试。

      • 在测试中引入引入主程序(main program)作为一个模块。现在一切都声明为 public,所以对于测试来说都是可见的,有时候也包括应该声明为 private 的内部细节。

    • 现在可以启用 testability,它就像 C# 中的 InternalsVisibleTo。主应用程序目标模块的内部细节对测试模块可见。

      • 在对应用或框架的测试设置中,启用 testability。
      • 在单元测试中,使用 @testable import { ModuleName }
    • 这将导致测试忽略某些优化行为并保留稍后导入到测试模块中的那些内部符号。官方文档警告说,由于阻止了某些优化,因此这只适用于调试和测试版本。

  • 模式匹配:

    • Switch 语句的模式匹配(pattern matching)语法和 “if let ..., .... where” 语法一直在推广。可以在任何控制流中使用逗号操作符和 where 条件语句。还可以使用新的 case 条件语句,例 - 如:if case .Silly(let a) { }。还有一种用于 Optional<T> 的特殊形式:if case let a? = anOptional { }

      • 模式匹配在循环语句中也可以使用:for case let thing? in array { }。 这又是值得单独成文的另一个特性。

      • 类型标注不能用于模式匹配,而需要作为标注声明的一部分:

        • 这意味着,以前的这样的写法:

          • var (a:Int, b:Float) = foo()
        • 需要被重构为:

          • var (a, b):(Int, Float) = foo()
        • 其实这个改动原因是为了和元组用法相区分。

  • 错误处理:

    • NSError 变成 throw。这不是我们一贯所认识的异常,这是一个使函数提前返回 Result 的操作,单隐藏了所有提前返回的对象,也隐藏了错误解析(error unwrapping)过程等内容。

          let systemAttributes: [NSObject: AnyObject]?
      
          do {
      
              systemAttributes = try NSFileManager.defaultManager()
                                                  .attributesOfFileSystemForPath(documentDirectoryPath.last!)
      
          } catch _ {
      
              systemAttributes = nil
          }
      
          它完美地与 Objective-C 进行互操作,Swift 语言中,将标记为 throws 的方法作为选择器。这是使用 NSError 的方法,
          -(BOOL or nullable type)someMethodTakingParam:(type)param error:(NSError **),
          这种样式会自动引入标记为 throws 的方法。
      
    • 应该明白的是这并不像 Java 中已经被检查过的异常(checked exception)那样。Swift 语言并不关心异常的类型,或者处理或者不处理。这又是值得单独成文的另一功能特性。

  • guard 语句块:

    • 显式地声明你要恒成立的条件语句,恒成立时跳过整个 guard 语句。这样做的好处是绑定在 guard 语句的变量在函数的其他部分也可用。这就避免了将所有的东西都围绕一条 if 语句嵌套使用来解析(unwrap)可选类型的变量。执行到函数中 guard 语句中的 else 部分,函数一定会退出并抛出异常。也可能会调用带有 @noreturn 标记的函数。
  • Defer 关键字:

    • 关键字 defer 也很重要,因为它可以取代传统 C 风格的 “if (err) goto cleanup”。获得资源后接着就是 defer { release_resource() }。然后不管函数返回结果如何,获得的资源都将被清理。这也意味着资源的释放紧随获取资源之后。这看起来不起眼儿,实则很重要。
  • NS_OPTIONS 和 OptionSetType:

    • NS_OPTIONS 类型现在遵循 OptionSetType 协议,这样可以避免 set 样式的接口调用。位操作枚举(bitwise enumeration)与数组风格的语法相结合,而不使用管道符 “ | ” 按位操作,并且具有所有范围的集合操作功能。检查一下是否具有 contains 功能的标志,或能够执行像 isSubsetOfisDisjointWith 等这样集合操作的其他功能。这是显著的改进,表达了不直接对位进行操作的意愿。

    • 避免采用如下位运算的调用方式:

          // Swift1.2:
      
              object.invokeMethodWithOptions(.OptionA | .OptionB)
              object.invokeMethodWithOptions(nil)
      
              if options & .OptionC == .OptionC {
      
                  //.OptionC 被设置
              }
      
    • 选项设置支持字面量语法和 set 样式的调用,如 contains:

          object.invokeMethodWithOptions([.OptionA, .OptionB])
          object.invokeMethodWithOptions([])
      
          if options.contains(.OptionC) {
      
              //.OptionC is set
          }
      
    • 这种变化意味着位操作枚举实际上不再是枚举了。将这些位操作枚举声明为结构体,实现 OptionSetType 协议,提供 rawValue 属性。并且创建值作为结构体的静态成员。Swift 便会搞定其余的一切,自动提供所有集合的操作。

    • 在 Swift 中一个新的 Option 设置类型可以采用结构体遵循 OptionSetType 协议的方式编写。如果该类型中指定了一个 rawValue 属性和 static let 的常量定义,那么标准库将会为其他选项提供默认实现:

          structMyOptions:OptionSetType {
      
          let rawValue:Int
              static let TuringMachine = MyOptions(rawValue:1)
              static let LambdaCalculus = MyOptions(rawValue:2)
              static let VonNeumann = MyOptions(rawValue:4)
          }
      
          let churchTuring:MyOptions = [.TuringMachine, .LambdaCalculus]
      
  • 协议扩展:

    • 在 Swift 1.0 时代,协议(Protocol)基本上类似一个接口,定义若干属性和方法,供类、结构体、枚举遵循和实现。在 Swift 2.0 中,可以对协议进行属性或者方法的扩展,和扩展类与结构体类似。,包括与类型约束有关的通用协议。还可以自己提供协议的默认实现。这让我们开启了面向协议编程的篇章。先前,你不能,你说:“我要使用方法 X 来扩展 CollectionType,但只有集合中的类型满足某些条件才可以”。现在,你可以这么做,并且很多像 map,filter 和 sort 这样的全局函数已经进行了扩展。这样就解决了很多痛点,这也是值得单独成文的内容。同时,要看看 WWDC 的面向协议编程(Protocol Oriented Programming)了解一些细节。

    • Swift 中,大多数基础对象都遵循了 CustomStringConvertible 协议,比如 Array、Dictionary(Swift 1.0 中的 Printable 协议),该协议定义了 description 方法,用于 print 方法打印对象。现在我们对该协议扩展一个方法,让其打印出大写的内容:

          var arr = ["hello", "world"]
      
          print(arr.description)         // "[hello, world]"
      
          extension CustomStringConvertible {
              var upperDescription: String {
                  return "(self.description.uppercaseString)"
              }
          }
      
          print(arr.upperDescription)    // "[HELLO, WORLD]"
      
    • 如果在 Swfit 1.0 时代,要想达到上述示例的效果,那么我们需要分别对 Array、Dictionary 进行扩展,所以协议的扩展极大的提高了我们的编程效率,也同样使代码更简洁和易读。

  • available 检查:

    • 作为 iOS 开发者,谁都希望使用最新版本 iOS 的 Api 进行开发,省事省力。但常常事与愿违,因为我们经常需要适配老版本的 iOS,这就会面临一个问题,一些新特性特性或一些类无法在老版本的 iOS 中使用,所以在编码过程中经常会对 iOS 的版本做以判断,就像这样:

          if NSClassFromString("NSURLQueryItem") != nil {
              // iOS 8 或更高版本
          } else{
              // iOS8 之前的版本
          }
      
    • 以上这只是一种方式,在 Swift 2.0 之前也没有一个标准的模式或机制帮助开发者判断 iOS 版本,而且容易出现疏漏。在 Swift 2.0 到来后,我们有了标准的方式来做这个工作:

          if #available(iOS 8, *) {
      
              // iOS 8 或更高版本
              let queryItem = NSURLQueryItem()
      
          } else {
              // iOS8 之前的版本
          }
      
    • @available 属性自 Swift 1.2 就存在了并且后续支持得很好。添加了一个新的陌生语法 if #available(),为处理版本检查提供了支持。而不是插入你喜欢的方法。

    • 遗憾的是你不能只声明一个属性 UISearchController 并将 target 设置为 iOS 7,然后只允许访问类中的属性。Swift 希望整个类的定义都可以或者不可以。也可以不再采用协议,除非支持 target设置中所有的操作系统版本,除非将整个类标记为只在更新的操作系统版本可用。

    • 这意味着使用 if #available() 存在单独的子类和对创建适当对象的保护。尽管如此,我个人还是发现了一个 Bug,应用在 iOS 4.0-4.1 发生崩溃,由于编译器没有发出警告,方法只在 iOS 4.2 才引入,因此我犹如与定时炸弹相伴。

  • C 函数指针:

    • Swift 现在可以使用 C 函数指针,CFunctionPointer<T -> U> 类型被移除,C 函数现在使用新的 @convention(c) 属性声明,和其他函数类型一样,@convention(c) T -> U 是一个非空的除非是它是可选的。任何全局函数,嵌套函数和不捕获状态的闭包都可以作为一个 C 函数指针直接传递。你也可以调用来自 C 程序的函数。

    • 你可以显示地使用新属性 @convention(c),表示函数应该使用 C 调用约定,简单痛快!尽管我想不出在此对块(block)的支持有何用,作为所发生变化的一部分,@objc_block 也被删掉了,使用 @convention(block) 取而代之。@convention(swift) 默认支持所有函数和闭包。

  • API 审计:

    • 大量的 API 已经进一步进行了审计而更合理。举几个例子:

      • UITableView 的 dequeueReusableCellWithIdentifier 方法现在返回 UITableViewCell? 类型的对象。

      • UIKit 的属性现在也被声明为了实际的属性。

      • translatesAutoresizingMaskToConstraints = false 代替了 setTranslatesAutoresizingMaskToConstrains(false)

  • 库:

    • 这并不是编程语言所特有的。iOS 9 含有不同版本的 Swift 标准库,并且在未来系统中将添加修正后的 Swift 标准库。结合新的 App Thining 技术,下载过程中苹果商店会将 Swift 标准库剥离出去的。我仍然在追根溯源地探求这究竟是如何工作的。
  • 遗漏:

    • 明显的一个遗漏是处理异步代码。苹果公司为我们提供了 GCD,这是一个强大的基础类库,可以构建很多异步操作和并发原语。然而,这些天我们做的每件事,构建用户接口和 API 都需要考虑异步性和并发性。我们把一个文件读操作锁定一段时间,对用户来说整个世界就都静止了。这是个持续的痛点,不是多大的事儿,但如果经常性地每天重复,恐怕也是不行的。C# 和 JavaScript 都采用了 async/await 来为异步代码提供一流的语言支持。我想很多人都想知道,Swift 会提供什么样的语法糖来帮助我们在实现异步操作方面确保正确性。

前言

  • Swift 全面支持 Unicode 符号。
  • Swift 中的定义和实现是在同一个单元中的,通常一个 Swift 源代码单文件是以 “.Swift” 结尾的。
  • Swift 不需要单独编写一个 main 函数作为入口,在 Swift 语言中函数是一等成员,编译器会自动将遇到的第一个函数作为入口。
  • Swift 允许我们不用在行尾加分号 “;”。但如果在同一行有两个甚至多个表达式,需要在每个表达式后面加上分号。

  • Playground 是一种编写代码时可以即时预览代码运行效果的功能。使用 Playground 后,在实际项目中可以为我们节省不少功能调试和函数测试时间,这些时间完全可以从事其他创造性的活动。

  • Swift 常用标注:

    • // MARK: 添加注释说明,加 “-” 添加分割横线
    • // FIXME: 表示此处有 bug 或者要优化
    • // TODO: 一般用于写到哪了做个标记,然后回来继续

1、语法速览

var myVariable = 42
myVariable = 50
let myConstant = 42

变量定义用var,常量则用let,类型自行推断。

 

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

用括号包含变量

 

let quotation = """
I said "I have (apples) apples."
And then I said "I have (apples + oranges) pieces of fruit."
"""

代码通过三个双引号来包含预定格式的字符串(包括换行符号),左侧缩进空格省略。

 

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

初始化函数也比较简洁。

 

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-else这些和其他语言没有什么差异,for ... in 则是迭代遍历的语法,控制流方式还支持其他的while、repeat...while等不同的语法。

 

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

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

这部分则是可空类型的使用,以及可空判断语句的使用,可空判断语句在Swift中使用非常广泛,这种相当于先求值再判断是否进入大括符语句。

 

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

Switch语法和常规的语言不同,这种简化了一些语法,每个子条件不用显式的写break语句(默认就是返回的),多个条件逗号分开即可公用一个判断处理。

 

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
for (kind, numbers) in interestingNumbers {
    for number in numbers {
        if number > largest {
            largest = number
        }
    }
}
print(largest)

上面字典遍历的方式采用for...in的方式进行遍历,另外通过(kind, numbers)的方式进行一个参数的解构过程,把字典的键值分别付给kind,numbers这两个参数。

 

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

上面的for...in循环采用了一个语法符号..<属于数学半封闭概念,从0到4,不含4,同理还有全封闭符号:...全包含左右两个范围的值。

 

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

上面是函数的定义,以func关键字定义,括号内是参数的标签、名称和类型内容,返回值通过->指定。

上面函数需要输入参数名称,如果不需要参数名称,可以通过下划线省略输入,如下

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

另外参数名称可以使用标签名称。

func greet(person: String, from hometown: String) -> String {
    return "Hello (person)!  Glad you could visit from (hometown)."
}
print(greet(person: "Bill", from: "Cupertino"))
// Prints "Hello Bill!  Glad you could visit from Cupertino."

嵌套函数如下所示。

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

复杂一点的函数的参数可以传入函数进行使用,这种类似闭包的处理了

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

 

 

class Shape {
    var numberOfSides = 0
    func simpleDescription() -> String {
        return "A shape with (numberOfSides) sides."
    }
}

类的定义通过class关键字进行标识,默认的权限是internal,在项目模块内部可以访问的,非常方便。

使用则如下所示,可以通过点语法直接获取属性和调用方法。

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

 

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

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

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

类通过使用init的指定名称作为构造函数,使用deinit来做析构函数,使用self来获取当前的类引用,类似于其他语言的this语法,super获取基类的引用。

其他的处理方式如继承、重写的语法和C#类似。

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

 

类的属性使用get、set语法关键字,和C#类似

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

 

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

类属性的赋值可以进行观察,如通过willSet在设置之前调用,didSet在设置之后调用,实现对属性值得监控处理。

 

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

和类及其他类型一样,枚举类型在Swift中还可以有方法定义,是一种非常灵活的类型定义,这个和我们之前接触过的一般语言有所差异。

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

结构类型和类的各个方面很类似,结构支持构造函数,方法定义,属性等,重要一点不同是结构在代码传递的是副本,而类实例传递的是类的引用。

 

protocol ExampleProtocol {
    var simpleDescription: String { get }
    mutating func adjust()
}

这里的协议,类似很多语言的接口概念,不过比常规语言(包括C#)的接口更加多样化、复杂化一些。

Swift的协议,可以有部分方法实现,协议可以可选,继承其他协议等等。

 

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

扩展函数通过extension进行标识,可以为已有的类进行扩展一些特殊的方法处理,这个类似C#的扩展函数。

 

func send(job: Int, toPrinter printerName: String) throws -> String {
    if printerName == "Never Has Toner" {
        throw PrinterError.noToner
    }
    return "Job sent"
}

异常处理中,函数声明通过throws关键字标识有异常抛出,在函数里面通过throw进行异常抛出处理。

而在处理有异常的地方进行拦截,则通过do...catch的方式进行处理,在do的语句里面,通过try来拦截可能出现的异常,默认catch里面的异常名称为error。

do {
    let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
    print(printerResponse)
} catch {
    print(error)
}

可以对多个异常进行判断处理

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

还可以通过使用try?的方式进行友好的异常处理,如果有异常返回nil,否者获取结果赋值给变量

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

 

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)

使用defer的关键字来在函数返回前处理代码块,如果有多个defer函数,则是后进先出的方式进行调用,最后的defer先调用,依次倒序。

 

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)

Swift支持泛型,因此可以大大简化很多函数的编写,提供更加强大的功能。

enum OptionalValue<Wrapped> {
    case none
    case some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .none
possibleInteger = .some(100)

 

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

泛型的参数支持where的关键字进行泛型类型的约束,如可以指定泛型的参数采用什么协议或者继承哪个基类等等。

 

2、Swift 2.2 新特性

  • 允许更多的关键字用做参数名:

    • 好的参数名对于提高代码可读性很重要。在 Swift 中很多关键字比如 in,repeat,defer 都不能用做参数名。2.2 中,除了少数修饰参数的关键字外都将允许用作参数名。

      图片 1

  • 为 Tuples 增加对比操作符:

    • 当前,Tuples 的数据不能使用 == 操作符,2.2 中将支持 Tuples。

      图片 2

  • 关联已存在类型时,不再使用 typealias:

    • typealias 现在有两个用处:
      • 为一个已经存在的类型取个别名
      • 在协议中作为一个类型的占位名称
    • 代码如下:

      图片 3

    • 这是两种完全不同的用法,不应该用一样的关键字。2.2 中将第一种情况时,启用新的关键字 associatedtype

  • 函数签名将包括参数名:

    • 一个函数有相同的函数名,参数名不同有多个重载很常见。当有多个重载时,在调用时能够通过参数名来区别。但是在获取类型时,却不包括参数名。

    • 举例 UIView 中有这么几个方法:

      图片 4

    • 使用时可以通过参数名区分:

      图片 5

    • 但是这样使用时却会报错,2.2 中将会解决这个问题。

      • let fn = someView.insertSubview // ambiguous: could be any of the three methods
  • 一个新的方法生成 selector:

    • 现在为了生成 OC 下使用的 selector 只能使用字符串生成,没有类型检查,很容易造成失误。将提供一个 #selector() 方法生成 selector,如下:

    • let sel = #selector(UIView.insertSubview(_:at:)) // produces the Selector "insertSubview:atIndex:" 增加 #if swift 语法判断当前 swift 版本

    • 使用如下:

      图片 6

1、Swift 基本数据类型

  • 1)变量与常量

    • 在 Swift 语言中声明变量使用 var 关键字,声明常量使用 let 关键字。

          // 定义 String 类型的字符串  str
          let str:String = "hello world"
      
    • 声明时类型是可选的,如果在声明时没有指定类型且对变量赋了初值,编译器会自动推断常量或者变量的类型,这种机制被称为 “类型推断”。如果在声明时指定了类型又赋了初值,那么指定的类型必须和赋给它们的值一样,Swift 是一门强类型语言,不能将变量本身类型之外的值赋值给它。如果没有赋给初值,务必声明变量或者常量的类型,并用冒号充当分隔符,否则编译会报错。

    • Swift 语言将具体的某种类型的值称之为类型字面量。例如 let num = 2.8 中的 "2.8" 就是浮点类型字面量。

  • 2)整型

    • Swift 语言拥有继承自 C 语言的有符号类型 Int、Int8、Int16、Int32、Int64,以及无符号整形 UInt、UInt8、UInt16、UInt32、UInt64。其中 IntUInt 类型的字长始终和当前平台的原生字长相同,即 32 位系统下声明获得的是 32 位的整型,64 位系统下获得的是 64 位的整型。

    • 整型的取值范围:

      • 最小值:Int8.minINT8_MIN
      • 最大值:Int8.maxINT8_MAX
    • 整型的声明:

      • 隐式声明机制:

            // 自动调用构造函数
            let intNum:Int = 12
        
      • 显式声明机制:

            // 显式的调用初始化构造器
            let intNum = Int.init(22)
        
    • 其他方法或属性:

          // 计算两个数字之间的距离(两数之差)
          num.distanceTo(15)
      
          // 访问变量或常量的字符串版本
          num.description
      
          。。。。。
      
  • 3)浮点型

    • Swift 语言为我们提供了两种有符号浮点数类型,FloatDoubleFloat 是 32 位浮点数类型,Double 是 64 位浮点数类型。当使用类型推断声明一个浮点型变量或者常量时,变量或常量总是默认被推断为类 Double 型。

    • 浮点型的声明:

          let floatNum:Float = 2.1
      
          // 默认被推断为 Double 型
          let doubleNum = 2.2`         
      
  • 4)布尔型

    • Swift 语言中,布尔型只有两种值,truefalse。如果在 Swift 语言中直接使用零或者非零来表示逻辑真假,编译器一定会弹出异常。可以直接在布尔变量前加 “!”,来达到布尔值取反的作用。

    • 布尔型的声明:

          let boolNum:Bool = false
      
  • 5)值类型/引用类型

    • 在 Swift 语言中,所有的类型都可以被分为 “值类型” 或者 “引用类型”,可以将其理解为函数参数传递的方式。
    • 从程序的角度来看,值类型和引用类型是相对的一个概念,其中的差别就在于:对新的对象产生赋值等指向性的操作之后,再次操作赋值对象或被赋值对象是否会同步于另外一个对象。
    • 在 Swift 语言中,大多数类型都是值类型的,但是也有一些特殊情况,比如可以在函数参数定义中使用 inout 关键字将参数定义为引用类型。

          // a,b 都是引用类型
          func swapT<T>(inout a:T, inout b:T)
      
  • 6)可选类型

    • Swift 语言为我们提供了一种全新的、更加安全的类型 —— 可选类型。可选类型是使用范型枚举的形式来组织的,也就是说此特性可以运用于所有的类型、结构体、类或者其他复杂数据类型。

    • 可选是指当一个变量、常量或者其他类中存储有值的时候返回里面存储的值,没有值的时候返回 nil。nil 不能用于非可选的变量或者常量,如果声明了一个可选的变量或者常量没有初始化,程序会默认赋值 nil。在 OC 中 nil 表示的是一个指向不存在对象的指针,而 Swift 中表示空的关键字为 “nil”,它没有其他含义。

    • 可选的声明:

      • 可选的标准声明形式是在程序中使用类型名紧跟 “ ? ”。

            var value:Int?
        
            print("(value)")
            // 或 
            print("(value?.description)")
        
            // 输出为 nil
        
      • 可选的显式声明形式。

            var value:Optional<Int>
        
            print("(value)")
            // 或 
            print("(value?.description)")
        
            // 输出为 nil
        
    • <1>、可选绑定(Optional binding):

          var value:Optional<Int> 
          if var maxValue = value { 
              maxValue++
              print("(maxValue)") 
          }
      
      • 如果 value 值为 nil,则不执行变量 maxValue 的声明,同时也不执行 if 判断语句中第一个分支的代码段,这样程序会很容易被理解,而且只需要这样简单的两行代码就避免了因为使用值为 nil 的对象导致的程序异常。
    • <2>、强制解析可选:

      • 如果确定这个可选类型中的变量肯定包含值的时候,可以使用名称紧跟 “ ! ” 的方式强制获取类型可选中的值,从而省略判断步骤。但是如果这个变量中没有值,使用强制解析可选可能会在运行期弹出异常。这种机制叫做“强制解析可选”。
    • <3>、隐式解析可选:

      • 在某些程序架构中,在特定模块中可以确定某个可选变量总是有值的,这种时候可以使用隐式解析可选解析可选,隐式解析可选用于一个确定会有值的可选类型实例声明。可以将可选变量声明中的“ ? ”改为“ ! ” 来标注一个隐式解析可选。

            var nullValue:String! = "Not Null String"
            print(nullValue)
        
    • <4>、可选运算符:

      • 可选运算符 “ ?? ” 的执行逻辑是表达式 var value = a ?? b 中当操作数 a 不为 nil 时表达式返回操作数 a 的值,当操作数 a 为 nil 时表达式返回操作数 b 的值。

            var value1:Int?
            var value2 = 3
            var value = value1 ?? value2
        
            print(value)
        
    • <5>、可选链:

      • 就是将可选的调用链接在一起形成一个链,如果任何一个节点为空(nil),将导致整个链失效。而不会引发强制解包可选时发生的错误。可选链可以多层可选。
  • 7)泛型

    • 使用同样的操作可以应用于不同的数据类型。泛型编程的实现是我们程序在另一种抽象层次上的提升。类是现实世界事物的抽象,而泛型则是现实世界行为的抽象。在 Swift 语言中,泛型可以说是用的最广最强大的特性之一,因为在 Swift 语言本身的语言底层大量的使用了泛型。

    • 泛型同其他语言相通,用“< >” 符号来声明泛型。

          func +<T : _IntegerArithmeticType>(lhs: T, rhs: T) -> T
      
    • 泛型使得我们能够在编写好一份代码之后,应用于多种数据类型,甚至为了使用安全起见,我们还能限制响应的泛型数据类型,必须遵从某些约束。

          func swapT<T>(inout a:T, inout b:T)
          struct Dictionary<Key : Hashable, Value> : CollectionType, DictionaryLiteralConvertible { }
      
  • 8)元组

    • 可以通过使用元组,把多个不同类型的值组合在一起,组成一个复合值。元组中的元素类型可以是相同的,也可以是不同的。元组的声明中也可以使用类型推断。元组可以用作函数返回值,它可以使函数能一次返回更多的信息。

      • 元组的声明:

        • 标准声明:

              // 定义时指定元组名称、元素名称并且初始化
              let myProject = (oneElement:"game", twoElement:2048)
          
        • 匿名声明:

              // 声明一个匿名的元组
              let (appType, appName) = ("game", 2048)
          
      • 元组中元素的访问:

        • 标准声明的元组:

              // 使用元素名访问
              print(myProject.oneElement)
          
              // 使用元素在元组中的顺序下标访问
              print(myProject.0)
          
        • 匿名声明的元组:

              // 使用元素名访问
              print(appType)
          
  • 9)枚举

    • 枚举是一种自定义的数据类型,在 Swift 中枚举类型拥有相当高的自由度。在 Swift 语言中枚举是一级类型,它拥有在其他语言中只有类才拥有的一些特性,比如实例方法,实例构造器等。枚举定义了一个常用的具有相关性的一组数据,并在你的代码中以一个安全的方式使用它们。

    • 一个枚举通常包含多个枚举成员,枚举成员可以包括计算型属性、类型别名,甚至其它枚举、结构体和类。枚举声明中,每一个事件块都由一个 case 关键字开始。多个成员的值可以出现在一行上,用逗号分隔。

    • 枚举是值类型,并且只有在赋予变量或常量,或者被函数调用时才被复制。

    • <1>、标准定义格式:

          enum enumerationName {
      
              case enumerationCase1
              case enumerationCase2
              .....
          }
      
          enum PointRect {
      
              case top
              case bottom
              case left
              case right
          }
      
    • <2>、和 C 语言不同的是,标准的枚举定义方式成功定义枚举后,成员值并不会隐式被指定为 0、1、2、…… 这种形式。如果需要在定义时指定初始值,我们可以使用另一种形式。在声明的时候赋予的值叫做原始值(raw value),这些值的类型会自动进行判断,原始值必须是字面上的整数、浮点数、字符或者字符串。如果原始值类型变量被指定为整型,则不必为每个成员显示地指定值,它们会被隐式的被标为值 0、1、2 、…… 等。

      • 带原始值的声明形式:

            enum enumerationName: rawValueType {
        
                case enumerationCase1 = rawValue1
                case enumerationCase2 = rawValue2
                .....
            }
        
            enum PointRect:Int {
        
                case top
                case bottom = 2
                case left
                case right
            }
        
            // 枚举原始值类型变量被指定为整型,只显示的指定一个值,其它会被隐式的指定值(top == 0, left == 3, right == 4)
            print(PointRect.top.rawValue)           
        
    • <3>、另外可以通过为每个枚举成员设定一个或多个关联值,从而使用枚举来存储和维护一些特别的数据。

          enum PointRect {
      
              case top(Int, Int)
              case bottom(Int, Int)
              case left(Double, Double)
              case right(Double, Double)
          }
      
    • <4>、枚举与类和结构体的关系:

      • 枚举与其他两者最大的相同之处就在于都可以定义方法。而其他的更多特性,对于枚举基本没有,没有属性,每一个枚举值都是常量。枚举中所定义的方法也基于对本身值的操作,无法定义一些无关的属性和操作。
  • 10)结构体

    • 结构体是值类型的,其实例将会在被赋予变量或者常量和被函数调用时被赋值。

    • 标准定义格式:

          struct structName {
      
              var 成员1: 数据类型 1
              var 成员2: 数据类型 2
              .....
          }
      
          struct BookInfo {
      
              var ID:Int = 0
              var Name:String = "Default"
              var Author:String = "Default"
              var RootType:String = "Default"
          }
      
  • 11)类型别名

    • 在 Swift 语言中使用 typealias 定义类型别名。

          typealias ShortInteger = Int8
      
  • 12)类型转换

    • 隐式类型转换:如 C 语言的类型转换
    • 显式类型转换:Swift 语言是一种强类型语言,其整型的强制类型转换就是调用了参数类型对应的整形扩展构造方法,然后通过对应扩展构造方法的处理返回一个当前整形字长的整形值。

          // 将字符型转换成整型
          Int(12.4)
      

2、运算符

  • Swift 语言支持大部分标准 C 语言的运算符,并且改进了许多特性来使我们的代码更加规范,其中主要包含算数运算符、区间运算符、逻辑运算符、关系运算符、赋值运算符、自增自减运算符、溢出运算符等。

    图片 7

  • 1)组合赋值运算符:是将其他运算符和赋值运算符组合在一起执行的运算。算数自反赋值运算符属于组合赋值运算符。

    • 要实现一个组合赋值符号需要把运算符的左参数设置成 inout 类型,从而使运算符函数体内部可以直接修改他的值。

          func += (inout lhs: Int, rhs: Int) {
              lhs = lhs + rhs
          }
      
  • 2)自定义运算符:自定义运算符是新的编程语言才支持的特性,不同于组合赋值运算符,你可以使用 / = - + * % < > ! & | ^ ~ 来组合构成新的运算符。

    • 自定义一个运算符通常需要先声明再实现其功能,声明自定义的运算符需要使用 operator 关键字。

          operator      :表示要进行运算符重载
          infixpostfix  :表示这是一个二元运算符,操作符在两个操作数中间。 
          prefix        :表示这是一个一元运算符,操作符在操作数前边。
          postfix       :表示这是一个一元运算符,操作符在操作数后边。
          associativity :结合性,包含 left(左结合)、right(右结合)和 none(自动),默认值为 none。
          precedence    :优先级,默认为 100,可省略。
      
          // 声明自定义运算符 <>
          infix operator <> {associativity none precedence 100}
      
          // 实现自定义的运算符 <>
          func <> (lhs: Int, rhs: Int) -> Int {
      
              return (lhs + rhs) * (lhs - rhs)
          }
      
          // 输出值等于 20
          let n1 = 6;   let n2 = 4;   let value = n1 <> n2;   print(value)
      
  • 3)运算符重载:让已有的运算符对自定义的类和结构进行运算或者重新定义已有运算符的运算规则,这种机制被称为运算符重载。同一个运算符在处理不同数据类型时,实现的是不同的功能。

        // 声明运算符
        infix operator >< { associativity left }
    
        // 实现运算符
        func >< (inout leftValue:String, inout rightValue:String) -> String {
    
            var tmp = leftValue
            leftValue = rightValue
            rightValue = tmp
    
            return tmp
        }
    
    • 默认的赋值符 “=” 和三目条件运算符( ? : )是不可重载的。

    • Swift 语言和其他高级语言不同,其原生的关系运算符不能判断自定义的类型是否相等,所以我们需要重载自定义的类和结构的比较符 “==”或 “!=”。

          func == (left: CenterPointer, right: CenterPointer) -> Bool {
              return (left.x == right.x) && (left.y == right.y)
          }
      
          func != (left: CenterPointer, right: CenterPointer) -> Bool {
              return  !(left == right)
          }
      
  • 4)运算符优先级和结合性:运算符的优先级使得一些运算符优先于其他运算符,从而使得高优先级的运算符会先被计算。结合性用于定义相同优先级的运算符在一起时和表达式结合或关联的规则。

    • 结合性(associativity)包含 left(左结合)、right(右结合)和 none(自动),结合性的默认值为 none。优先级( precedence )默认为 100。

          // 指定运算符的优先级和结合性 左结合 优先级 140
          infix operator +- {associativity left precedence 140}
      

      图片 8

3、表达式

  • Swift 语言使用表达式来表示程序中的最小单位,通常一个表达式可以由数字、字符、运算符、变量、常量、函数调用等可以求得值的有意义的排列组成的组合。根据组合方式的不同,表达式可以分为基本表达式、多元表达式、前缀表达式、后缀表达式。

  • 1)基本表达式:

    • self 表达式:用于对当前类型或者类型实例自身进行引用,从而访问其内部成员。

          self.menberFunc
      
    • super 表达式:超类表达式,也可以理解为父类,用于访问当前类或者实例的父类成员或者方法。

          super.menber
      
    • 隐式成员表达式:用于在可以推断出类型的上下文中引用这个类型的成员。

          var poNum = SomType.max
          poNum = .min
      
    • 圆括号表达式:用于划分运算符优先级和创建元组,通常由一对圆括号和若干个自表达式和逗号共同构成。

          (表达式1, lab2:表达式2, lab3:表达式3, ...)
      
    • 通配符表达式:主要使用符号 “_” 来忽略表达式中的某个参数,这和正则表达式的通配符的概念是不同的。

          (a, _) = (1, 2)
      
  • 2)前缀表达式:

    • 函数调用表达式:通常由函数名加上参数列表组成。

          FuncName(value1, lab2:value2)
      
    • 初始化函数表达式:即某个类型用于初始化其实例的函数表达式。

          SomeClass.init
      
    • 显式成员表达式:是显式的访问类型、元组或者其他模块成员变量的一种方式。

          var cat:Tanimal()
          var iFoots = cat.hasfoot
      
    • 后缀 self 表达式:通常有两种形式的后缀表达式。

      • 1、表达式.self 这种形式的表达式返回表达式的自身的值。
      • 2、类型实例.self 这种形式的表达式返回当前实例所属的类型,通常用于需要动态获取实例类型的场景中。
    • 动态类型表达式:专门用于动态获取类型的表达式。
      • 标准形式是:表达式.dynamicType ,其中表达式不能为类型名称。
      • 可以通过使用 .dynamicType 获得当前实例对象所属的类型,并访问其类方法。
    • 附属脚本表达式:可以通过附属脚本表达式访问 getter/setter 的方法,他的基本形式是:表达式1 [index 表达式2]

    • 强制取值表达式:使用 “!” 来强制获取某个不为 nil 的可选表达式的值。

    • 可选链表达式:使用 “?” 来声明一个可选类型变量或者对象,当对象不为 nil 时就可以访问对象的方法或者成员。

      • 在一个后缀表达式的子表达式中,有一个可选表达式,那么只有最外层的表达式返回的才是一个可选值。

4、控制流(控制结构)

  • 指令的执行顺序在程序结构中,我们称之为控制流。控制流,也称为控制结构,通常包括:顺序结构、条件结构、循环结构、转向结构。

  • 1)条件结构(分支结构):

    • if 语句:

          if <条件表达式> {
              语句体 1
          } else {
              语句体 2
          }
      
    • switch 语句:

          switch value {
      
              case value1:
                  语句体1
              case value2:
                  语句体2
              default:
                  默认语句体
          }
      
      • 和 OC 中的 switch 语句不同,在 switch 语言中你不需要在 case 块中显式的使用 break 语句跳出 switch,当匹配到的 case 块中的代码块中的代码执行完毕后,程序会终止 switch 语句,而不会继续执行下一个 case 块。

      • 可以使用 fallthrough 在 switch 语句中使代码继续执行到下一个 case 中的代码,而不会检查它下一个将会落入执行的 case 中的条件是否匹配,从而达到和 C 语言标准中 switch 语句特性一样的效果。

      • 在 switch 语言中每个 case 块后的匹配条件可以有多个,每个匹配条件之间用逗号隔开。switch 语句不会同时匹配大些字母和小写字母。 如:case 1, 2, 3, 4, 5,

      • 在 switch 语言中每一个 case 块都必须包含至少一条语句。

      • 可以使用元组在同一个 switch 语句中匹配多个值,元组中的元素可以是值,也可以是范围。

      • switch 语句允许多个 case 匹配同一个值,不过如果存在多个可匹配分支的时候,只会执行第一个被匹配到的 case 块。

      • 像 if 语句一样,switch 语句也支持值绑定,case 块允许将匹配的值绑定到一个临时的常量或变量,这个常量或变量在该 case 块里就可以被引用了。

  • 2)循环结构:

    • for-in 循环语句:

          for 循环变量 in <范围,集合,队列...> {
      
              循环体.....
          }
      
      • 当不需要使用范围内的每一项的值时,可以使用下划线 “_” 变量名来忽略对值的访问。

      • 遍历字典时,字典的每项元素会以(key, value)元组的形式返回。

      • 循环变量不需要定义。

            for num in 0...10 {
                print(num)
            }
        
    • for 循环语句:

          for initialization; condation; increment {
      
              statements
          }
      
      • initialization:初始化表达式,condation:循环条件,increment:改变循环条件的表达式。

      • 在 Swift 2.2 中 C 语言样式的 for 循环语句被废弃,C-style for statement is deprecated and will be removed in a future version of Swift。

    • while 循环语句:

          while <条件表达式> {
      
              statements
          }
      
          do {
              statements
      
          } while <条件表达式>
      
      • 在 Swift 2.2 中 do-while 循环语句被废弃,使用 repeat-while 循环语句代替,'do-while' statement is not allowed; use 'repeat-while' instead。

            repeat {
                statements
        
            } while <条件表达式>
        
  • 3)控制转向语句:

    • continue :会通知一个循环体立即停止本次循环,直接回到循环条件判断,重新开始下次循环。

    • break :会立即中断该循环体,然后跳转到表示循环体结束的大括号后的第一行代码,即跳出本层循环体。可以在 switch 和循环结构中使用。

    • fallthrough :在 switch 语句中使代码继续执行到下一个 case 中的代码,而不会检查它下一个将会落入执行的 case 中的条件是否匹配,从而达到和 C 语言标准中 switch 语句特性一样的效果。

    • 标签语句 :Swift 语言提供了更强大的跳出机制,你可以显式的指出需要跳出的是哪一层循环或 switch 结构。为了实现这个目的,我们可以使用标签来为循环体或者 switch 代码打上标记,当需要使用 break 或者 continue 时,带上这个标签就可以控制跳出或中断的是哪一个循环或 switch 结构。

          label loopName: for number in sArray {
      
              statements
          }
      
          // 跳出 loopName 循环
          break loopName              
      

5、函数

  • 函数是执行特定任务的代码块,每个函数都有一个类型,可以像使用 Swift 语言中其他类型一样使用函数类型,将函数作为参数传递给其他函数,或者将函数类型当作返回类型。在 Swift 语言中没有主函数。在 Swift 语言中函数分为两类,一种是库和框架中的函数,一种是自定义的函数。

  • 函数定义需要关键字 func,其一般格式为:

        // 使用时第一个参数名(参数名1)会被省略
        func 函数名 (参数名1:参数类型, 参数名2:参数类型 ...) -> 函数返回类型 {
    
            函数体 .....
    
            return 返回值
        }
    
  • 在 Swift 语言中,函数的形参和返回值是非常具有灵活性的,在需要的时候,可以定义一个或者多个甚至选择性的省略。实际上,在定义的时候忽略返回值等于隐式声明了函数的返回值类型为 void,而实际上函数还是返回了一个空的元组作为返回值。

  • 1)外部形参:

    • Swift 语言也能支持 OC 的函数参数标签模式,这种模式被称为外部形参。不过如果你为参数制定了外部形参名,那么在调用的时候就必须显式的使用。

      • 定义格式:

            // 使用时本地形参名(本地形参名1、本地形参名2 ...)会被省略
            func 函数名 (外部形参名1 本地形参名1:参数类型, 外部形参名2 本地形参名2:参数类型 ...) -> 函数返回类型 {
        
                函数体 .....
        
                return 返回值
            }
        
    • 如果别人第一次阅读你的代码,使用外部形参名称可以使你要表达的意思更加明确,上下文更加清清晰。在写外部形参名时,完全可以只写一次名字,只需要用一个 hash 符号“#” 作为参数名称的前缀,从而告诉 Swift,我们使用了名称相同的本地形参名称和外部形参名称。

      • 定义格式:

            // Xcode 7.3.1  Swift 2.2 中不支持该种定义方式
            func 函数名 (#参数名1:参数类型, #参数名2:参数类型 ...) -> 函数返回类型 {
        
                函数体 .....
        
                return 返回值
            }
        
  • 2)默认值形参:

    • 在 Swift 语言中可以为任何形参定义默认值以作为函数定义的一部分,如果已经定义了默认值,那么调用函数时就可以省略该形参。为了避免遗漏参数或者参数传递的二义性,需在函数形参列表的末尾放置带默认值的形参,不要在非默认值的形参前放置。

      • 定义格式:

            func 函数名 (参数名1:参数类型, 参数名2:参数类型, sAge:String = "20") -> 函数返回类型 {
        
                函数体 .....
        
                return 返回值
            }
        
    • 在有定义默认值的情况下,当没有指定外部形参名称时,Swift 语言将为你定义的任何默认值形参提供一个自动外部形参名,这个自动外部形参名和本地形参名相同。

  • 3)可变数量形参:

    • 可变数量形参是指可接受零个或多个指定类型值的形参,可以用它来传递任意数量的输入参数。声明可变形参需要用到“...”,当参数传递进函数体后,参数在函数体内可以通过集合的形式访问。一个函数最多可以有一个可变参数,而且它必须出现在参数列表的最后。

      • 定义格式:

            func 函数名 (参数名1:参数类型, 参数名2:参数类型, numbers:Double...) -> 函数返回类型 {
        
                函数体 .....
        
                return 返回值
            }
        
  • 4)可变值形参:

    • Swift 语言函数的形参默认是常量,我们不能直接在函数体内部改变形参的值,也就是说函数的形参默认是值类型的。但是如果需要在函数体内部修改函数参数值,可以使用可变形参,要定义可变形参可以在参数名前使用 var 关键字。可变形参可以让你能够修改形参的值,它可以给函数体一个可修改的形参值副本,但这并不意味着可变形参就是引用类型的。

      • 定义格式:

            func 函数名 (var 参数名1:参数类型, var 参数名2:参数类型, ...) -> 函数返回类型 {
        
                函数体 .....
        
                return 返回值
            }
        
  • 5)引用类型形参:

    • 在实际的编码中,我们往往需要在函数体内部修改形参值,并同时作用到实参本身,从而省去增加返回值数量的步骤。这时可以把形参定义为 in-out 类型,要定义 in-out 类型的参数,需要在参数名前使用 inout 关键字。当把变量传递给 in-out 形参时,必须在变量前添加 “&” 符号,以表明他被函数内部修改的是它本身的引用。

      • 定义格式:

            func 函数名 (inout 参数名1:参数类型, var 参数名2:参数类型, ...) {
        
                函数体 .....
            }
        
            func *= (inout lhs: UInt, rhs: UInt)
        
    • 使用 in-out 参数的同时有几条规则需要注意:

      • 1、被标记为 inout 后不能将常量和字面量传递进函数。
      • 2、不能同时将参数标记为 var、let、inout。
      • 3、可变数量参数的参数不能标记为 inout。
      • 4、函数不能有默认值。
  • 6)函数类型:

    • 每一个函数都有特定的函数类型,函数类型通常由函数的形参类型和返回值类型组成。如果一个函数没有形参或返回值,那么这个函数的类型是 () -> () 。例如函数

          func addString(s1:string, s2:string, s2:string) -> String { } 的类型就是 (String, String, String) -> String 。
      
    • 可以在 Swift 语言中像使用其他任何类型一样的使用函数类型。例如可以定义一个函数常量或函数变量,并像一般数据类型指定初始值一样为他指定一个对应的函数。与其他类型一样,当你给函数赋一个变量或者常量时,你可以让 Swift 语言去推断函数的类型。

          // 指定函数类型
          var addSome:(String, String, String) -> String = addString
      
          // 推断函数类型
          let anotherAddSome = addString
      
    • 你也可以使用一个函数类型作为另一个函数的形参类型和返回值类型,使用方法和一般的数据类型相同。

  • 7)嵌套函数:

    • 在一个函数体中定义另外一个函数体就称为嵌套函数。嵌套的函数默认对外是隐藏的,但仍可以通过包裹他们的函数调用和使用它们。

      • 定义格式:

            func 函数名1 (参数名1:参数类型, 参数名2:参数类型 ...) -> 函数返回类型1 {
        
                func 函数名2 (参数名3:参数类型, 参数名4:参数类型 ...) -> 函数返回类型2 {
        
                函数体2 .....
        
                return 返回值2
                }
        
                函数体1 .....
        
                return 返回值1
            }
        

6、断言

  • 对每次运行都会出现的错误通常不会过于苦恼,可以使用断点调试或者 “try-catch”之类的方式判断并修复它。但是一些偶发(甚至是无数次运行才会出现一次)的错误单靠断点之类的方式是很难排除掉的,为此,引入一个不是很常用的调试工具函数:assert(condition: Bool, message: String),assert 是单纯地触发断言即停止程序,不会让你有机会将可能出错的设计走过它这一关。

  • 在实际编码中,为了保证程序正常运行,只有在某些必要条件被满足的情况下才执行特定代码段,这种编程思想叫做防错性编程。

  • 在 Swift 语言中可以调用全局的 assert 函数来增加一个断言,这里的全局意思是你可以将断言放在你程序的任何一个地方。程序在执行到 assert 时会判断其中的逻辑条件表达式参数是否为 true。如果条件判断为 true,代码运行会继续进行。如果条件判断为 false,程序将终止。通常,在为程序加入并触发断言后,Xcode 会精确定位到异常代码段,并反馈异常信息等修改 bug 必须的调试信息。

  • 标准的断言格式:

        assert(condition: Bool, message: String) 
    
    • condition 判断条件,message 自定义调试信息,断言中的调试信息参数是可选的。
  • 定义:

        func assert(condition: @autoclosure () -> Bool, 
                    _ message: @autoclosure () -> String = default, 
                         file: StaticString = default, 
                         line: UWord = default)
    
        @inline(__always) func assertionFailure(_ message: @autoclosure () -> String = default, 
                                                     file: StaticString = default, 
                                                     line: UWord = default)
    
  • 使用:

        var usedate = -1
        usedate = 2
    
        // 当 usedate 大于 0 时,程序中断,进入断言函数打印调试信息
        assert(usedate <= 0, "超出试用期,不能启动程序!")
    
  • 系统在断言的源代码中加入了类似 “#if NDEBUG”这样的编译字,使其只能用于 debug 期,当你在发布 release 版本或者更新版的时候,编译器会使用一个编译字段将断言无效化,所以当你的产品在提交给用户之后还需要继续收集错误信息时,需使用其他方式。

  • 断言函数中用到的“@autoclosure”属性,使用这种属性的参数意味着我们可以在这个参数的位置传入一个表达式,这个表达式会被自动封包成一个闭包,这也正是其字面的意思:“自动闭包”。在 assert 函数中它起到的作用也是非常明显的,如果在这里我们使用的是普通的布尔型参数,那么在执行到 assert 函数时,就会先运算条件表达式的值,而使用“@autoclosure”属性后,程序会先在 assert 函数内判断 debug 期的编译字是否存在,如果存在才会运算条件表达式的值,当然,这时条件表达式已经被自动封装成了一个闭包。

  • 断言使用的几种场景:

    • 验证参数的合法性。
    • 将要使用一个对象,但是不确定其是否已经正确创建。
    • 数组或者其他集合类、字典等复杂数据类型下标没有处于安全范围导致可能会越界。
    • assert 函数的条件表达式参数最好一次只判断一个条件,因为如果判断多个条件,当断言被触发时,往往会无法直观的判断到底是哪一个条件不被满足。

7、闭包

  • 闭包 在 Swift 中非常有用。通俗的解释就是一个 Int 类型里存储着一个整数,一个 String 类型包含着一串字符,同样,闭包是一个包含着函数的类型。有了闭包,你就可以处理很多在一些古老的语言中不能处理的事情。这是因为闭包使用的多样性,比如你可以将闭包赋值给一个变量,你也可以将闭包作为一个函数的参数,你甚至可以将闭包作为一个函数的返回值。它的强大之处可见一斑。在 Swift 的很多文档教材中都说函数是“一等公民”,起初我还不是很理解“一等公民”是什么意思,但当我理解了闭包以及它的强大功能后,我恍然大悟、茅塞顿开、醍醐灌顶。原来闭包的这些特性就是“一等公民”的特性啊!

  • 闭包是功能性自包含模块,可以在代码中被传递和使用。一段程序代码通常由常量、变量和表达式组成,然后使用一对花括号“{}” 来表示闭合并包裹着这些代码,由这对花括号包裹着的代码块就是一个闭包。Swift 中的闭包与 C 和 Objective-C 中的 Block 以及其他一些编程语言中的 lambdas 比较相似。Block 和闭包的区别只是语法的不同而已,而且闭包的可读性比较强。

  • 闭包是引用类型,无论你将函数/闭包赋值给一个常量还是变量,实际上都是在将常量/变量设置为对应函数/闭包的引用,这也意味着如果你将闭包赋值给了两个不同的常量/变量,两个值都会指向同一个闭包。

  • 在 Swift 语言中有三种闭包形式:

    • 全局函数:是一个有名字但不会捕获任何值的闭包。
    • 嵌套函数:是一个有名字并可以捕获到其封闭函数域内的值的闭包。
    • 匿名闭包:闭包表达式是一个利用轻量级语法所写的,可以捕获其上下文中变量或常量值。
  • 1)函数形式:

        func myConpare(s1:String, s2:String) -> Bool {
    
            return s1 > s2
        }
    
        let namesArray:Array = ["Jill", "Tim", "Chris"]
    
        let names = namesArray.sort(myConpare)
    
  • 2)一般形式:

        { (parameters参数) -> returnType返回类型 in
    
            statements
        }
    
    • 可以使用常量、变量、inout、可变参数、元组类型作为闭包的参数,但不能在闭包参数中设置默认值,定义返回值和函数返回值的类型相同。

    • 闭包表达式中的 in 关键字表示闭包的参数和返回值类型定义已经完成,这些参数和返回值都将在下面的闭包函数体中得到处理。

          let namesArray:Array = ["Jill", "Tim", "Chris"]
      
          let names = namesArray.sort { (s1:String, s2:String) -> Bool in
      
              return s1 > s2
          }
      
  • 3)参数类型隐藏形式:

    • Swift 中有类型推断的特性,所以我们可以去掉参数类型。

          let namesArray:Array = ["Jill", "Tim", "Chris"]
      
          let names = namesArray.sort { (s1, s2) -> Bool in
      
              return s1 > s2
          }
      
  • 4)返回值类型隐藏形式:

    • Swift 中有类型推断的特性,所以我们可以去掉返回值类型。

          let namesArray:Array = ["Jill", "Tim", "Chris"]
      
          let names = namesArray.sort { (s1, s2) in
      
              return s1 > s2
          }
      
  • 5)return 隐藏形式:

    • 单行表达式的闭包可以通过隐藏关键字 return 来隐式地将单行表达式的结果作为返回值。

          let namesArray:Array = ["Jill", "Tim", "Chris"]
      
          let names = namesArray.sort { (s1, s2) in
      
              s1 > s2
          }
      
  • 6)参数名省略形式:

    • 闭包的使用非常的灵活,我们可以省略闭包参数列表中的参数的参数类型定义,被省略的参数类型会通过闭包函数的类型进行推断。同时,我们也可以在闭包函数体中通过使用闭包的参数名简写功能,直接使用 $0、$1、$2 等名字就可以引用的闭包参数值。如果同时省略了参数名和参数类型,那么 in 关键字也必须被省略,此时闭包表达式完全由闭包函数体构成。

          let namesArray:Array = ["Jill", "Tim", "Chris"]
      
          let names = namesArray.sort {
      
              $0 > $1
          }
      
  • 7)trailing 闭包形式:

    • 闭包可以做其他函数的参数,而且通常都是函数的最后一个参数。但是如果作为参数的这个闭包表达式非常长,那么很有可能会影响函数调用表达式的可读性,这个时候我们就应该使用 trailing 闭包。trailing 闭包和普通闭包的不同之处在于它是一个书写在函数参数括号之外(之后)的闭包表达式,函数会自动将其作为最后一个参数调用。

    • 当函数有且仅有一个参数,并该参数是闭包时,不但可以将闭包写在 () 外,还可以省略 ()。Swift 2.2 中可以不管参数的个数完全省略 ()。

          let namesArray:Array = ["Jill", "Tim", "Chris"]
      
          let names = namesArray.sort() {
      
              $0 > $1
          }
      
  • 8)闭包捕获:

    • 闭包可以在其定义的上下文中捕获常量或变量,即使定义这些常量或变量的原作用域已经不存在,仍然可以在闭包函数体内引用和修改这些常量或变量,这种机制被称为闭包捕获。比如:嵌套函数就可以捕获其父函数的参数以及定义的常量和变量,全局函数可以捕获其上下文中的常量或变量。

          func increment(amount: Int) -> (() -> Int) {
      
              var total = 0
      
              func incrementAmount() -> Int {
      
              // total 是外部函数体内的变量,这里是可以捕获到的
                  total += amount
      
                  return total
              }
      
              // 返回的是一个嵌套函数(闭包)
              return incrementAmount
          }
      
          // 闭包是引用类型,所以 incrementByTen 声明为常量也可以修改 total
          let incrementByTen = increment(10)
          incrementByTen()    // return 10,incrementByTen 是一个闭包
      
          // 这里是没有改变对 increment 的引用,所以会保存之前的值
          incrementByTen()    // return 20
          incrementByTen()    // return 30
      
          let incrementByOne = increment(1)
          incrementByOne()    // return 1,incrementByOne 是一个闭包
          incrementByOne()    // return 2
          incrementByTen()    // return 40
          incrementByOne()    // return 3
      

8、下标脚本

  • 下标脚本 允许你通过在实例后面的方括号中传入一个或者多个的索引值来对实例进行访问和赋值。语法类似于实例方法和计算型属性的混合。与定义实例方法类似,定义下标脚本使用 subscript 关键字,显式声明入参(一个或多个)和返回类型,每个输入参数的类型也没有限制,返回值可以是任何类型,并无限制。输入参数也可以使用可变参数,但使用输入/输出(in-out)参数或和给参数设置默认值都是不允许的。与实例方法不同的是下标脚本可以设定为读写或只读。这种方式又有点像计算型属性的 getter 和 setter 方法。

  • 下标脚本就是对一个东西通过索引,快速取值的一种语法,例如数组的 a[0]。这就是一个下标脚本。通过索引 0 来快速取值。在 Swift 中,我们可以对类(Class)、结构体(structure)和枚举(enumeration)中自己定义下标脚本的语法。

  • 重点:

    • 下标脚本使用 subscript 关键字来定义。
    • 下标脚本使用 get、set 来定义读、写属性,并不需要 2 个属性都有,可以只读,并且读必须有。
    • 定义 set 属性时,传入的参数默认名称为 newValue。并且 newValue 的类型和 subscript 函数返回值相同。
  • 1) 下标脚本的使用 1

    • 下标脚本的定义

          struct myString {
      
              var str:String = ""
      
              subscript(start:Int, length:Int) -> String {
      
                  get {
                      return (str as NSString).substringWithRange(NSRange(location: start, length: length))
                  }
      
                  set {
      
                      str = newValue
                  }
              }
          }
      
    • 下标脚本的使用

          let str1 = myString(str: "hello world")
      
          let str2 = str1[2, 5]
      
          // 输出 hello world
          print(str1[0, 11])
      
          // 输出 llo w
          print(str2)
      
          var str3 = myString()
      
          // [0, 0] 参数无意义
          str3[0, 0] = "world"
      
          // 输出 world
          print(str3[0, 5])
      
  • 2) 下标脚本的使用 2

    • 下标脚本的定义

          class Student1 {
      
              var scores:[Int] = Array(count:5, repeatedValue:0)
      
              subscript(index:Int) -> Int {
      
                  get {
      
                      return scores[index];
                  }
      
                  set {
      
                      scores[index] = newValue
                  }
              }
      
              subscript(indexs:Int...) -> [Int] {
      
                  get {
                      var values:[Int] = Array()
      
                      for index in indexs {
                          values.append(scores[index])
                      }
                      return values
                  }
      
                  set {
                      var i = 0
      
                      for index in indexs {
      
                          scores[index] = newValue[i]
      
                          i += 1
                      }
                  }
              }
          }
      
    • 下标脚本的使用

          let stu1 = Student1()
      
          stu1[0] = 1
          stu1[1] = 2
      
          // 输出 a[0]:1, a[1]:2
          print("a[0]:(stu1[0]), a[1]:(stu1[1])")
      
          let stu2 = Student1()
      
          stu2[1, 2, 3] = [5, 6, 7]
      
          // 输出 [0, 5, 6, 7, 0]
          print(stu2[0, 1, 2, 3, 4])
      

本文由9159.com发布于编程,转载请注明出处:这种Swift语言也具有很多这种脚本语法的特点,

关键词: