TutorialsPointSwift教程

Swift是一种支持多编程范式和编译式的开源编程语言,苹果于2014年WWDC(苹果开发者大会)发布,用于开发iOS,OSX和watchOS应用程序。

Swift结合了C和Objective-C的优点并且不受C兼容性的限制。

Swift在MacOS和iOS平台可以和Object-C使用相同的运行环境。这意味着Swift程序可以运行于目前已存在的平台之上,包含iOS6和OSX10.8都可以运行Swift的程序。

更重要的,Swift和Obj-C的代码可并存于单一程序内,这种延伸就如同C和C++的关系一样。

2015年6月8日,苹果于WWDC2015上宣布,Swift将开放源代码,包括编译器和标准库。

本教程适合想从事移动端(iphone)开发或OSX应用的编程人员,如果之前有编程基础更好。

本教程所有实例基于Xcode7.1(Swift2.x的语法格式)开发测试。

第一个Swift程序当然从输出"Hello,World!"开始,代码如下所示:

/*我的第一个Swift程序*/varmyString="Hello,World!"print(myString)

Swift是一门开源的编程语言,该语言用于开发OSX和iOS应用程序。

在正式开发应用程序前,我们需要搭建Swift开发环境,以便更好友好的使用各种开发工具和语言进行快速应用开发。由于Swift开发环境需要在OSX系统中运行,因此其环境的搭建将不同于Windows环境,下面就一起来学习一下swift开发环境的搭建方法。

成功搭建swift开发环境的前题:

下载完成后,双击下载的dmg文件安装,安装完成后我们将Xcode图标踢移动到应用文件夹。

你也可以在AppStore中搜索xcode安装,如下图所示:

Xcode安装完成后,我们就可以开始编写Swift代码了。

接下来我们在应用文件夹打开Xcode,打开后在屏幕顶部选择File=>New=>Playground。

接着为playground设置一个名字并选择iOS平台。

以下是SwiftPlayground窗口默认的代码:

importUIKitvarstr="Hello,playground"

如果你想创建OSx程序,需要导入Cocoa包importCocoa代码如下所示:

importCocoavarstr="Hello,playground"

以上程序载入后,会在Playground窗口右侧显示程序执行结果:

Hello,playground

至此,你已经完成了第一个Swift程序的学习,恭喜你入门了。

1、打开xcode工具,选择File=>New=>Project

2、我们选择一个"SingleViewApplication",并点击"next",创建一个简单示例app应用。

3、接着我们输入项目名称(ProductName),公司名称(OrganizationName),公司标识前缀名(Organizationidentifier)还要选择开发语言(Language),选择设备(Devices)。

其中Language有两个选项:Objective-c和swift,因为我们是学习swift当然选择swift项了。点击"Next"下一步。

4、选择存放的目录,如果要使用Git源代码管理,将勾上SourceControl的creategitrepositoryonMyMac.点击create创建项目。

5、项目创建后,默认生成了一个示例文件,可以看到swift将oc中的h和m文件合并成了一个文件(即swift后缀名文件).Main.storyboard相当于xib文件,有比xib更多的功能。

6、打开main.storyboard,默认看到一个简单的空白的应用界面,大小为平板界面大小。如果开发都只需要开发兼容iphone手机的app,那么可以把UseAutoLayout的勾去掉(默认为勾上)。

7、弹出了一个对话框,让我们选择界面尺寸,iPhone或都iPad。我们选择iPhone的尺寸。

8、可以看到,界面大小变为了手机iphone的宽度和高度。

iPhone或iTouch的宽为320像素,高为480像素,状态栏高为20像素,toobar高为44像素,tabbar高为49像素,导航栏高为44像素。

9.我们为界面添加点内容,在右下方找到Text控件,将它拖入storyboard上,并双击写入文本"HelloWorld!"。

运行一下模拟器(command+R快捷键或在菜单栏中选择Product=>Run)。

至此,我们的第一个Swift项目就完成了。

在上一章节中我们已经讲到如何创建Swift语言的"Hello,World!"程序。现在我们来复习下。

如果创建的是OSXplayground需要引入Cocoa:

importCocoa/*我的第一个Swift程序*/varmyString="Hello,World!"print(myString)

如果我们想创建iOSplayground则需要引入UIKit:

importUIKitvarmyString="Hello,World!"print(myString)

执行以上程序,输出结果为:

Hello,World!

以上代码即为Swift程序的基本结构,接下来我们来详细说明结构的组成部分。

我们可以使用import语句来引入任何的Objective-C框架(或C库)到Swift程序中。例如importcocoa语句导入了使用了Cocoa库和API,我们可以在Swift程序中使用他们。

Cocoa本身由Objective-C语言写成,Objective-C又是C语言的严格超集,所以在Swift应用中我们可以很简单的混入C语言代码,甚至是C++代码。

Swift程序由多种标记组成,标记可以是单词,标识符,常量,字符串或符号。例如以下Swift程序由三种标记组成:

print("test!")标记是:单词、符号print("test!")

Swift的注释与C语言极其相似,单行注释以两个反斜线开头:

//这是一行注释

多行注释以/开始,以/结束:

/*这也是一条注释,但跨越多行*/

与C语言的多行注释有所不同的是,Swift的多行注释可以嵌套在其他多行注释内部。写法是在一个多行注释块内插入另一个多行注释。第二个注释块封闭时,后面仍然接着第一个注释块:

/*这是第一个多行注释的开头/*这是嵌套的第二个多行注释*/这是第一个多行注释的结尾*/

多行注释的嵌套是你可以更快捷方便的注释代码块,即使代码块中已经有了注释。

与其它语言不同的是,Swift不要求在每行语句的结尾使用分号(;),但当你在同一行书写多条语句时,必须用分号隔开:

importCocoa/*我的第一个Swift程序*/varmyString="Hello,World!";print(myString)

标识符就是给变量、常量、方法、函数、枚举、结构体、类、协议等指定的名字。构成标识符的字母均有一定的规范,Swift语言中标识符的命名规则如下:

例如:userName、User_Name、_sys_val、身高等为合法的标识符,而2mail、room#和class为非法的标识符。

注意:Swift中的字母采用的是Unicode编码[1]。Unicode叫做统一编码制,它包含了亚洲文字编码,如中文、日文、韩文等字符,甚至是我们在聊天工具中使用的表情符号

如果一定要使用关键字作为标识符,可以在关键字前后添加重音符号(`),例如:

关键字是类似于标识符的保留字符序列,除非用重音符号(`)将其括起来,否则不能用作标识符。关键字是对编译器具有特殊意义的预定义保留标识符。常见的关键字有以下4种。

class

deinit

enum

extension

func

import

init

internal

let

operator

private

protocol

public

static

struct

subscript

typealias

var

break

case

continue

default

do

else

fallthrough

for

if

in

return

switch

where

while

as

dynamicType

false

is

nil

self

Self

super

true

COLUMN

FILE

FUNCTION

LINE

associativity

convenience

dynamic

didSet

final

get

infix

inout

lazy

left

mutating

none

nonmutating

optional

override

postfix

precedence

prefix

Protocol

required

right

set

Type

unowned

weak

willSet

Swift语言并不是像C/C++,Java那样完全忽视空格,Swift对空格的使用有一定的要求,但是又不像Python对缩进的要求那么严格。

在Swift中,运算符不能直接跟在变量或常量的后面。例如下面的代码会报错:

leta=1+2

错误信息是:

error:prefix/postfix'='isreserved

意思大概是等号直接跟在前面或后面这种用法是保留的。

下面的代码还是会报错(继续注意空格):

error:consecutivestatementsonalinemustbeseparatedby';'

这是因为Swift认为到1+这个语句就结束了,2就是下一个语句了。

只有这样写才不会报错:

leta=1+2;//编码规范推荐使用这种写法letb=3+4//这样也是OK的

所谓字面量,就是指像特定的数字,字符串或者是布尔值这样,能够直接了当地指出自己的类型并为变量进行赋值的值。比如在下面:

42//整型字面量3.14159//浮点型字面量"Hello,world!"//字符串型字面量true//布尔型字面量

在我们使用任何程序语言编程时,需要使用各种数据类似来存储不同的信息。

所有变量都具有数据类型,以决定能够存储哪种数据。

Swift提供了非常丰富的数据类型,以下列出了常用了集中数据类型:

一般来说,你不需要专门指定整数的长度。Swift提供了一个特殊的整数类型Int,长度与当前平台的原生字长相同:

除非你需要特定长度的整数,一般来说使用Int就够了。这可以提高代码一致性和可复用性。即使是在32位平台上,Int可以存储的整数范围也可以达到-2,147,483,648~2,147,483,647,大多数时候这已经足够大了。

Swift也提供了一个特殊的无符号类型UInt,长度与当前平台的原生字长相同:

注意:

浮点数是有小数部分的数字,比如3.14159,0.1和-273.15。

浮点类型比整数类型表示的范围更大,可以存储比Int类型更大或者更小的数字。Swift提供了两种有符号浮点数类型:

Double精确度很高,至少有15位数字,而Float最少只有6位数字。选择哪个类型取决于你的代码需要处理的值的范围。

Swift有一个基本的布尔(Boolean)类型,叫做Bool。布尔值指逻辑上的值,因为它们只能是真或者假。Swift有两个布尔常量,true和false。

字符串是字符的序列集合,例如:

"Hello,World!"

字符指的是单个字母,例如:

"C"

使用可选类型(optionals)来处理值可能缺失的情况。可选类型表示有值或没有值。

下表显示了不同变量类型内存的存储空间,及变量类型的最大最小值:

类型

大小(字节)

区间值

Int8

1字节

-127到127

UInt8

0到255

Int32

4字节

-2147483648到2147483647

UInt32

0到4294967295

Int64

8字节

-9223372036854775808到9223372036854775807

UInt64

0到18446744073709551615

Float

1.2E-38到3.4E+38(~6digits)

Double

2.3E-308到1.7E+308(~15digits)

类型别名对当前的类型定义了另一个名字,类型别名通过使用typealias关键字来定义。语法格式如下:

typealiasnewname=type

例如以下定义了Int的类型别名为Feet:

typealiasFeet=Int

现在,我们可以通过别名来定义变量:

importCocoatypealiasFeet=Intvardistance:Feet=100print(distance)

我们使用playground执行以上程序,输出结果为:

100

Swift是一个类型安全(typesafe)的语言。

由于Swift是类型安全的,所以它会在编译你的代码时进行类型检查(typechecks),并把不匹配的类型标记为错误。这可以让你在开发的时候尽早发现并修复错误。

importCocoavarvarA=42varA="Thisishello"print(varA)

以上程序,会在Xcode中报错:

error:cannotassignvalueoftype'String'totype'Int'varA="Thisishello"

意思为不能将'String'字符串赋值给'Int'变量。

如果你没有显式指定类型,Swift会使用类型推断(typeinference)来选择合适的类型。

例如,如果你给一个新常量赋值42并且没有标明类型,Swift可以推断出常量类型是Int,因为你给它赋的初始值看起来像一个整数:

letmeaningOfLife=42//meaningOfLife会被推测为Int类型

同理,如果你没有给浮点字面量标明类型,Swift会推断你想要的是Double:

letpi=3.14159//pi会被推测为Double类型

当推断浮点数的类型时,Swift总是会选择Double而不是Float。

如果表达式中同时出现了整数和浮点数,会被推断为Double类型:

letanotherPi=3+0.14159//anotherPi会被推测为Double类型

importCocoa//varA会被推测为Int类型varvarA=42print(varA)//varB会被推测为Double类型varvarB=3.14159print(varB)//varC也会被推测为Double类型varvarC=3+0.14159print(varC)

执行以上代码,输出结果为:

423.141593.14159

变量是一种使用方便的占位符,用于引用计算机内存地址。

Swift每个变量都指定了特定的类型,该类型决定了变量占用内存的大小,不同的数据类型也决定可存储值的范围。

varvariableName=

importCocoavarvarA=42print(varA)varvarB:FloatvarB=3.14159print(varB)

以上程序执行结果为:

423.14159

变量名可以由字母,数字和下划线组成。

变量名需要以字母或下划线开始。

Swift是一个区分大小写的语言,所以字母大写与小写是不一样的。

变量名也可以使用简单的Unicode字符,如下实例:

importCocoavar_var="Hello,Swift!"print(_var)var你好="你好世界"var菜鸟教程="www.runoob.com"print(你好)print(菜鸟教程)

Hello,Swift!你好世界www.runoob.com

变量和常量可以使用print(swift2将print替换了println)函数来输出。

在字符串中可以使用括号与反斜线来插入变量,如下实例:

Swift的可选(Optional)类型,用于处理值缺失的情况。可选表示"那儿有一个值,并且它等于x"或者"那儿没有值"。

varoptionalInteger:IntvaroptionalInteger:Optional

在这两种情况下,变量optionalInteger都是可选整数类型。注意,在类型和?之间没有空格。

可选项遵照LogicValue协议,因此可以出现在布尔环境中。在这种情况下,如果可选类型T包含类型为T的任何值(也就是说它的值是Optional.Some(T)),这个可选类型等于true,反之为false。

如果一个可选类型的实例包含一个值,你可以用后缀操作符!来访问这个值,如下所示:

optionalInteger=42optionalInteger!//42

使用操作符!去获取值为nil的可选变量会有运行时错误。

你可以用可选链接和可选绑定选择性执行可选表达式上的操作。如果值为nil,任何操作都不会执行,也不会有运行报错。

让我们来详细看下以下实例来了解Swift中可选类型的应用:

importCocoavarmyString:String=nilifmyString!=nil{print(myString)}else{print("字符串为nil")}

字符串为nil

可选类型类似于Objective-C中指针的nil值,但是nil只对类(class)有用,而可选类型对所有的类型都可用,并且更安全。

当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号(!)来获取值。这个感叹号表示"我知道这个可选有值,请使用它。"这被称为可选值的强制解析(forcedunwrapping)。

实例如下:

importCocoavarmyString:StringmyString="Hello,Swift!"ifmyString!=nil{print(myString)}else{print("myString值为nil")}

Optional("Hello,Swift!")

强制解析可选值,使用感叹号(!):

importCocoavarmyString:StringmyString="Hello,Swift!"ifmyString!=nil{//强制解析print(myString!)}else{print("myString值为nil")}

Hello,Swift!

使用!来获取一个不存在的可选值会导致运行时错误。使用!来强制解析值之前,一定要确定可选包含一个非nil的值。

importCocoavarmyString:String!myString="Hello,Swift!"ifmyString!=nil{print(myString)}else{print("myString值为nil")}

使用可选绑定(optionalbinding)来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量。可选绑定可以用在if和while语句中来对可选类型的值进行判断并把值赋给一个常量或者变量。

像下面这样在if语句中写一个可选绑定:

ifletconstantName=someOptional{statements}

让我们来看下一个简单的可选绑定实例:

importCocoavarmyString:StringmyString="Hello,Swift!"ifletyourString=myString{print("你的字符串值为-\(yourString)")}else{print("你的字符串没有值")}

你的字符串值为-Hello,Swift!

常量一旦设定,在程序运行时就无法改变其值。

常量可以是任何的数据类型如:整型常量,浮点型常量,字符常量或字符串常量。同样也有枚举类型的常量:

常量类似于变量,区别在于常量的值一旦设定就不能改变,而变量的值可以随意更改。

letconstantName=

以下是一个简单的Swift程序中使用常量的实例:

importCocoaletconstA=42print(constA)

42

varconstantName:=

以下是一个简单是实例演示了Swift中常量使用类型标注。需要注意的是常量定义时必须初始值:

importCocoaletconstA=42print(constA)letconstB:Float=3.14159print(constB)

常量的命名可以由字母,数字和下划线组成。

常量需要以字母或下划线开始。

常量名也可以使用简单的Unicode字符,如下实例:

importCocoalet_const="Hello,Swift!"print(_const)let你好="你好世界"print(你好)

Hello,Swift!你好世界

在字符串中可以使用括号与反斜线来插入常量,如下实例:

letaNumber=3//整型字面量letaString="Hello"//字符串字面量letaBool=true//布尔值字面量

整型字面量可以是一个十进制,二进制,八进制或十六进制常量。二进制前缀为0b,八进制前缀为0o,十六进制前缀为0x,十进制没有前缀:

以下为一些整型字面量的实例:

letdecimalInteger=17//17-十进制表示letbinaryInteger=0b10001//17-二进制表示letoctalInteger=0o21//17-八进制表示lethexadecimalInteger=0x11//17-十六进制表示

浮点型字面量有整数部分,小数点,小数部分及指数部分。

除非特别指定,浮点型字面量的默认推导类型为Swift标准库类型中的Double,表示64位浮点数。

浮点型字面量默认用十进制表示(无前缀),也可以用十六进制表示(加前缀0x)。

十进制浮点型字面量由十进制数字串后跟小数部分或指数部分(或两者皆有)组成。十进制小数部分由小数点.后跟十进制数字串组成。指数部分由大写或小写字母e为前缀后跟十进制数字串组成,这串数字表示e之前的数量乘以10的几次方。例如:1.25e2表示1.2510^2,也就是125.0;同样,1.25e-2表示1.2510^-2,也就是0.0125。

十六进制浮点型字面量由前缀0x后跟可选的十六进制小数部分以及十六进制指数部分组成。十六进制小数部分由小数点后跟十六进制数字串组成。指数部分由大写或小写字母p为前缀后跟十进制数字串组成,这串数字表示p之前的数量乘以2的几次方。例如:0xFp2表示152^2,也就是60;同样,0xFp-2表示152^-2,也就是3.75。

负的浮点型字面量由一元运算符减号-和浮点型字面量组成,例如-42.5。

浮点型字面量允许使用下划线_来增强数字的可读性,下划线会被系统忽略,因此不会影响字面量的值。同样地,也可以在数字前加0,并不会影响字面量的值。

以下为一些浮点型字面量的实例:

letdecimalDouble=12.1875//十进制浮点型字面量letexponentDouble=1.21875e1//十进制浮点型字面量lethexadecimalDouble=0xC.3p0//十六进制浮点型字面量

字符串型字面量由被包在双引号中的一串字符组成,形式如下:

"characters"

字符串型字面量中不能包含未转义的双引号(")、未转义的反斜线(\)、回车符或换行符。

转移字符

含义

\0

空字符

\\

反斜线\

\b

退格(BS),将当前位置移到前一列

\f

换页(FF),将当前位置移到下页开头

\n

换行符

\r

回车符

\t

水平制表符

\v

垂直制表符

\'

单引号

\"

双引号

\000

1到3位八进制数所代表的任意字符

\xhh...

1到2位十六进制所代表的任意字符

以下为字符串字面量的简单实例:

布尔型字面量的默认类型是Bool。

布尔值字面量有三个值,它们是Swift的保留关键字:

运算符是一个符号,用于告诉编译器执行一个数学或逻辑运算。

Swift提供了以下几种运算符:

本章节我们将为大家详细介绍算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符及其他运算符。

以下表格列出了Swift语言支持的算术运算符,其中变量A为10,变量B为20:

运算符

描述

实例

+

加号

A+B结果为30

减号

AB结果为-10

*

乘号

A*B结果为200

/

除号

B/A结果为2

%

求余

B%A结果为0

++

自增

A++结果为11

--

自减

A--结果为9

以下为算术运算的简单实例:

importCocoavarA=10varB=20print("A+B结果为:\(A+B)")print("A-B结果为:\(A-B)")print("A*B结果为:\(A*B)")print("B/A结果为:\(B/A)")A++print("A++后A的值为\(A)")B--print("B--后B的值为\(B)")

A+B结果为:30A-B结果为:-10A*B结果为:200B/A结果为:2A++后A的值为11B--后B的值为19

以下表格列出了Swift语言支持的比较运算符,其中变量A为10,变量B为20:

==

等于

(A==B)为false。

!=

不等于

(A!=B)为true。

>

大于

(A>B)为false。

<

小于

(A

>=

大于等于

(A>=B)为false。

<=

小于等于

(A<=B)为true。

以下为比较运算的简单实例:

importCocoavarA=10varB=20print("A==B结果为:\(A==B)")print("A!=B结果为:\(A!=B)")print("A>B结果为:\(A>B)")print("A=B结果为:\(A>=B)")print("A<=B结果为:\(A<=B)")

A==B结果为:falseA!=B结果为:trueA>B结果为:falseA=B结果为:falseA<=B结果为:true

以下表格列出了Swift语言支持的逻辑运算符,其中变量A为true,变量B为false:

&&

逻辑与。如果运算符两侧都为TRUE则为TRUE。

(A&&B)为false。

||

逻辑或。如果运算符两侧至少有一个为TRUE则为TRUE。

(A||B)为true。

!

逻辑非。布尔值取反,使得true变false,false变true。

!(A&&B)为true。

以下为逻辑运算的简单实例:

importCocoavarA=truevarB=falseprint("A&&B结果为:\(A&&B)")print("A||B结果为:\(A||B)")print("!A结果为:\(!A)")print("!B结果为:\(!B)")

A&&B结果为:falseA||B结果为:true!A结果为:false!B结果为:true

位运算符用来对二进制位进行操作,~,&,|,^分别为取反,按位与与,按位与或,按位与异或运算,如下表实例:

p

q

p&q

p|q

p^q

0

1

如果指定A=60;及B=13;两个变量对应的二进制为:

A=00111100B=00001101

进行位运算:

图解

&

按位与。按位与运算符对两个数进行操作,然后返回一个新的数,这个数的每个位都需要两个输入数的同一位都为1时才为1。

(A&B)结果为12,二进制为00001100

|

按位或。按位或运算符|比较两个数,然后返回一个新的数,这个数的每一位设置1的条件是两个输入数的同一位都不为0(即任意一个为1,或都为1)。

(A|B)结果为61,二进制为00111101

^

按位异或.按位异或运算符^比较两个数,然后返回一个数,这个数的每个位设为1的条件是两个输入数的同一位不同,如果相同就设为0。

(A^B)结果为49,二进制为00110001

~

按位取反运算符~对一个操作数的每一位都取反。

(~A)结果为-61,二进制为11000011in2'scomplementform.

<<

按位左移。左移操作符(<<)将操作数的所有位向左移动指定的位数。

A<<2结果为240,二进制为11110000

>>

按位右移。右移操作符(<<)将操作数的所有位向又移动指定的位数。

A>>2结果为15,二进制为00001111

以下为位运算的简单实例:

importCocoavarA=60//二进制为00111100varB=13//二进制为00001101print("A&B结果为:\(A&B)")print("A|B结果为:\(A|B)")print("A^B结果为:\(A^B)")print("~A结果为:\(~A)")

A&B结果为:12A|B结果为:61A^B结果为:49~A结果为:-61

下表列出了Swift语言的基本赋值运算:

=

简单的赋值运算,指定右边操作数赋值给左边的操作数。

C=A+B将A+B的运算结果赋值给C

+=

相加后再赋值,将左右两边的操作数相加后再赋值给左边的操作数。

C+=A相当于C=C+A

-=

相减后再赋值,将左右两边的操作数相减后再赋值给左边的操作数。

C-=A相当于C=C-A

*=

相乘后再赋值,将左右两边的操作数相乘后再赋值给左边的操作数。

C=A相当于C=CA

/=

相除后再赋值,将左右两边的操作数相除后再赋值给左边的操作数。

C/=A相当于C=C/A

%=

求余后再赋值,将左右两边的操作数求余后再赋值给左边的操作数。

C%=AisequivalenttoC=C%A

<<=

按位左移后再赋值

C<<=2相当于C=C<<2

>>=

按位右移后再赋值

C>>=2相当于C=C>>2

&=

按位与运算后赋值

C&=2相当于C=C&2

^=

按位异或运算符后再赋值

C^=2相当于C=C^2

|=

按位或运算后再赋值

C|=2相当于C=C|2

以下为赋值运算的简单实例:

importCocoavarA=10varB=20varC=100C=A+Bprint("C结果为:\(C)")C+=Aprint("C结果为:\(C)")C-=Aprint("C结果为:\(C)")C*=Aprint("C结果为:\(C)")C/=Aprint("C结果为:\(C)")//以下测试已注释,可去掉注释测试每个实例/*C%=Aprint("C结果为:\(C)")C<<=Aprint("C结果为:\(C)")C>>=Aprint("C结果为:\(C)")C&=Aprint("C结果为:\(C)")C^=Aprint("C结果为:\(C)")C|=Aprint("C结果为:\(C)")*/

C结果为:30C结果为:40C结果为:30C结果为:300C结果为:30

Swift提供了两个区间的运算符。

闭区间运算符

闭区间运算符(a...b)定义一个包含从a到b(包括a和b)的所有值的区间,b必须大于等于a。闭区间运算符在迭代一个区间的所有值时是非常有用的,如在for-in循环中:

1...5区间值为1,2,3,4和5

半开区间运算符

半开区间(a..

1..<5区间值为1,2,3,和4

以下为区间运算的简单实例:

importCocoaprint("闭区间运算符:")forindexin1...5{print("\(index)*5=\(index*5)")}print("半开区间运算符:")forindexin1..<5{print("\(index)*5=\(index*5)")}

闭区间运算符:1*5=52*5=103*5=154*5=205*5=25半开区间运算符:1*5=52*5=103*5=154*5=20

Swift提供了其他类型的的运算符,如一元、二元和三元运算符。

一元减

数字前添加-号前缀

-3或-4

一元加

数字钱添加+号前缀

+6结果为6

三元运算符

条件X:Y

如果添加为true,值为X,否则为Y

以下为一元、二元、三元的运算的简单实例:

importCocoavarA=1varB=2varC=truevarD=falseprint("-A的值为:\(-A)")print("A+B的值为:\(A+B)")print("三元运算:\(CA:B)")print("三元运算:\(DA:B)")

-A的值为:-1A+B的值为:3三元运算:1三元运算:2

在一个表达式中可能包含多个有不同运算符连接起来的、具有不同数据类型的数据对象;由于表达式有多种运算,不同的运算顺序可能得出不同结果甚至出现错误运算错误,因为当表达式中含多种运算时,必须按一定顺序进行结合,才能保证运算的合理性和结果的正确性、唯一性。

优先级从上到下依次递减,最上面具有最高的优先级,逗号操作符具有最低的优先级。

相同优先级中,按结合顺序计算。大多数运算是从左至右计算,只有三个优先级是从右至左结合的,它们是单目运算符、条件运算符、赋值运算符。

基本的优先级需要记住:

运算符类型

结合方向

表达式运算

()[].expr++expr--

左到右

一元运算符

&+-!~++expr--expr*/%+->><<<><=>===!=

右到左

位运算符

&^|&&||

:

赋值运算符

=+=-=*=/=%=>>=<<=&=^=|=

逗号

,

以下为运算符优先级简单实例:

importCocoavarA=0A=2+3*4%5print("A的值为:\(A)")

A的值为:4

实例解析:

根据运算符优先级,可以将以上程序的运算解析为以下步骤,表达式相当于:

2+((3*4)%5)

第一步计算:(3*4)=12,所以表达式相当于:

2+(12%5)

第二步计算12%5=2,所以表达式相当于:

2+2

此时可以容易地看出计算的结果为4。

条件语句通过设定的一个或多个条件来执行程序,在条件为真时执行指定的语句,在条件为false时执行另外指定的语句。

可以通过下图来简单了解条件语句的执行过程:

Swift提供了以下几种类型的条件语句:

语句

if语句

if语句由一个布尔表达式和一个或多个执行语句组成。

if...else语句

if语句后可以有可选的else语句,else语句在布尔表达式为false时执行。

if...elseif...else语句

if后可以有可选的elseif...else语句,elseif...else语句常用于多个条件判断。

内嵌if语句

你可以在if或elseif中内嵌if或elseif语句。

switch语句

switch语句允许测试一个变量等于多个值时的情况。

我们已经在前面的章节中讲解了条件运算符:,可以用来替代if...else语句。它的一般形式如下:

Exp1Exp2:Exp3;

其中,Exp1、Exp2和Exp3是表达式。请注意,冒号的使用和位置。

表达式的值是由Exp1决定的。如果Exp1为真,则计算Exp2的值,结果即为整个表达式的值。如果Exp1为假,则计算Exp3的值,结果即为整个表达式的值。

一个if语句由一个布尔表达式后跟一个或多个语句组成。

Swift语言中if语句的语法:

ifboolean_expression{/*如果布尔表达式为真将执行的语句*/}

如果布尔表达式为true,则if语句内的代码块将被执行。如果布尔表达式为false,则if语句结束后的第一组代码(闭括号后)将被执行。

importCocoavarvarA:Int=10;/*检测条件*/ifvarA<20{/*如果条件语句为true执行以下程序*/print("varA小于20");}print("varA变量的值为\(varA)");

当上面的代码被编译和执行时,它会产生下列结果:

varA小于20varA变量的值为10

一个if语句后可跟一个可选的else语句,else语句在布尔表达式为false时执行。

Swift语言中if...else语句的语法:

ifboolean_expression{/*如果布尔表达式为真将执行的语句*/}else{/*如果布尔表达式为假将执行的语句*/}

如果布尔表达式为true,则执行if块内的代码。如果布尔表达式为false,则执行else块内的代码。

importCocoavarvarA:Int=100;/*检测布尔条件*/ifvarA<20{/*如果条件为true执行以下语句*/print("varA小于20");}else{/*如果条件为false执行以下语句*/print("varA大于20");}print("varA变量的值为\(varA)");

当上面的代码被编译执行时,它会产生下列结果:

varA大于20varA变量的值为100

一个if语句后可跟一个可选的elseif...else语句,elseif...else语句在测试多个条件语句时是非常有用的。

当你使用if,elseif,else语句时需要注意以下几点:

ifboolean_expression_1{/*如果boolean_expression_1表达式为true则执行该语句*/}elseifboolean_expression_2{/*如果boolean_expression_2表达式为true则执行该语句*/}elseifboolean_expression_3{/*如果boolean_expression_3表达式为true则执行该语句*/}else{/*如果以上所有条件表达式都不为true则执行该语句*/}

importCocoavarvarA:Int=100;/*检测布尔条件*/ifvarA==20{/*如果条件为true执行以下语句*/print("varA的值为20");}elseifvarA==50{/*如果条件为true执行以下语句*/print("varA的值为50");}else{/*如果以上条件都为false执行以下语句*/print("没有匹配条件");}print("varA变量的值为\(varA)");

没有匹配条件varA变量的值为100

在Swift语言中,你可以在一个if或elseif语句内使用另一个if或elseif语句。

Swift语言中嵌套if语句的语法:

您可以嵌套elseif...else,方式与嵌套if语句相似。

importCocoavarvarA:Int=100;varvarB:Int=200;/*检测布尔条件*/ifvarA==100{/*如果条件为true执行以下语句*/print("第一个条件为true");ifvarB==200{/*如果条件为true执行以下语句*/print("第二个条件也是true");}}print("varA变量的值为\(varA)");print("varB变量的值为\(varB)");

第一个条件为true第二个条件也是truevarA变量的值为100varB变量的值为200

switch语句允许测试一个变量等于多个值时的情况。Swift语言中只要匹配到case语句,则整个switch语句执行完成。

Swift语言中switch语句的语法:

switchexpression{caseexpression1:statement(s)fallthrough/*可选*/caseexpression2,expression3:statement(s)fallthrough/*可选*/default:/*可选*/statement(s);}

一般在switch语句中不使用fallthrough语句。

这里我们需要注意case语句中如果没有使用fallthrough语句,则在执行当前的case语句后,switch会终止,控制流将跳转到switch语句后的下一行。

如果使用了fallthrough语句,则会继续执行之后的case或default语句,不论条件是否满足都会执行。

注意:在大多数语言中,switch语句块中,case要紧跟break,否则case之后的语句会顺序运行,而在Swift语言中,默认是不会执行下去的,switch也会终止。如果你想在Swift中让case之后的语句会按顺序继续运行,则需要使用fallthrough语句。

以下实例没有使用fallthrough语句:

importCocoavarindex=10switchindex{case100:print("index的值为100")case10,15:print("index的值为10或15")case5:print("index的值为5")default:print("默认case")}

index的值为10或15

以下实例使用fallthrough语句:

importCocoavarindex=10switchindex{case100:print("index的值为100")fallthroughcase10,15:print("index的值为10或15")fallthroughcase5:print("index的值为5")default:print("默认case")}

index的值为10或15index的值为5

有的时候,我们可能需要多次执行同一块代码。一般情况下,语句是按顺序执行的:函数中的第一个语句先执行,接着是第二个语句,依此类推。

编程语言提供了更为复杂执行路径的多种控制结构。

循环语句允许我们多次执行一个语句或语句组,下面是大多数编程语言中循环语句的流程图:

Swift语言提供了以下几种循环类型。点击链接查看每个类型的详细描述:

循环类型

for-in

遍历一个集合里面的所有元素,例如由数字表示的区间、数组中的元素、字符串中的字符。

for循环

用来重复执行一系列语句直到达成特定条件达成,一般通过在每次循环完成后增加计数器的值来实现。

while循环

运行一系列语句,如果条件为true,会重复运行,直到条件变为false。

repeat...while循环

类似while语句区别在于判断循环条件之前,先执行一次循环的代码块。

循环控制语句改变你代码的执行顺序,通过它你可以实现代码的跳转。Swift以下几种循环控制语句:

控制语句

continue语句

告诉一个循环体立刻停止本次循环迭代,重新开始下次循环迭代。

break语句

中断当前循环。

fallthrough语句

如果在一个case执行完后,继续执行下面的case,需要使用fallthrough(贯穿)关键字。

Swiftfor-in循环用于遍历一个集合里面的所有元素,例如由数字表示的区间、数组中的元素、字符串中的字符。

Swiftfor-in循环的语法格式如下:

forindexinvar{循环体}

流程图:

importCocoavarsomeInts:[Int]=[10,20,30]forindexinsomeInts{print("index的值为\(index)")}

以上程序执行输出结果为:

index的值为10index的值为20index的值为30

Swiftfor循环用来重复执行一系列语句直到达成特定条件,一般通过在每次循环完成后增加计数器的值来实现。

Swiftfor循环的语法格式如下:

forinit;condition;increment{循环体}

参数解析:

importCocoavarsomeInts:[Int]=[10,20,30]forvarindex=0;index<3;++index{print("索引[\(index)]对应的值为\(someInts[index])")}

索引[0]对应的值为10索引[1]对应的值为20索引[2]对应的值为30

Swiftwhile循环从计算单一条件开始。如果条件为true,会重复运行一系列语句,直到条件变为false。

Swiftwhile循环的语法格式如下:

whilecondition{statement(s)}

语法中的statement(s)可以是一个语句或者一个语句块。condition可以是一个表达式。如果条件为true,会重复运行一系列语句,直到条件变为false。

数字0,字符串'0'和"",空的list(),及未定义的变量都为false,其他的则都为true。true取反使用!号或not,取反后返回false。

importCocoavarindex=10whileindex<20{print("index的值为\(index)")index=index+1}

index的值为10index的值为11index的值为12index的值为13index的值为14index的值为15index的值为16index的值为17index的值为18index的值为19

Swiftrepeat...while循环不像for和while循环在循环体开始执行前先判断条件语句,而是在循环执行结束时判断条件是否符合。

Swiftrepeat...while循环的语法格式如下:

repeat{statement(s);}while(condition);

请注意,条件表达式出现在循环的尾部,所以循环中的statement(s)会在条件被测试之前至少执行一次。

如果条件为true,控制流会跳转回上面的repeat,然后重新执行循环中的statement(s)。这个过程会不断重复,直到给定条件变为false为止。

importCocoavarindex=15repeat{print("index的值为\(index)")index=index+1}whileindex<20

index的值为15index的值为16index的值为17index的值为18index的值为19

Swiftcontinue语句告诉一个循环体立刻停止本次循环迭代,重新开始下次循环迭代。

对于for循环,continue语句执行后自增语句仍然会执行。对于while和do...while循环,continue语句重新执行条件判断语句。

Swiftcontinue语句的语法格式如下:

importCocoavarindex=10repeat{index=index+1if(index==15){//index等于15时跳过continue}print("index的值为\(index)")}whileindex<20

index的值为11index的值为12index的值为13index的值为14index的值为16index的值为17index的值为18index的值为19index的值为20

Swiftbreak语句会立刻结束整个控制流的执行。

如果您使用的是嵌套循环(即一个循环内嵌套另一个循环),break语句会停止执行最内层的循环,然后开始执行该块之后的下一行代码。

Swiftbreak语句的语法格式如下:

importCocoavarindex=10repeat{index=index+1if(index==15){//index等于15时终止循环break}print("index的值为\(index)")}whileindex<20

index的值为11index的值为12index的值为13index的值为14

Swiftfallthrough语句让case之后的语句会按顺序继续运行,且不论条件是否满足都会执行。

Swift中的switch不会从上一个case分支落入到下一个case分支中。只要第一个匹配到的case分支完成了它需要执行的语句,整个switch代码块完成了它的执行。

Swiftfallthrough语句的语法格式如下:

Swift字符串是一系列字符的集合。例如"Hello,World!"这样的有序的字符类型的值的集合,它的数据类型为String。

你可以通过使用字符串字面量或String类的实例来创建一个字符串:

importCocoa//使用字符串字面量varstringA="Hello,World!"print(stringA)//String实例化varstringB=String("Hello,World!")print(stringB)

Hello,World!Hello,World!

你可以使用空的字符串字面量赋值给变量或初始化一个String类的实例来初始值一个空的字符串。我们可以使用字符串属性isEmpty来判断字符串是否为空:

importCocoa//使用字符串字面量创建空字符串varstringA=""ifstringA.isEmpty{print("stringA是空的")}else{print("stringA不是空的")}//实例化String类来创建空字符串letstringB=String()ifstringB.isEmpty{print("stringB是空的")}else{print("stringB不是空的")}

stringA是空的stringB是空的

你可以将一个字符串赋值给一个变量或常量,变量是可修改的,常量是不可修改的。

以上程序执行输出结果会报错,以为stringB为常量是不能被修改的:

字符串插值是一种构建新字符串的方式,可以在其中包含常量、变量、字面量和表达式。您插入的字符串字面量的每一项都在以反斜线为前缀的圆括号中:

importCocoavarvarA=20letconstA=100varvarC:Float=20.0varstringA="\(varA)乘于\(constA)等于\(varC*100)"print(stringA)

20乘于100等于2000.0

字符串可以通过+号来连接,实例如下:

字符串长度使用String.characters.count属性来计算,实例如下:

importCocoavarvarA="www.runoob.com"print("\(varA),长度为\(varA.characters.count)")

www.runoob.com,长度为14

你可以使用==来比较两个字符串是否相等:

importCocoavarvarA="Hello,Swift!"varvarB="Hello,World!"ifvarA==varB{print("\(varA)与\(varB)是相等的")}else{print("\(varA)与\(varB)是不相等的")}

Hello,Swift!与Hello,World!是不相等的

Unicode是一个国际标准,用于文本的编码,Swift的String类型是基于Unicode建立的。你可以循环迭代出字符串中UTF-8与UTF-16的编码,实例如下:

importCocoavarunicodeString="菜鸟教程"print("UTF-8编码:")forcodeinunicodeString.utf8{print("\(code)")}print("\n")print("UTF-16编码:")forcodeinunicodeString.utf16{print("\(code)")}

UTF-8编码:232143156233184159230149153231168139UTF-16编码:33756404792594531243

Swift支持以下几种字符串函数及运算符:

函数/运算符

isEmpty

判断字符串是否为空,返回布尔值

hasPrefix(prefix:String)

检查字符串是否拥有特定后缀

hasSuffix(suffix:String)

检查字符串是否拥有特定后缀。

Int(String)

转换字符串数字为整型。实例:letmyString:String="256"letmyInt:Int=Int(myString)

String.characters.count

计算字符串的长度

utf8

您可以通过遍历String的utf8属性来访问它的UTF-8编码

utf16

您可以通过遍历String的utf8属性来访问它的UTF-16编码

unicodeScalars

您可以通过遍历String值的unicodeScalars属性来访问它的Unicode标量编码.

连接两个字符串,并返回一个新的字符串

连接操作符两边的字符串并将新字符串赋值给左边的操作符变量

判断两个字符串是否相等

比较两个字符串,对两个字符串的字母逐一比较。

比较两个字符串是否不相等。

Swift的字符是一个单一的字符字符串字面量,数据类型为Character。

以下实例列出了两个字符实例:

importCocoaletchar1:Character="A"letchar2:Character="B"print("char1的值为\(char1)")print("char2的值为\(char2)")

char1的值为Achar2的值为B

如果你想在Character(字符)类型的常量中存储更多的字符,则程序执行会报错,如下所示:

importCocoa//Swift中以下赋值会报错letchar:Character="AB"print("Valueofchar\(char)")

error:cannotconvertvalueoftype'String'tospecifiedtype'Character'letchar:Character="AB"

Swift中不能创建空的Character(字符)类型变量或常量:

importCocoa//Swift中以下赋值会报错letchar1:Character=""varchar2:Character=""print("char1的值为\(char1)")print("char2的值为\(char2)")

error:cannotconvertvalueoftype'String'tospecifiedtype'Character'letchar1:Character=""^~error:cannotconvertvalueoftype'String'tospecifiedtype'Character'varchar2:Character=""

Swift的String类型表示特定序列的Character(字符)类型值的集合。每一个字符值代表一个Unicode字符。

您可通过for-in循环来遍历字符串中的characters属性来获取每一个字符的值:

importCocoaforchin"Hello".characters{print(ch)}

Hello

以下实例演示了使用String的append()方法来实现字符串连接字符:

importCocoavarvarA:String="Hello"letvarB:Character="G"varA.append(varB)print("varC=\(varA)")

varC=HelloG

Swift数组使用有序列表存储同一类型的多个值。相同的值可以多次出现在一个数组的不同位置中。

Swift数组会强制检测元素的类型,如果类型不同则会报错,Swift数组应该遵循像Array这样的形式,其中Element是这个数组中唯一允许存在的数据类型。

如果创建一个数组,并赋值给一个变量,则创建的集合就是可以修改的。这意味着在创建数组后,可以通过添加、删除、修改的方式改变数组里的项目。如果将一个数组赋值给常量,数组就不可更改,并且数组的大小和内容都不可以修改。

我们可以使用构造语法来创建一个由特定数据类型构成的空数组:

varsomeArray=[SomeType]()

以下是创建一个初始化大小数组的语法:

varsomeArray=[SomeType](count:NumbeOfElements,repeatedValue:InitialValue)

以下实例创建了一个类型为Int,大小为3,初始值为0的空数组:

varsomeInts=[Int](count:3,repeatedValue:0)

以下实例创建了含有三个元素的数组:

varsomeInts:[Int]=[10,20,30]

我们可以根据数组的索引来访问数组的元素,语法如下:

varsomeVar=someArray[index]

index索引从0开始,及索引0对应第一个元素,索引1对应第二个元素,以此类推。

我们可以通过以下实例来学习如何创建,初始化,访问数组:

importCocoavarsomeInts=[Int](count:3,repeatedValue:10)varsomeVar=someInts[0]print("第一个元素的值\(someVar)")print("第二个元素的值\(someInts[1])")print("第三个元素的值\(someInts[2])")

第一个元素的值10第二个元素的值10第三个元素的值10

你可以使用append()方法或者赋值运算符+=在数组末尾添加元素,如下所示,我们初始化一个数组,并向其添加元素:

importCocoavarsomeInts=[Int]()someInts.append(20)someInts.append(30)someInts+=[40]varsomeVar=someInts[0]print("第一个元素的值\(someVar)")print("第二个元素的值\(someInts[1])")print("第三个元素的值\(someInts[2])")

第一个元素的值20第二个元素的值30第三个元素的值40

我们也可以通过索引修改数组元素的值:

importCocoavarsomeInts=[Int]()someInts.append(20)someInts.append(30)someInts+=[40]//修改最后一个元素someInts[2]=50varsomeVar=someInts[0]print("第一个元素的值\(someVar)")print("第二个元素的值\(someInts[1])")print("第三个元素的值\(someInts[2])")

第一个元素的值20第二个元素的值30第三个元素的值50

我们可以使用for-in循环来遍历所有数组中的数据项:

importCocoavarsomeStrs=[String]()someStrs.append("Apple")someStrs.append("Amazon")someStrs.append("Runoob")someStrs+=["Google"]foriteminsomeStrs{print(item)}

AppleAmazonRunoobGoogle

如果我们同时需要每个数据项的值和索引值,可以使用String的enumerate()方法来进行数组遍历。实例如下:

importCocoavarsomeStrs=[String]()someStrs.append("Apple")someStrs.append("Amazon")someStrs.append("Runoob")someStrs+=["Google"]for(index,item)insomeStrs.enumerate(){print("在index=\(index)位置上的值为\(item)")}

在index=0位置上的值为Apple在index=1位置上的值为Amazon在index=2位置上的值为Runoob在index=3位置上的值为Google

我们可以使用加法操作符(+)来合并两种已存在的相同类型数组。新数组的数据类型会从两个数组的数据类型中推断出来:

importCocoavarintsA=[Int](count:2,repeatedValue:2)varintsB=[Int](count:3,repeatedValue:1)varintsC=intsA+intsBforiteminintsC{print(item)}

22111

我们可以使用count属性来计算数组元素个数:

importCocoavarintsA=[Int](count:2,repeatedValue:2)varintsB=[Int](count:3,repeatedValue:1)varintsC=intsA+intsBprint("intsA元素个数为\(intsA.count)")print("intsB元素个数为\(intsB.count)")print("intsC元素个数为\(intsC.count)")

intsA元素个数为2intsB元素个数为3intsC元素个数为5

我们可以通过只读属性isEmpty来判断数组是否为空,返回布尔值:

importCocoavarintsA=[Int](count:2,repeatedValue:2)varintsB=[Int](count:3,repeatedValue:1)varintsC=[Int]()print("intsA.isEmpty=\(intsA.isEmpty)")print("intsB.isEmpty=\(intsB.isEmpty)")print("intsC.isEmpty=\(intsC.isEmpty)")

intsA.isEmpty=falseintsB.isEmpty=falseintsC.isEmpty=true

Swift字典用来存储无序的相同类型数据的集合,Swift数组会强制检测元素的类型,如果类型不同则会报错。

Swift字典每个值(value)都关联唯一的键(key),键作为字典中的这个值数据的标识符。

和数组中的数据项不同,字典中的数据项并没有具体顺序。我们在需要通过标识符(键)访问数据的时候使用字典,这种方法很大程度上和我们在现实世界中使用字典查字义的方法一样。

Swift字典的key没有类型限制可以是整型或字符串,但必须是唯一的。

如果创建一个字典,并赋值给一个变量,则创建的字典就是可以修改的。这意味着在创建字典后,可以通过添加、删除、修改的方式改变字典里的项目。如果将一个字典赋值给常量,字典就不可修改,并且字典的大小和内容都不可以修改。

我们可以使用以下语法来创建一个特定类型的空字典:

varsomeDict=[KeyType:ValueType]()

以下是创建一个空字典,键的类型为Int,值的类型为String的简单语法:

varsomeDict=[Int:String]()

以下为创建一个字典的实例:

varsomeDict:[Int:String]=[1:"One",2:"Two",3:"Three"]

我们可以根据字典的索引来访问数组的元素,语法如下:

varsomeVar=someDict[key]

我们可以通过以下实例来学习如何创建,初始化,访问字典:

importCocoavarsomeDict:[Int:String]=[1:"One",2:"Two",3:"Three"]varsomeVar=someDict[1]print("key=1的值为\(someVar)")print("key=2的值为\(someDict[2])")print("key=3的值为\(someDict[3])")

key=1的值为Optional("One")key=2的值为Optional("Two")key=3的值为Optional("Three")

我们可以使用updateValue(forKey:)增加或更新字典的内容。如果key不存在,则添加值,如果存在则修改key对应的值。updateValue(_:forKey:)方法返回Optional值。实例如下:

importCocoavarsomeDict:[Int:String]=[1:"One",2:"Two",3:"Three"]varoldVal=someDict.updateValue("One新的值",forKey:1)varsomeVar=someDict[1]print("key=1旧的值\(oldVal)")print("key=1的值为\(someVar)")print("key=2的值为\(someDict[2])")print("key=3的值为\(someDict[3])")

key=1旧的值Optional("One")key=1的值为Optional("One新的值")key=2的值为Optional("Two")key=3的值为Optional("Three")

你也可以通过指定的key来修改字典的值,如下所示:

importCocoavarsomeDict:[Int:String]=[1:"One",2:"Two",3:"Three"]varoldVal=someDict[1]someDict[1]="One新的值"varsomeVar=someDict[1]print("key=1旧的值\(oldVal)")print("key=1的值为\(someVar)")print("key=2的值为\(someDict[2])")print("key=3的值为\(someDict[3])")

我们可以使用removeValueForKey()方法来移除字典key-value对。如果key存在该方法返回移除的值,如果不存在返回nil。实例如下:

importCocoavarsomeDict:[Int:String]=[1:"One",2:"Two",3:"Three"]varremovedValue=someDict.removeValueForKey(2)print("key=1的值为\(someDict[1])")print("key=2的值为\(someDict[2])")print("key=3的值为\(someDict[3])")

key=1的值为Optional("One")key=2的值为nilkey=3的值为Optional("Three")

你也可以通过指定键的值为nil来移除key-value(键-值)对。实例如下:

importCocoavarsomeDict:[Int:String]=[1:"One",2:"Two",3:"Three"]someDict[2]=nilprint("key=1的值为\(someDict[1])")print("key=2的值为\(someDict[2])")print("key=3的值为\(someDict[3])")

我们可以使用for-in循环来遍历某个字典中的键值对。实例如下:

importCocoavarsomeDict:[Int:String]=[1:"One",2:"Two",3:"Three"]for(key,value)insomeDict{print("字典key\(key)-字典value\(value)")}

字典key2-字典valueTwo字典key3-字典valueThree字典key1-字典valueOne

我们也可以使用enumerate()方法来进行字典遍历,返回的是字典的索引及(key,value)对,实例如下:

importCocoavarsomeDict:[Int:String]=[1:"One",2:"Two",3:"Three"]for(key,value)insomeDict.enumerate(){print("字典key\(key)-字典(key,value)对\(value)")}

字典key0-字典(key,value)对(2,"Two")字典key1-字典(key,value)对(3,"Three")字典key2-字典(key,value)对(1,"One")

你可以提取字典的键值(key-value)对,并转换为独立的数组。实例如下:

importCocoavarsomeDict:[Int:String]=[1:"One",2:"Two",3:"Three"]letdictKeys=[Int](someDict.keys)letdictValues=[String](someDict.values)print("输出字典的键(key)")for(key)indictKeys{print("\(key)")}print("输出字典的值(value)")for(value)indictValues{print("\(value)")}

输出字典的键(key)231输出字典的值(value)TwoThreeOne

我们可以使用只读的count属性来计算字典有多少个键值对:

importCocoavarsomeDict1:[Int:String]=[1:"One",2:"Two",3:"Three"]varsomeDict2:[Int:String]=[4:"Four",5:"Five"]print("someDict1含有\(someDict1.count)个键值对")print("someDict2含有\(someDict2.count)个键值对")

someDict1含有3个键值对someDict2含有2个键值对

Y我们可以通过只读属性isEmpty来判断字典是否为空,返回布尔值:

importCocoavarsomeDict1:[Int:String]=[1:"One",2:"Two",3:"Three"]varsomeDict2:[Int:String]=[4:"Four",5:"Five"]varsomeDict3:[Int:String]=[Int:String]()print("someDict1=\(someDict1.isEmpty)")print("someDict2=\(someDict2.isEmpty)")print("someDict3=\(someDict3.isEmpty)")

someDict1=falsesomeDict2=falsesomeDict3=true

Swift函数用来完成特定任务的独立的代码块。

Swift使用一个统一的语法来表示简单的C语言风格的函数到复杂的Objective-C语言风格的方法。

Swift函数包含了参数类型及返回值类型:

Swift定义函数使用关键字func。

定义函数的时候,可以指定一个或多个输入参数和一个返回值类型。

每个函数都有一个函数名来描述它的功能。通过函数名以及对应类型的参数值来调用这个函数。函数的参数传递的顺序必须与参数列表相同。

函数的实参传递的顺序必须与形参列表相同,->后定义函数的返回值类型。

funcfuncname(形参)->returntype{Statement1Statement2……StatementNreturnparameters}

以下我们定义了一个函数名为runoob的函数,形参的数据类型为String,返回值也为String:

importCocoafuncrunoob(site:String)->String{returnsite}print(runoob("www.runoob.com"))

www.runoob.com

我们可以通过函数名以及对应类型的参数值来调用函数,函数的参数传递的顺序必须与参数列表相同。

以下我们定义了一个函数名为runoob的函数,形参site的数据类型为String,之后我们调用函数传递的实参也必须String类型,实参传入函数体后,将直接返回,返回的数据类型为String。

函数可以接受一个或者多个参数,我们也可以使用元组(tuple)向函数传递一个或多个参数:

importCocoafuncmult(no1:Int,no2:Int)->Int{returnno1*no2}print(mult(2,no2:20))print(mult(3,no2:15))print(mult(4,no2:30))

4045120

我们可以创建不带参数的函数。

funcfuncname()->datatype{returndatatype}

importCocoafuncsitename()->String{return"菜鸟教程"}print(sitename())

菜鸟教程

函数返回值类型可以是字符串,整型,浮点型等。

元组与数组类似,不同的是,元组中的元素可以是任意类型,使用的是圆括号。

你可以用元组(tuple)类型让多个值作为一个复合值从函数中返回。

下面的这个例子中,定义了一个名为minMax(_:)的函数,作用是在一个Int数组中找出最小值与最大值。

importCocoafuncminMax(array:[Int])->(min:Int,max:Int){varcurrentMin=array[0]varcurrentMax=array[0]forvalueinarray[1..currentMax{currentMax=value}}return(currentMin,currentMax)}letbounds=minMax([8,-6,2,109,3,71])print("最小值为\(bounds.min),最大值为\(bounds.max)")

minMax(_:)函数返回一个包含两个Int值的元组,这些值被标记为min和max,以便查询函数的返回值时可以通过名字访问它们。

最小值为-6,最大值为109

如果你不确定返回的元组一定不为nil,那么你可以返回一个可选的元组类型。

你可以通过在元组类型的右括号后放置一个问号来定义一个可选元组,例如(Int,Int)或(String,Int,Bool)

注意可选元组类型如(Int,Int)与元组包含可选类型如(Int,Int)是不同的.可选的元组类型,整个元组是可选的,而不只是元组中的每个元素值。

前面的minMax(_:)函数返回了一个包含两个Int值的元组。但是函数不会对传入的数组执行任何安全检查,如果array参数是一个空数组,如上定义的minMax(_:)在试图访问array[0]时会触发一个运行时错误。

为了安全地处理这个"空数组"问题,将minMax(_:)函数改写为使用可选元组返回类型,并且当数组为空时返回nil:

importCocoafuncminMax(array:[Int])->(min:Int,max:Int){ifarray.isEmpty{returnnil}varcurrentMin=array[0]varcurrentMax=array[0]forvalueinarray[1..currentMax{currentMax=value}}return(currentMin,currentMax)}ifletbounds=minMax([8,-6,2,109,3,71]){print("最小值为\(bounds.min),组大值为\(bounds.max)")}

最小值为-6,组大值为109

下面是runoob(_:)函数的另一个版本,这个函数接收菜鸟教程官网网址参数,没有指定返回值类型,并直接输出String值,而不是返回它:

函数参数都有一个外部参数名和一个局部参数名。

局部参数名在函数的实现内部使用。

funcsample(number:Int){println(number)}

以上实例中number为局部参数名,只能在函数体内使用。

importCocoafuncsample(number:Int){print(number)}sample(1)sample(2)sample(3)

123

你可以在局部参数名前指定外部参数名,中间以空格分隔,外部参数名用于在函数调用时传递给函数的参数。

如下你可以定义以下两个函数参数名并调用它:

importCocoafuncpow(firstArga:Int,secondArgb:Int)->Int{varres=afor_in1..

125

注意如果你提供了外部参数名,那么函数在被调用时,必须使用外部参数名。

可变参数可以接受零个或多个值。函数调用时,你可以用可变参数来指定函数参数,其数量是不确定的。

可变参数通过在变量类型名后面加入(...)的方式来定义。

importCocoafuncvari(members:N...){foriinmembers{print(i)}}vari(4,3,5)vari(4.5,3.1,5.6)vari("Google","Baidu","Runoob")

4354.53.15.6GoogleBaiduRunoob

一般默认在函数中定义的参数都是常量参数,也就是这个参数你只可以查询使用,不能改变它的值。

例如:

funcgetName(varid:String).........

此时这个id值可以在函数中改变。

一般默认的参数传递都是传值调用的,而不是传引用。所以传入的参数在函数内改变,并不影响原来的那个参数。传入的只是这个参数的副本。

变量参数,正如上面所述,仅仅能在函数体内被更改。如果你想要一个函数可以修改参数的值,并且想要在这些修改在函数调用结束后仍然存在,那么就应该把这个参数定义为输入输出参数(In-OutParameters)。

定义一个输入输出参数时,在参数定义前加inout关键字。一个输入输出参数有传入函数的值,这个值被函数修改,然后被传出函数,替换原来的值。

importCocoafuncswapTwoInts(vara:Int,varb:Int){lett=aa=bb=t}varx=0,y=100print("x=\(x);y=\(y)")swapTwoInts(x,b:y)print("x=\(x);y=\(y)")

x=0;y=100x=0;y=100

修改方法是使用inout关键字:

importCocoafuncswapTwoInts(inouta:Int,inoutb:Int){lett=aa=bb=t}varx=0,y=100print("x=\(x);y=\(y)")swapTwoInts(&x,b:&y)print("x=\(x);y=\(y)")

x=0;y=100x=100;y=0

每个函数都有种特定的函数类型,由函数的参数类型和返回类型组成。

funcinputs(no1:Int,no2:Int)->Int{returnno1/no2}

importCocoafuncinputs(no1:Int,no2:Int)->Int{returnno1/no2}print(inputs(20,no2:10))print(inputs(36,no2:6))

26

以上函数定义了两个Int参数类型,返回值也为Int类型。

接下来我们看下如下函数,函数定义了参数为String类型,返回值为String类型。

Funcinputstr(name:String)->String{returnname}

函数也可以定义任何参数及类型,如下所示:

importCocoafuncinputstr(){print("菜鸟教程")print("www.runoob.com")}inputstr()

菜鸟教程www.runoob.com

在Swift中,使用函数类型就像使用其他类型一样。例如,你可以定义一个类型为函数的常量或变量,并将适当的函数赋值给它:

varaddition:(Int,Int)->Int=sum

解析:

"定义一个叫做addition的变量,参数与返回值类型均是Int,并让这个新变量指向sum函数"。

sum和addition有同样的类型,所以以上操作是合法的。

现在,你可以用addition来调用被赋值的函数了:

importCocoafuncsum(a:Int,b:Int)->Int{returna+b}varaddition:(Int,Int)->Int=sumprint("输出结果:\(addition(40,89))")

输出结果:129

我们可以将函数作为参数传递给另外一个参数:

importCocoafuncsum(a:Int,b:Int)->Int{returna+b}varaddition:(Int,Int)->Int=sumprint("输出结果:\(addition(40,89))")funcanother(addition:(Int,Int)->Int,a:Int,b:Int){print("输出结果:\(addition(a,b))")}another(sum,a:10,b:20)

输出结果:129输出结果:30

函数嵌套指的是函数内定义一个新的函数,外部的函数可以调用函数内定义的函数。

importCocoafunccalcDecrement(forDecrementtotal:Int)->()->Int{varoverallDecrement=0funcdecrementer()->Int{overallDecrement-=totalreturnoverallDecrement}returndecrementer}letdecrem=calcDecrement(forDecrement:30)print(decrem())

-30

闭包(Closures)是自包含的功能代码块,可以在代码中使用或者用来作为参数传值。

Swift中的闭包与C和Objective-C中的代码块(blocks)以及其他一些编程语言中的匿名函数比较相似。

全局函数和嵌套函数其实就是特殊的闭包。

闭包的形式有:

全局函数

嵌套函数

闭包表达式

有名字但不能捕获任何值。

有名字,也能捕获封闭函数内的值。

无名闭包,使用轻量级语法,可以根据上下文环境捕获值。

Swift中的闭包有很多优化的地方:

以下定义了一个接收参数并返回指定类型的闭包语法:

{(parameters)->returntypeinstatements}

importCocoaletstudname={print("Swift闭包实例。")}studname()

Swift闭包实例。

以下闭包形式接收两个参数并返回布尔值:

{(Int,Int)->BoolinStatement1Statement2---Statementn}

importCocoaletdivide={(val1:Int,val2:Int)->Intinreturnval1/val2}letresult=divide(200,20)print(result)

10

闭包表达式是一种利用简洁语法构建内联闭包的方式。闭包表达式提供了一些语法优化,使得撰写闭包变得简单明了。

Swift标准库提供了名为sort的函数,会根据您提供的用于排序的闭包函数将已知类型数组中的值进行排序。

排序完成后,sort(:)方法会返回一个与原数组大小相同,包含同类型元素且元素已正确排序的新数组,原数组不会被sort(:)方法修改。

sort(_:)方法需要传入两个参数:

importCocoaletnames=["AT","AE","D","S","BE"]//使用普通函数(或内嵌函数)提供排序功能,闭包函数类型需为(String,String)->Bool。funcbackwards(s1:String,s2:String)->Bool{returns1>s2}varreversed=names.sort(backwards)print(reversed)

["S","D","BE","AT","AE"]

如果第一个字符串(s1)大于第二个字符串(s2),backwards函数返回true,表示在新的数组中s1应该出现在s2前。对于字符串中的字符来说,"大于"表示"按照字母顺序较晚出现"。这意味着字母"B"大于字母"A",字符串"S"大于字符串"D"。其将进行字母逆序排序,"AT"将会排在"AE"之前。

Swift自动为内联函数提供了参数名称缩写功能,您可以直接通过$0,$1,$2来顺序调用闭包的参数。

importCocoaletnames=["AT","AE","D","S","BE"]varreversed=names.sort({$0>$1})print(reversed)

$0和$1表示闭包中第一个和第二个String类型的参数。

如果你在闭包表达式中使用参数名称缩写,您可以在闭包参数列表中省略对其定义,并且对应参数名称缩写的类型会通过函数类型进行推断。in关键字同样也可以被省略.

实际上还有一种更简短的方式来撰写上面例子中的闭包表达式。

Swift的String类型定义了关于大于号(>)的字符串实现,其作为一个函数接受两个String类型的参数并返回Bool类型的值。而这正好与sort(_:)方法的第二个参数需要的函数类型相符合。因此,您可以简单地传递一个大于号,Swift可以自动推断出您想使用大于号的字符串函数实现:

importCocoaletnames=["AT","AE","D","S","BE"]varreversed=names.sort(>)print(reversed)

尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。

funcsomeFunctionThatTakesAClosure(closure:()->Void){//函数体部分}//以下是不使用尾随闭包进行函数调用someFunctionThatTakesAClosure({//闭包主体部分})//以下是使用尾随闭包进行函数调用someFunctionThatTakesAClosure(){//闭包主体部分}

sort()后的{$0>$1}为尾随闭包。

注意:如果函数只需要闭包表达式一个参数,当您使用尾随闭包时,您甚至可以把()省略掉。

reversed=names.sort{$0>$1}

闭包可以在其定义的上下文中捕获常量或变量。

即使定义这些常量和变量的原域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。

Swift最简单的闭包形式是嵌套函数,也就是定义在其他函数的函数体内的函数。

嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。

看这个例子:

funcmakeIncrementor(forIncrementamount:Int)->()->Int{varrunningTotal=0funcincrementor()->Int{runningTotal+=amountreturnrunningTotal}returnincrementor}

一个函数makeIncrementor,它有一个Int型的参数amout,并且它有一个外部参数名字forIncremet,意味着你调用的时候,必须使用这个外部名字。返回值是一个()->Int的函数。

incrementor函数并没有获取任何参数,但是在函数体内访问了runningTotal和amount变量。这是因为其通过捕获在包含它的函数体内已经存在的runningTotal和amount变量而实现。

由于没有修改amount变量,incrementor实际上捕获并存储了该变量的一个副本,而该副本随着incrementor一同被存储。

所以我们调用这个函数时会累加:

importCocoafuncmakeIncrementor(forIncrementamount:Int)->()->Int{varrunningTotal=0funcincrementor()->Int{runningTotal+=amountreturnrunningTotal}returnincrementor}letincrementByTen=makeIncrementor(forIncrement:10)//返回的值为10print(incrementByTen())//返回的值为20print(incrementByTen())//返回的值为30print(incrementByTen())

102030

上面的例子中,incrementByTen是常量,但是这些常量指向的闭包仍然可以增加其捕获的变量值。

这是因为函数和闭包都是引用类型。

无论您将函数/闭包赋值给一个常量还是变量,您实际上都是将常量/变量的值设置为对应函数/闭包的引用。上面的例子中,incrementByTen指向闭包的引用是一个常量,而并非闭包内容本身。

这也意味着如果您将闭包赋值给了两个不同的常量/变量,两个值都会指向同一个闭包:

importCocoafuncmakeIncrementor(forIncrementamount:Int)->()->Int{varrunningTotal=0funcincrementor()->Int{runningTotal+=amountreturnrunningTotal}returnincrementor}letincrementByTen=makeIncrementor(forIncrement:10)//返回的值为10incrementByTen()//返回的值为20incrementByTen()//返回的值为30incrementByTen()//返回的值为40incrementByTen()letalsoIncrementByTen=incrementByTen//返回的值也为50print(alsoIncrementByTen())

50

枚举简单的说也是一种数据类型,只不过是这种数据类型只包含自定义的特定数据,它是一组有共同特性的数据的集合。

Swift的枚举类似于ObjectiveC和C的结构,枚举的功能为:

Swift中使用enum关键词来创建枚举并且把它们的整个定义放在一对大括号内:

enumenumname{//枚举定义放在这里}

例如我们定义以下表示星期的枚举:

importCocoa//定义枚举enumDaysofaWeek{caseSundaycaseMondaycaseTUESDAYcaseWEDNESDAYcaseTHURSDAYcaseFRIDAYcaseSaturday}varweekDay=DaysofaWeek.THURSDAYweekDay=.THURSDAYswitchweekDay{case.Sunday:print("星期天")case.Monday:print("星期一")case.TUESDAY:print("星期二")case.WEDNESDAY:print("星期三")case.THURSDAY:print("星期四")case.FRIDAY:print("星期五")case.Saturday:print("星期六")}

星期四

枚举中定义的值(如Sunday,Monday,……和Saturday)是这个枚举的成员值(或成员)。case关键词表示一行新的成员值将被定义。

注意:和C和Objective-C不同,Swift的枚举成员在被创建时不会被赋予一个默认的整型值。在上面的DaysofaWeek例子中,Sunday,Monday,……和Saturday不会隐式地赋值为0,1,……和6。相反,这些枚举成员本身就有完备的值,这些值是已经明确定义好的DaysofaWeek类型。

varweekDay=DaysofaWeek.THURSDAY

varweekDay=.THURSDAY

当weekDay的类型已知时,再次为其赋值可以省略枚举名。使用显式类型的枚举值可以让代码具有更好的可读性。

原始值

不同数据类型

相同数据类型

实例:enum{10,0.8,"Hello"}

实例:enum{10,35,50}

值的创建基于常量或变量

预先填充的值

原始值始终是相同的

importCocoaenumStudent{caseName(String)caseMark(Int,Int,Int)}varstudDetails=Student.Name("Runoob")varstudMarks=Student.Mark(98,97,95)switchstudMarks{case.Name(letstudName):print("学生的名字是:\(studName)。")case.Mark(letMark1,letMark2,letMark3):print("学生的成绩是:\(Mark1),\(Mark2),\(Mark3)。")}

学生的成绩是:98,97,95。

在原始值为整数的枚举时,不需要显式的为每一个成员赋值,Swift会自动为你赋值。

importCocoaenumMonth:Int{caseJanuary=1,February,March,April,May,June,July,August,September,October,November,December}letyearMonth=Month.May.rawValueprint("数字月份为:\(yearMonth)。")

数字月份为:5。

Swift结构体是构建代码所用的一种通用且灵活的构造体。

我们可以为结构体定义属性(常量、变量)和添加方法,从而扩展结构体的功能。

与C和ObjectiveC不同的是:

结构体总是通过被复制的方式在代码中传递,因此它的值是不可修改的。

我们通过关键字struct来定义结构体:

structnameStruct{Definition1Definition2……DefinitionN}

我们定义一个名为MarkStruct的结构体,结构体的属性为学生三个科目的分数,数据类型为Int:

structMarkStruct{varmark1:Intvarmark2:Intvarmark3:Int}

我们可以通过结构体名来访问结构体成员。

结构体实例化使用let关键字:

importCocoastructstudentMarks{varmark1=100varmark2=78varmark3=98}letmarks=studentMarks()print("Mark1是\(marks.mark1)")print("Mark2是\(marks.mark2)")print("Mark3是\(marks.mark3)")

Mark1是100Mark2是78Mark3是98

实例中,我们通过结构体名'studentMarks'访问学生的成绩。结构体成员初始化为mark1,mark2,mark3,数据类型为整型。

然后我们通过使用let关键字将结构体studentMarks()实例化并传递给marks。

最后我们就通过.号来访问结构体成员的值。

以下实例化通过结构体实例化时传值并克隆一个结构体:

importCocoastructMarksStruct{varmark:Intinit(mark:Int){self.mark=mark}}varaStruct=MarksStruct(mark:98)varbStruct=aStruct//aStruct和bStruct是使用相同值的结构体!bStruct.mark=97print(aStruct.mark)//98print(bStruct.mark)//97

9897

在你的代码中,你可以使用结构体来定义你的自定义数据类型。

结构体实例总是通过值传递来定义你的自定义数据类型。

按照通用的准则,当符合一条或多条以下条件时,请考虑构建结构体:

举例来说,以下情境中适合使用结构体:

结构体实例是通过值传递而不是通过引用传递。

importCocoastructmarkStruct{varmark1:Intvarmark2:Intvarmark3:Intinit(mark1:Int,mark2:Int,mark3:Int){self.mark1=mark1self.mark2=mark2self.mark3=mark3}}print("优异成绩:")varmarks=markStruct(mark1:98,mark2:96,mark3:100)print(marks.mark1)print(marks.mark2)print(marks.mark3)print("糟糕成绩:")varfail=markStruct(mark1:34,mark2:42,mark3:13)print(fail.mark1)print(fail.mark2)print(fail.mark3)

优异成绩:9896100糟糕成绩:344213

以上实例中我们定义了结构体markStruct,三个成员属性:mark1,mark2和mark3。结构体内使用成员属性使用self关键字。

从实例中我们可以很好的理解到结构体实例是通过值传递的。

Swift类是构建代码所用的一种通用且灵活的构造体。

我们可以为类定义属性(常量、变量)和方法。

与其他编程语言所不同的是,Swift并不要求你为自定义类去创建独立的接口和实现文件。你所要做的是在一个单一文件中定义一个类,系统会自动生成面向其它代码的外部接口。

Swift中类和结构体有很多共同点。共同处在于:

与结构体相比,类还有如下的附加功能:

Classclassname{Definition1Definition2……DefinitionN}

classstudent{varstudname:Stringvarmark:Intvarmark2:Int}

实例化类:

letstudrecord=student()

importCocoaclassMarksStruct{varmark:Intinit(mark:Int){self.mark=mark}}classstudentMarks{varmark=300}letmarks=studentMarks()print("成绩为\(marks.mark)")

成绩为300

类的属性可以通过.来访问。格式为:实例化类名.属性名:

importCocoaclassMarksStruct{varmark:Intinit(mark:Int){self.mark=mark}}classstudentMarks{varmark1=300varmark2=400varmark3=900}letmarks=studentMarks()print("Mark1is\(marks.mark1)")print("Mark2is\(marks.mark2)")print("Mark3is\(marks.mark3)")

Mark1is300Mark2is400Mark3is900

因为类是引用类型,有可能有多个常量和变量在后台同时引用某一个类实例。

为了能够判定两个常量或者变量是否引用同一个类实例,Swift内建了两个恒等运算符:

恒等运算符

不恒等运算符

运算符为:===

运算符为:!==

如果两个常量或者变量引用同一个类实例则返回true

如果两个常量或者变量引用不同一个类实例则返回true

importCocoaclassSampleClass:Equatable{letmyProperty:Stringinit(s:String){myProperty=s}}func==(lhs:SampleClass,rhs:SampleClass)->Bool{returnlhs.myProperty==rhs.myProperty}letspClass1=SampleClass(s:"Hello")letspClass2=SampleClass(s:"Hello")ifspClass1===spClass2{//falseprint("引用相同的类实例\(spClass1)")}ifspClass1!==spClass2{//trueprint("引用不相同的类实例\(spClass2)")}

引用不相同的类实例SampleClass

Swift属性将值跟特定的类、结构或枚举关联。

属性可分为存储属性和计算属性:

存储属性

计算属性

存储常量或变量作为实例的一部分

计算(而不是存储)一个值

用于类和结构体

用于类、结构体和枚举

存储属性和计算属性通常用于特定类型的实例。

属性也可以直接用于类型本身,这种属性称为类型属性。

另外,还可以定义属性观察器来监控属性值的变化,以此来触发一个自定义的操作。属性观察器可以添加到自己写的存储属性上,也可以添加到从父类继承的属性上。

简单来说,一个存储属性就是存储在特定类或结构体的实例里的一个常量或变量。

存储属性可以是变量存储属性(用关键字var定义),也可以是常量存储属性(用关键字let定义)。

importCocoastructNumber{vardigits:Intletpi=3.1415}varn=Number(digits:12345)n.digits=67print("\(n.digits)")print("\(n.pi)")

673.1415

考虑以下代码:

letpi=3.1415

代码中pi在定义存储属性的时候指定默认值(pi=3.1415),所以不管你什么时候实例化结构体,它都不会改变。

如果你定义的是一个常量存储属性,如果尝试修改它就会报错,如下所示:

importCocoastructNumber{vardigits:Intletnumbers=3.1415}varn=Number(digits:12345)n.digits=67print("\(n.digits)")print("\(n.numbers)")n.numbers=8.7

以上程序,执行会报错,错误如下所示:

error:cannotassigntoproperty:'numbers'isa'let'constantn.numbers=8.7

意思为'numbers'是一个常量,你能修改它。

延迟存储属性是指当第一次被调用的时候才会计算其初始值的属性。

延迟存储属性一般用于:

importCocoaclasssample{lazyvarno=number()//`var`关键字是必须的}classnumber{varname="RunoobSwift教程"}varfirstsample=sample()print(firstsample.no.name)

RunoobSwift教程

如果您有过Objective-C经验,应该知道Objective-C为类实例存储值和引用提供两种方法。对于属性来说,也可以使用实例变量作为属性值的后端存储。

Swift编程语言中把这些理论统一用属性来实现。Swift中的属性没有对应的实例变量,属性的后端存储也无法直接访问。这就避免了不同场景下访问方式的困扰,同时也将属性的定义简化成一个语句。

一个类型中属性的全部信息——包括命名、类型和内存管理特征——都在唯一一个地方(类型定义中)定义。

除存储属性外,类、结构体和枚举可以定义计算属性,计算属性不直接存储值,而是提供一个getter来获取值,一个可选的setter来间接设置其他属性或变量的值。

importCocoaclasssample{varno1=0.0,no2=0.0varlength=300.0,breadth=150.0varmiddle:(Double,Double){get{return(length/2,breadth/2)}set(axis){no1=axis.0-(length/2)no2=axis.1-(breadth/2)}}}varresult=sample()print(result.middle)result.middle=(0.0,10.0)print(result.no1)print(result.no2)

(150.0,75.0)-150.0-65.0

如果计算属性的setter没有定义表示新值的参数名,则可以使用默认名称newValue。

只有getter没有setter的计算属性就是只读计算属性。

只读计算属性总是返回一个值,可以通过点(.)运算符访问,但不能设置新的值。

importCocoaclassfilm{varhead=""varduration=0.0varmetaInfo:[String:String]{return["head":self.head,"duration":"\(self.duration)"]}}varmovie=film()movie.head="Swift属性"movie.duration=3.09print(movie.metaInfo["head"]!)print(movie.metaInfo["duration"]!)

Swift属性3.09

属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,甚至新的值和现在的值相同的时候也不例外。

可以为除了延迟存储属性之外的其他存储属性添加属性观察器,也可以通过重载属性的方式为继承的属性(包括存储属性和计算属性)添加属性观察器。

不需要为无法重载的计算属性添加属性观察器,因为可以通过setter直接监控和响应值的变化。

可以为属性添加如下的一个或全部观察器:

importCocoaclassSamplepgm{varcounter:Int=0{willSet(newTotal){print("计数器:\(newTotal)")}didSet{ifcounter>oldValue{print("新增数\(counter-oldValue)")}}}}letNewCounter=Samplepgm()NewCounter.counter=100NewCounter.counter=800

计数器:100新增数100计数器:800新增数700

计算属性和属性观察器所描述的模式也可以用于全局变量和局部变量。

局部变量

全局变量

在函数、方法或闭包内部定义的变量。

函数、方法、闭包或任何类型之外定义的变量。

用于存储和检索值。

存储属性用于获取和设置值。

也用于计算属性。

类型属性是作为类型定义的一部分写在类型最外层的花括号({})内。

使用关键字static来定义值类型的类型属性,关键字class来为类定义类型属性。

structStructname{staticvarstoredTypeProperty=""staticvarcomputedTypeProperty:Int{//这里返回一个Int值}}enumEnumname{staticvarstoredTypeProperty=""staticvarcomputedTypeProperty:Int{//这里返回一个Int值}}classClassname{classvarcomputedTypeProperty:Int{//这里返回一个Int值}}

例子中的计算型类型属性是只读的,但也可以定义可读可写的计算型类型属性,跟实例计算属性的语法类似。

类似于实例的属性,类型属性的访问也是通过点运算符(.)来进行。但是,类型属性是通过类型本身来获取和设置,而不是通过实例。实例如下:

importCocoastructStudMarks{staticletmarkCount=97staticvartotalCount=0varInternalMarks:Int=0{didSet{ifInternalMarks>StudMarks.markCount{InternalMarks=StudMarks.markCount}ifInternalMarks>StudMarks.totalCount{StudMarks.totalCount=InternalMarks}}}}varstud1Mark1=StudMarks()varstud1Mark2=StudMarks()stud1Mark1.InternalMarks=98print(stud1Mark1.InternalMarks)stud1Mark2.InternalMarks=87print(stud1Mark2.InternalMarks)

9787

在Objective-C中,类是唯一能定义方法的类型。但在Swift中,你不仅能选择是否要定义一个类/结构体/枚举,还能灵活的在你创建的类型(类/结构体/枚举)上定义方法。

在Swift语言中,实例方法是属于某个特定类、结构体或者枚举类型实例的方法。

实例方法提供以下方法:

实例方法要写在它所属的类型的前后大括号({})之间。

实例方法能够隐式访问它所属类型的所有的其他实例方法和属性。

实例方法只能被它所属的类的某个特定实例调用。

实例方法不能脱离于现存的实例而被调用。

funcfuncname(Parameters)->returntype{Statement1Statement2……StatementNreturnparameters}

importCocoaclassCounter{varcount=0funcincrement(){count++}funcincrementBy(amount:Int){count+=amount}funcreset(){count=0}}//初始计数值是0letcounter=Counter()//计数值现在是1counter.increment()//计数值现在是6counter.incrementBy(5)print(counter.count)//计数值现在是0counter.reset()print(counter.count)

60

Counter类定义了三个实例方法:

Swift函数参数可以同时有一个局部名称(在函数体内部使用)和一个外部名称(在调用函数时使用

Swift中的方法和Objective-C中的方法极其相似。像在Objective-C中一样,Swift中方法的名称通常用一个介词指向方法的第一个参数,比如:with,for,by等等。

Swift默认仅给方法的第一个参数名称一个局部参数名称;默认同时给第二个和后续的参数名称为全局参数名称。

importCocoaclassdivision{varcount:Int=0funcincrementBy(no1:Int,no2:Int){count=no1/no2print(count)}}letcounter=division()counter.incrementBy(1800,no2:3)counter.incrementBy(1600,no2:5)counter.incrementBy(11000,no2:3)

6003203666

我们强制在第一个参数添加外部名称把这个局部名称当作外部名称使用(Swift2.0前是使用#号)。

importCocoaclassmultiplication{varcount:Int=0funcincrementBy(firstno1:Int,no2:Int){count=no1*no2print(count)}}letcounter=multiplication()counter.incrementBy(first:800,no2:3)counter.incrementBy(first:100,no2:5)counter.incrementBy(first:15000,no2:3)

240050045000

类型的每一个实例都有一个隐含属性叫做self,self完全等同于该实例本身。

你可以在一个实例的实例方法中使用这个隐含的self属性来引用当前实例。

importCocoaclasscalculations{leta:Intletb:Intletres:Intinit(a:Int,b:Int){self.a=aself.b=bres=a+bprint("Self内:\(res)")}functot(c:Int)->Int{returnres-c}funcresult(){print("结果为:\(tot(20))")print("结果为:\(tot(50))")}}letpri=calculations(a:600,b:300)letsum=calculations(a:1200,b:300)pri.result()sum.result()

Self内:900Self内:1500结果为:880结果为:850结果为:1480结果为:1450

Swift语言中结构体和枚举是值类型。一般情况下,值类型的属性不能在它的实例方法中被修改。

但是,如果你确实需要在某个具体的方法中修改结构体或者枚举的属性,你可以选择变异(mutating)这个方法,然后方法就可以从方法内部改变它的属性;并且它做的任何改变在方法结束时还会保留在原始结构中。

方法还可以给它隐含的self属性赋值一个全新的实例,这个新实例在方法结束后将替换原来的实例。

importCocoastructarea{varlength=1varbreadth=1funcarea()->Int{returnlength*breadth}mutatingfuncscaleBy(res:Int){length*=resbreadth*=resprint(length)print(breadth)}}varval=area(length:3,breadth:5)val.scaleBy(3)val.scaleBy(30)val.scaleBy(300)

91527045081000135000

可变方法能够赋给隐含属性self一个全新的实例。

importCocoastructarea{varlength=1varbreadth=1funcarea()->Int{returnlength*breadth}mutatingfuncscaleBy(res:Int){self.length*=resself.breadth*=resprint(length)print(breadth)}}varval=area(length:3,breadth:5)val.scaleBy(13)

3965

实例方法是被类型的某个实例调用的方法,你也可以定义类型本身调用的方法,这种方法就叫做类型方法。

类型方法和实例方法一样用点号(.)语法调用。

importCocoaclassMath{classfuncabs(number:Int)->Int{ifnumber<0{return(-number)}else{returnnumber}}}structabsno{staticfuncabs(number:Int)->Int{ifnumber<0{return(-number)}else{returnnumber}}}letno=Math.abs(-35)letnum=absno.abs(-5)print(no)print(num)

355

下标脚本可以定义在类(Class)、结构体(structure)和枚举(enumeration)这些目标中,可以认为是访问对象、集合或序列的快捷方式,不需要再调用实例的特定的赋值和访问方法。

举例来说,用下标脚本访问一个数组(Array)实例中的元素可以这样写someArray[index],访问字典(Dictionary)实例中的元素可以这样写someDictionary[key]。

对于同一个目标可以定义多个下标脚本,通过索引值类型的不同来进行重载,而且索引值的个数可以是多个。

下标脚本允许你通过在实例后面的方括号中传入一个或者多个的索引值来对实例进行访问和赋值。

语法类似于实例方法和计算型属性的混合。

与实例方法不同的是下标脚本可以设定为读写或只读。这种方式又有点像计算型属性的getter和setter:

importCocoastructsubexample{letdecrementer:Intsubscript(index:Int)->Int{returndecrementer/index}}letdivision=subexample(decrementer:100)print("100除以9等于\(division[9])")print("100除以2等于\(division[2])")print("100除以3等于\(division[3])")print("100除以5等于\(division[5])")print("100除以7等于\(division[7])")

100除以9等于11100除以2等于50100除以3等于33100除以5等于20100除以7等于14

在上例中,通过subexample结构体创建了一个除法运算的实例。数值100作为结构体构造函数传入参数初始化实例成员decrementer。

你可以通过下标脚本来得到结果,比如division[2]即为100除以2。

SundayMondayTuesdayWednesday

根据使用场景不同下标脚本也具有不同的含义。

通常下标脚本是用来访问集合(collection),列表(list)或序列(sequence)中元素的快捷方式。

你可以在你自己特定的类或结构体中自由的实现下标脚本来提供合适的功能。

例如,Swift的字典(Dictionary)实现了通过下标脚本对其实例中存放的值进行存取操作。在下标脚本中使用和字典索引相同类型的值,并且把一个字典值类型的值赋值给这个下标脚来为字典设值:

importCocoavarnumberOfLegs=["spider":8,"ant":6,"cat":4]numberOfLegs["bird"]=2print(numberOfLegs)

["ant":6,"bird":2,"cat":4,"spider":8]

上例定义一个名为numberOfLegs的变量并用一个字典字面量初始化出了包含三对键值的字典实例。numberOfLegs的字典存放值类型推断为Dictionary。字典实例创建完成之后通过下标脚本的方式将整型值2赋值到字典实例的索引为bird的位置中。

下标脚本允许任意数量的入参索引,并且每个入参类型也没有限制。

下标脚本的返回值也可以是任何类型。

下标脚本可以使用变量参数和可变参数。

一个类或结构体可以根据自身需要提供多个下标脚本实现,在定义下标脚本时通过传入参数的类型进行区分,使用下标脚本时会自动匹配合适的下标脚本实现运行,这就是下标脚本的重载。

importCocoastructMatrix{letrows:Int,columns:Intvarprint:[Double]init(rows:Int,columns:Int){self.rows=rowsself.columns=columnsprint=Array(count:rows*columns,repeatedValue:0.0)}subscript(row:Int,column:Int)->Double{get{returnprint[(row*columns)+column]}set{print[(row*columns)+column]=newValue}}}//创建了一个新的3行3列的Matrix实例varmat=Matrix(rows:3,columns:3)//通过下标脚本设置值mat[0,0]=1.0mat[0,1]=2.0mat[1,0]=3.0mat[1,1]=5.0//通过下标脚本获取值print("\(mat[0,0])")print("\(mat[0,1])")print("\(mat[1,0])")print("\(mat[1,1])")

1.02.03.05.0

Matrix结构体提供了一个两个传入参数的构造方法,两个参数分别是rows和columns,创建了一个足够容纳rows*columns个数的Double类型数组。为了存储,将数组的大小和数组每个元素初始值0.0。

你可以通过传入合适的row和column的数量来构造一个新的Matrix实例。

继承我们可以理解为一个类获取了另外一个类的方法和属性。

当一个类继承其它类时,继承类叫子类,被继承类叫超类(或父类)

在Swift中,类可以调用和访问超类的方法,属性和下标脚本,并且可以重写它们。

我们也可以为类中继承来的属性添加属性观察器。

没有继承其它类的类,称之为基类(BaseClass)。

以下实例中我们定义了基类StudDetails,描述了学生(stname)及其各科成绩的分数(mark1、mark2、mark3):

classStudDetails{varstname:String!varmark1:Int!varmark2:Int!varmark3:Int!init(stname:String,mark1:Int,mark2:Int,mark3:Int){self.stname=stnameself.mark1=mark1self.mark2=mark2self.mark3=mark3}}letstname="swift"letmark1=98letmark2=89letmark3=76print(stname)print(mark1)print(mark2)print(mark3)

swift988976

子类指的是在一个已有类的基础上创建一个新的类。

为了指明某个类的超类,将超类名写在子类名的后面,用冒号(:)分隔,语法格式如下

classSomeClass:SomeSuperclass{//类的定义}

以下实例中我们定义了超类StudDetails,然后使用子类Tom继承它:

classStudDetails{varmark1:Int;varmark2:Int;init(stm1:Int,resultsstm2:Int){mark1=stm1;mark2=stm2;}funcshow(){print("Mark1:\(self.mark1),Mark2:\(self.mark2)")}}classTom:StudDetails{init(){super.init(stm1:93,results:89)}}lettom=Tom()tom.show()

Mark1:93,Mark2:89

子类可以通过继承来的实例方法,类方法,实例属性,或下标脚本来实现自己的定制功能,我们把这种行为叫重写(overriding)。

我们可以使用override关键字来实现重写。

你可以通过使用super前缀来访问超类的方法,属性或下标脚本。

重写

访问方法,属性,下标脚本

方法

super.somemethod()

属性

super.someProperty()

下标脚本

super[someIndex]

在我们的子类中我们可以使用override关键字来重写超类的方法。

以下实例中我们重写了show()方法:

classSuperClass{funcshow(){print("这是超类SuperClass")}}classSubClass:SuperClass{overridefuncshow(){print("这是子类SubClass")}}letsuperClass=SuperClass()superClass.show()letsubClass=SubClass()subClass.show()

这是超类SuperClass这是子类SubClass

你可以提供定制的getter(或setter)来重写任意继承来的属性,无论继承来的属性是存储型的还是计算型的属性。

子类并不知道继承来的属性是存储型的还是计算型的,它只知道继承来的属性会有一个名字和类型。所以你在重写一个属性时,必需将它的名字和类型都写出来。

注意点:

以下实例我们定义了超类Circle及子类Rectangle,在Rectangle类中我们重写属性area:

classCircle{varradius=12.5vararea:String{return"矩形半径\(radius)"}}//继承超类CircleclassRectangle:Circle{varprint=7overridevararea:String{returnsuper.area+",但现在被重写为\(print)"}}letrect=Rectangle()rect.radius=25.0rect.print=3print("Radius\(rect.area)")

Radius矩形半径25.0,但现在被重写为3

你可以在属性重写中为一个继承来的属性添加属性观察器。这样一来,当继承来的属性值发生改变时,你就会监测到。

注意:你不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器。

classCircle{varradius=12.5vararea:String{return"矩形半径为\(radius)"}}classRectangle:Circle{varprint=7overridevararea:String{returnsuper.area+",但现在被重写为\(print)"}}letrect=Rectangle()rect.radius=25.0rect.print=3print("半径:\(rect.area)")classSquare:Rectangle{overridevarradius:Double{didSet{print=Int(radius/5.0)+1}}}letsq=Square()sq.radius=100.0print("半径:\(sq.area)")

半径:矩形半径为25.0,但现在被重写为3半径:矩形半径为100.0,但现在被重写为21

我们可以使用final关键字防止它们被重写。

如果你重写了final方法,属性或下标脚本,在编译时会报错。

你可以通过在关键字class前添加final特性(finalclass)来将整个类标记为final的,这样的类是不可被继承的,否则会报编译错误。

finalclassCircle{finalvarradius=12.5vararea:String{return"矩形半径为\(radius)"}}classRectangle:Circle{varprint=7overridevararea:String{returnsuper.area+",但现在被重写为\(print)"}}letrect=Rectangle()rect.radius=25.0rect.print=3print("半径:\(rect.area)")classSquare:Rectangle{overridevarradius:Double{didSet{print=Int(radius/5.0)+1}}}letsq=Square()sq.radius=100.0print("半径:\(sq.area)")

由于以上实例使用了final关键字不允许重写,所以执行会报错:

error:varoverridesa'final'varoverridevararea:String{^note:overriddendeclarationisherevararea:String{^error:varoverridesa'final'varoverridevarradius:Double{^note:overriddendeclarationisherefinalvarradius=12.5^error:inheritancefromafinalclass'Circle'classRectangle:Circle{^

构造过程是为了使用某个类、结构体或枚举类型的实例而进行的准备过程。这个过程包含了为实例中的每个属性设置初始值和为其执行必要的准备和初始化任务。

Swift构造函数使用init()方法。

与Objective-C中的构造器不同,Swift的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完成正确的初始化。

类实例也可以通过定义析构器(deinitializer)在类实例释放之前执行清理内存的工作。

类和结构体在实例创建时,必须为所有存储型属性设置合适的初始值。

存储属性在构造器中赋值时,它们的值是被直接设置的,不会触发任何属性观测器。

存储属性在构造器中赋值流程:

构造器在创建某特定类型的新实例时调用。它的最简形式类似于一个不带任何参数的实例方法,以关键字init命名。

init(){//实例化后执行的代码}

以下结构体定义了一个不带参数的构造器init,并在里面将存储型属性length和breadth的值初始化为6和12:

structrectangle{varlength:Doublevarbreadth:Doubleinit(){length=6breadth=12}}vararea=rectangle()print("矩形面积为\(area.length*area.breadth)")

矩形面积为72.0

使用默认值能让你的构造器更简洁、更清晰,且能通过默认值自动推导出属性的类型。

structrectangle{//设置默认值varlength=6varbreadth=12}vararea=rectangle()print("矩形的面积为\(area.length*area.breadth)")

矩形面积为72

你可以在定义构造器init()时提供构造参数,如下所示:

structRectangle{varlength:Doublevarbreadth:Doublevararea:Doubleinit(fromLengthlength:Double,fromBreadthbreadth:Double){self.length=lengthself.breadth=breadtharea=length*breadth}init(fromLengleng:Double,fromBreadbread:Double){self.length=lengself.breadth=breadarea=leng*bread}}letar=Rectangle(fromLength:6,fromBreadth:12)print("面积为:\(ar.area)")letare=Rectangle(fromLeng:36,fromBread:12)print("面积为:\(are.area)")

面积为:72.0面积为:432.0

跟函数和方法参数相同,构造参数也存在一个在构造器内部使用的参数名字和一个在调用构造器时使用的外部参数名字。

然而,构造器并不像函数和方法那样在括号前有一个可辨别的名字。所以在调用构造器时,主要通过构造器中的参数名和类型来确定需要调用的构造器。

如果你在定义构造器时没有提供参数的外部名字,Swift会为每个构造器的参数自动生成一个跟内部名字相同的外部名。

structColor{letred,green,blue:Doubleinit(red:Double,green:Double,blue:Double){self.red=redself.green=greenself.blue=blue}init(white:Double){red=whitegreen=whiteblue=white}}//创建一个新的Color实例,通过三种颜色的外部参数名来传值,并调用构造器letmagenta=Color(red:1.0,green:0.0,blue:1.0)print("red值为:\(magenta.red)")print("green值为:\(magenta.green)")print("blue值为:\(magenta.blue)")//创建一个新的Color实例,通过三种颜色的外部参数名来传值,并调用构造器lethalfGray=Color(white:0.5)print("red值为:\(halfGray.red)")print("green值为:\(halfGray.green)")print("blue值为:\(halfGray.blue)")

red值为:1.0green值为:0.0blue值为:1.0red值为:0.5green值为:0.5blue值为:0.5

如果你不希望为构造器的某个参数提供外部名字,你可以使用下划线_来显示描述它的外部名。

structRectangle{varlength:Doubleinit(frombreadthbreadth:Double){length=breadth*10}init(frombrebre:Double){length=bre*30}//不提供外部名字init(_area:Double){length=area}}//调用不提供外部名字letrectarea=Rectangle(180.0)print("面积为:\(rectarea.length)")//调用不提供外部名字letrearea=Rectangle(370.0)print("面积为:\(rearea.length)")//调用不提供外部名字letrecarea=Rectangle(110.0)print("面积为:\(recarea.length)")

面积为:180.0面积为:370.0面积为:110.0

如果你定制的类型包含一个逻辑上允许取值为空的存储型属性,你都需要将它定义为可选类型optionaltype(可选属性类型)。

structRectangle{varlength:Doubleinit(frombreadthbreadth:Double){length=breadth*10}init(frombrebre:Double){length=bre*30}init(_area:Double){length=area}}letrectarea=Rectangle(180.0)print("面积为:\(rectarea.length)")letrearea=Rectangle(370.0)print("面积为:\(rearea.length)")letrecarea=Rectangle(110.0)print("面积为:\(recarea.length)")

面积为:Optional(180.0)面积为:Optional(370.0)面积为:Optional(110.0)

对某个类实例来说,它的常量属性只能在定义它的类的构造过程中修改;不能在子类中修改。

尽管length属性现在是常量,我们仍然可以在其类的构造器中设置它的值:

structRectangle{letlength:Doubleinit(frombreadthbreadth:Double){length=breadth*10}init(frombrebre:Double){length=bre*30}init(_area:Double){length=area}}letrectarea=Rectangle(180.0)print("面积为:\(rectarea.length)")letrearea=Rectangle(370.0)print("面积为:\(rearea.length)")letrecarea=Rectangle(110.0)print("面积为:\(recarea.length)")

默认构造器将简单的创建一个所有属性值都设置为默认值的实例:

以下实例中,ShoppingListItem类中的所有属性都有默认值,且它是没有父类的基类,它将自动获得一个可以为所有属性设置默认值的默认构造器

classShoppingListItem{varname:Stringvarquantity=1varpurchased=false}varitem=ShoppingListItem()print("名字为:\(item.name)")print("数理为:\(item.quantity)")print("是否付款:\(item.purchased)")

名字为:nil数理为:1是否付款:false

结构体的逐一成员构造器

如果结构体对所有存储型属性提供了默认值且自身没有提供定制的构造器,它们能自动获得一个逐一成员构造器。

我们在调用逐一成员构造器时,通过与成员属性名相同的参数名进行传值来完成对成员属性的初始赋值。

下面例子中定义了一个结构体Rectangle,它包含两个属性length和breadth。Swift可以根据这两个属性的初始赋值100.0、200.0自动推导出它们的类型Double。

structRectangle{varlength=100.0,breadth=200.0}letarea=Rectangle(length:24.0,breadth:32.0)print("矩形的面积:\(area.length)")print("矩形的面积:\(area.breadth)")

由于这两个存储型属性都有默认值,结构体Rectangle自动获得了一个逐一成员构造器init(width:height:)。你可以用它来为Rectangle创建新的实例。

名字为:nil矩形的面积:24.0矩形的面积:32.0

构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理,它能减少多个构造器间的代码重复。

以下实例中,Rect结构体调用了Size和Point的构造过程:

Size结构体初始值:(0.0,0.0)Rect结构体初始值:(0.0,0.0)Size结构体初始值:(5.0,5.0)Rect结构体初始值:(2.0,2.0)Size结构体初始值:(3.0,3.0)Rect结构体初始值:(2.5,2.5)

值类型

类类型

不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给本身提供的其它构造器。你可以使用self.init在自定义的构造器中引用其它的属于相同值类型的构造器。

它可以继承自其它类,这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化。

Swift提供了两种类型的类构造器来确保所有类实例中存储型属性都能获得初始值,它们分别是指定构造器和便利构造器。

指定构造器

便利构造器

类中最主要的构造器

类中比较次要的、辅助型的构造器

初始化类中提供的所有属性,并根据父类链往上调用父类的构造器来实现父类的初始化。

可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。你也可以定义便利构造器来创建一个特殊用途或特定输入的实例。

每一个类都必须拥有至少一个指定构造器

只在必要的时候为类提供便利构造器

Init(parameters){statements}

convenienceinit(parameters){statements}

classmainClass{varno1:Int//局部存储变量init(no1:Int){self.no1=no1//初始化}}classsubClass:mainClass{varno2:Int//新的子类存储变量init(no1:Int,no2:Int){self.no2=no2//初始化super.init(no1:no1)//初始化超类}}letres=mainClass(no1:10)letres2=subClass(no1:10,no2:20)print("res为:\(res.no1)")print("res2为:\(res2.no1)")print("res2为:\(res2.no2)")

res为:10res为:10res为:20

classmainClass{varno1:Int//局部存储变量init(no1:Int){self.no1=no1//初始化}}classsubClass:mainClass{varno2:Intinit(no1:Int,no2:Int){self.no2=no2super.init(no1:no1)}//便利方法只需要一个参数overrideconvenienceinit(no1:Int){self.init(no1:no1,no2:0)}}letres=mainClass(no1:20)letres2=subClass(no1:30,no2:50)print("res为:\(res.no1)")print("res2为:\(res2.no1)")print("res2为:\(res2.no2)")

res为:20res2为:30res2为:50

Swift中的子类不会默认继承父类的构造器。

父类的构造器仅在确定和安全的情况下被继承。

当你重写一个父类指定构造器时,你需要写override修饰符。

classSuperClass{varcorners=4vardescription:String{return"\(corners)边"}}letrectangle=SuperClass()print("矩形:\(rectangle.description)")classSubClass:SuperClass{overrideinit(){//重载构造器super.init()corners=5}}letsubClass=SubClass()print("五角型:\(subClass.description)")

矩形:4边五角型:5边

接下来的例子将在操作中展示指定构造器、便利构造器和自动构造器的继承。

它定义了包含两个个类MainClass、SubClass的类层次结构,并将演示它们的构造器是如何相互作用的。

classMainClass{varname:Stringinit(name:String){self.name=name}convenienceinit(){self.init(name:"[匿名]")}}letmain=MainClass(name:"Runoob")print("MainClass名字为:\(main.name)")letmain2=MainClass()print("没有对应名字:\(main2.name)")classSubClass:MainClass{varcount:Intinit(name:String,count:Int){self.count=countsuper.init(name:name)}overrideconvenienceinit(name:String){self.init(name:name,count:1)}}letsub=SubClass(name:"Runoob")print("MainClass名字为:\(sub.name)")letsub2=SubClass(name:"Runoob",count:3)print("count变量:\(sub2.count)")

MainClass名字为:Runoob没有对应名字:[匿名]MainClass名字为:Runoobcount变量:3

如果一个类,结构体或枚举类型的对象,在构造自身的过程中有可能失败,则为其定义一个可失败构造器。

变量初始化失败可能的原因有:

为了妥善处理这种构造过程中可能会失败的情况。

你可以在一个类,结构体或是枚举类型的定义中,添加一个或多个可失败构造器。其语法为在init关键字后面加添问号(init)。

下例中,定义了一个名为Animal的结构体,其中有一个名为species的,String类型的常量属性。

同时该结构体还定义了一个,带一个String类型参数species的,可失败构造器。这个可失败构造器,被用来检查传入的参数是否为一个空字符串,如果为空字符串,则该可失败构造器,构建对象失败,否则成功。

structAnimal{letspecies:Stringinit(species:String){ifspecies.isEmpty{returnnil}self.species=species}}//通过该可失败构造器来构建一个Animal的对象,并检查其构建过程是否成功//someCreature的类型是Animal而不是AnimalletsomeCreature=Animal(species:"长颈鹿")//打印"动物初始化为长颈鹿"ifletgiraffe=someCreature{print("动物初始化为\(giraffe.species)")}

动物初始化为长颈鹿

你可以通过构造一个带一个或多个参数的可失败构造器来获取枚举类型中特定的枚举成员。

下例中,定义了一个名为TemperatureUnit的枚举类型。其中包含了三个可能的枚举成员(Kelvin,Celsius,和Fahrenheit)和一个被用来找到Character值所对应的枚举成员的可失败构造器:

enumTemperatureUnit{//开尔文,摄氏,华氏caseKelvin,Celsius,Fahrenheitinit(symbol:Character){switchsymbol{case"K":self=.Kelvincase"C":self=.Celsiuscase"F":self=.Fahrenheitdefault:returnnil}}}letfahrenheitUnit=TemperatureUnit(symbol:"F")iffahrenheitUnit!=nil{print("这是一个已定义的温度单位,所以初始化成功。")}letunknownUnit=TemperatureUnit(symbol:"X")ifunknownUnit==nil{print("这不是一个已定义的温度单位,所以初始化失败。")}

这是一个已定义的温度单位,所以初始化成功。这不是一个已定义的温度单位,所以初始化失败。

值类型(如结构体或枚举类型)的可失败构造器,对何时何地触发构造失败这个行为没有任何的限制。

但是,类的可失败构造器只能在所有的类属性被初始化后和所有类之间的构造器之间的代理调用发生完后触发失败行为。

下例子中,定义了一个名为StudRecord的类,因为studname属性是一个常量,所以一旦StudRecord类构造成功,studname属性肯定有一个非nil的值。

classStudRecord{letstudname:String!init(studname:String){self.studname=studnameifstudname.isEmpty{returnnil}}}ifletstname=StudRecord(studname:"失败构造器"){print("模块为\(stname.studname)")}

模块为失败构造器

就如同其它构造器一样,你也可以用子类的可失败构造器覆盖基类的可失败构造器。

者你也可以用子类的非可失败构造器覆盖一个基类的可失败构造器。

你可以用一个非可失败构造器覆盖一个可失败构造器,但反过来却行不通。

一个非可失败的构造器永远也不能代理调用一个可失败构造器。

以下实例描述了可失败与非可失败构造器:

classPlanet{varname:Stringinit(name:String){self.name=name}convenienceinit(){self.init(name:"[NoPlanets]")}}letplName=Planet(name:"Mercury")print("行星的名字是:\(plName.name)")letnoplName=Planet()print("没有这个名字的行星:\(noplName.name)")classplanets:Planet{varcount:Intinit(name:String,count:Int){self.count=countsuper.init(name:name)}overrideconvenienceinit(name:String){self.init(name:name,count:1)}}

行星的名字是:Mercury没有这个名字的行星:[NoPlanets]

通常来说我们通过在init关键字后添加问号的方式(init)来定义一个可失败构造器,但你也可以使用通过在init后面添加惊叹号的方式来定义一个可失败构造器(init!)。实例如下:

structStudRecord{letstname:Stringinit!(stname:String){ifstname.isEmpty{returnnil}self.stname=stname}}letstmark=StudRecord(stname:"Runoob")ifletname=stmark{print("指定了学生名")}letblankname=StudRecord(stname:"")ifblankname==nil{print("学生名为空")}

指定了学生名学生名为空

在一个类的实例被释放之前,析构函数被立即调用。用关键字deinit来标示析构函数,类似于初始化函数用init来标示。析构函数只适用于类类型。

Swift会自动释放不再需要的实例以释放资源。

Swift通过自动引用计数(ARC)处理实例的内存管理。

通常当你的实例被释放时不需要手动地去清理。但是,当使用自己的资源时,你可能需要进行一些额外的清理。

例如,如果创建了一个自定义的类来打开一个文件,并写入一些数据,你可能需要在类实例被释放之前关闭该文件。

在类的定义中,每个类最多只能有一个析构函数。析构函数不带任何参数,在写法上不带括号:

deinit{//执行析构过程}

varcounter=0;//引用计数器classBaseClass{init(){counter++;}deinit{counter--;}}varshow:BaseClass=BaseClass()print(counter)show=nilprint(counter)

当show=nil语句执行后,计算器减去1,show占用的内存就会释放。

varcounter=0;//引用计数器classBaseClass{init(){counter++;}deinit{counter--;}}varshow:BaseClass=BaseClass()print(counter)print(counter)

11

可选链(OptionalChaining)是一种是一种可以请求和调用属性、方法和子脚本的过程,用于请求或调用的目标可能为nil。

可选链返回两个值:

多次请求或调用可以被链接成一个链,如果任意一个节点为nil将导致整条链失效。

通过在属性、方法、或下标脚本的可选值后面放一个问号(),即可定义一个可选链。

可选链''

感叹号(!)强制展开方法,属性,下标脚本可选链

放置于可选值后来调用方法,属性,下标脚本

!放置于可选值后来调用方法,属性,下标脚本来强制展开值

当可选为nil输出比较友好的错误信息

当可选为nil时强制展开执行错误

classPerson{varresidence:Residence}classResidence{varnumberOfRooms=1}letjohn=Person()//将导致运行时错误letroomCount=john.residence!.numberOfRooms

fatalerror:unexpectedlyfoundnilwhileunwrappinganOptionalvalue

classPerson{varresidence:Residence}classResidence{varnumberOfRooms=1}letjohn=Person()//链接可选residence属性,如果residence存在则取回numberOfRooms的值ifletroomCount=john.residence.numberOfRooms{print("John的房间号为\(roomCount)。")}else{print("不能查看房间号")}

不能查看房间号

因为这种尝试获得numberOfRooms的操作有可能失败,可选链会返回Int类型值,或者称作"可选Int"。当residence是空的时候(上例),选择Int将会为空,因此会出现无法访问numberOfRooms的情况。

要注意的是,即使numberOfRooms是非可选Int(Int)时这一点也成立。只要是通过可选链的请求就意味着最后numberOfRooms总是返回一个Int而不是Int。

你可以使用可选链来多层调用属性,方法,和下标脚本。这让你可以利用它们之间的复杂模型来获取更底层的属性,并检查是否可以成功获取此类底层属性。

定义了四个模型类,其中包括多层可选链:

classPerson{varresidence:Residence}//定义了一个变量rooms,它被初始化为一个Room[]类型的空数组classResidence{varrooms=[Room]()varnumberOfRooms:Int{returnrooms.count}subscript(i:Int)->Room{returnrooms[i]}funcprintNumberOfRooms(){print("房间号为\(numberOfRooms)")}varaddress:Address}//Room定义一个name属性和一个设定room名的初始化器classRoom{letname:Stringinit(name:String){self.name=name}}//模型中的最终类叫做AddressclassAddress{varbuildingName:StringvarbuildingNumber:Stringvarstreet:StringfuncbuildingIdentifier()->String{if(buildingName!=nil){returnbuildingName}elseif(buildingNumber!=nil){returnbuildingNumber}else{returnnil}}}

你可以使用可选链的来调用可选值的方法并检查方法调用是否成功。即使这个方法没有返回值,你依然可以使用可选链来达成这一目的。

classPerson{varresidence:Residence}//定义了一个变量rooms,它被初始化为一个Room[]类型的空数组classResidence{varrooms=[Room]()varnumberOfRooms:Int{returnrooms.count}subscript(i:Int)->Room{returnrooms[i]}funcprintNumberOfRooms(){print("房间号为\(numberOfRooms)")}varaddress:Address}//Room定义一个name属性和一个设定room名的初始化器classRoom{letname:Stringinit(name:String){self.name=name}}//模型中的最终类叫做AddressclassAddress{varbuildingName:StringvarbuildingNumber:Stringvarstreet:StringfuncbuildingIdentifier()->String{if(buildingName!=nil){returnbuildingName}elseif(buildingNumber!=nil){returnbuildingNumber}else{returnnil}}}letjohn=Person()if((john.residence.printNumberOfRooms())!=nil){print("输出房间号")}else{print("无法输出房间号")}

无法输出房间号

使用if语句来检查是否能成功调用printNumberOfRooms方法:如果方法通过可选链调用成功,printNumberOfRooms的隐式返回值将会是Void,如果没有成功,将返回nil。

你可以使用可选链来尝试从下标脚本获取值并检查下标脚本的调用是否成功,然而,你不能通过可选链来设置下标脚本。

classPerson{varresidence:Residence}//定义了一个变量rooms,它被初始化为一个Room[]类型的空数组classResidence{varrooms=[Room]()varnumberOfRooms:Int{returnrooms.count}subscript(i:Int)->Room{returnrooms[i]}funcprintNumberOfRooms(){print("房间号为\(numberOfRooms)")}varaddress:Address}//Room定义一个name属性和一个设定room名的初始化器classRoom{letname:Stringinit(name:String){self.name=name}}//模型中的最终类叫做AddressclassAddress{varbuildingName:StringvarbuildingNumber:Stringvarstreet:StringfuncbuildingIdentifier()->String{if(buildingName!=nil){returnbuildingName}elseif(buildingNumber!=nil){returnbuildingNumber}else{returnnil}}}letjohn=Person()ifletfirstRoomName=john.residence[0].name{print("第一个房间名\(firstRoomName).")}else{print("无法检索到房间")}

无法检索到房间

在下标脚本调用中可选链的问号直接跟在circname.print的后面,在下标脚本括号的前面,因为circname.print是可选链试图获得的可选值。

实例中创建一个Residence实例给john.residence,且在他的rooms数组中有一个或多个Room实例,那么你可以使用可选链通过Residence下标脚本来获取在rooms数组中的实例了:

classPerson{varresidence:Residence}//定义了一个变量rooms,它被初始化为一个Room[]类型的空数组classResidence{varrooms=[Room]()varnumberOfRooms:Int{returnrooms.count}subscript(i:Int)->Room{returnrooms[i]}funcprintNumberOfRooms(){print("房间号为\(numberOfRooms)")}varaddress:Address}//Room定义一个name属性和一个设定room名的初始化器classRoom{letname:Stringinit(name:String){self.name=name}}//模型中的最终类叫做AddressclassAddress{varbuildingName:StringvarbuildingNumber:Stringvarstreet:StringfuncbuildingIdentifier()->String{if(buildingName!=nil){returnbuildingName}elseif(buildingNumber!=nil){returnbuildingNumber}else{returnnil}}}letjohn=Person()letjohnsHouse=Residence()johnsHouse.rooms.append(Room(name:"客厅"))johnsHouse.rooms.append(Room(name:"厨房"))john.residence=johnsHouseifletfirstRoomName=john.residence[0].name{print("第一个房间名为\(firstRoomName)")}else{print("无法检索到房间")}

第一个房间名为客厅

通过可选链接调用,我们可以用下标来对可选值进行读取或写入,并且判断下标调用是否成功。

如果下标返回可空类型值,比如Swift中Dictionary的key下标。可以在下标的闭合括号后面放一个问号来链接下标的可空返回值:

vartestScores=["Dave":[86,82,84],"Bev":[79,94,81]]testScores["Dave"][0]=91testScores["Bev"][0]++testScores["Brian"][0]=72//the"Dave"arrayisnow[91,82,84]andthe"Bev"arrayisnow[80,94,81]

上面的例子中定义了一个testScores数组,包含了两个键值对,把String类型的key映射到一个整形数组。

这个例子用可选链接调用把"Dave"数组中第一个元素设为91,把"Bev"数组的第一个元素+1,然后尝试把"Brian"数组中的第一个元素设为72。

前两个调用是成功的,因为这两个key存在。但是key"Brian"在字典中不存在,所以第三个调用失败。

你可以将多层可选链连接在一起,可以掘取模型内更下层的属性方法和下标脚本。然而多层可选链不能再添加比已经返回的可选值更多的层。

如果你试图通过可选链获得Int值,不论使用了多少层链接返回的总是Int。相似的,如果你试图通过可选链获得Int值,不论使用了多少层链接返回的总是Int。

下面的例子试图获取john的residence属性里的address的street属性。这里使用了两层可选链来联系residence和address属性,它们两者都是可选类型:

classPerson{varresidence:Residence}//定义了一个变量rooms,它被初始化为一个Room[]类型的空数组classResidence{varrooms=[Room]()varnumberOfRooms:Int{returnrooms.count}subscript(i:Int)->Room{returnrooms[i]}funcprintNumberOfRooms(){print("房间号为\(numberOfRooms)")}varaddress:Address}//Room定义一个name属性和一个设定room名的初始化器classRoom{letname:Stringinit(name:String){self.name=name}}//模型中的最终类叫做AddressclassAddress{varbuildingName:StringvarbuildingNumber:Stringvarstreet:StringfuncbuildingIdentifier()->String{if(buildingName!=nil){returnbuildingName}elseif(buildingNumber!=nil){returnbuildingNumber}else{returnnil}}}letjohn=Person()ifletjohnsStreet=john.residence.address.street{print("John的地址为\(johnsStreet).")}else{print("不能检索地址")}

不能检索地址

如果你为Address设定一个实例来作为john.residence.address的值,并为address的street属性设定一个实际值,你可以通过多层可选链来得到这个属性值。

classPerson{varresidence:Residence}classResidence{varrooms=[Room]()varnumberOfRooms:Int{returnrooms.count}subscript(i:Int)->Room{get{returnrooms[i]}set{rooms[i]=newValue}}funcprintNumberOfRooms(){print("房间号为\(numberOfRooms)")}varaddress:Address}classRoom{letname:Stringinit(name:String){self.name=name}}classAddress{varbuildingName:StringvarbuildingNumber:Stringvarstreet:StringfuncbuildingIdentifier()->String{if(buildingName!=nil){returnbuildingName}elseif(buildingNumber!=nil){returnbuildingNumber}else{returnnil}}}letjohn=Person()john.residence[0]=Room(name:"浴室")letjohnsHouse=Residence()johnsHouse.rooms.append(Room(name:"客厅"))johnsHouse.rooms.append(Room(name:"厨房"))john.residence=johnsHouseifletfirstRoomName=john.residence[0].name{print("第一个房间是\(firstRoomName)")}else{print("无法检索房间")}

以上实例输出结果为:

第一个房间是客厅

我们还可以通过可选链接来调用返回可空值的方法,并且可以继续对可选值进行链接。

classPerson{varresidence:Residence}//定义了一个变量rooms,它被初始化为一个Room[]类型的空数组classResidence{varrooms=[Room]()varnumberOfRooms:Int{returnrooms.count}subscript(i:Int)->Room{returnrooms[i]}funcprintNumberOfRooms(){print("房间号为\(numberOfRooms)")}varaddress:Address}//Room定义一个name属性和一个设定room名的初始化器classRoom{letname:Stringinit(name:String){self.name=name}}//模型中的最终类叫做AddressclassAddress{varbuildingName:StringvarbuildingNumber:Stringvarstreet:StringfuncbuildingIdentifier()->String{if(buildingName!=nil){returnbuildingName}elseif(buildingNumber!=nil){returnbuildingNumber}else{returnnil}}}letjohn=Person()ifjohn.residence.printNumberOfRooms()!=nil{print("指定了房间号)")}else{print("未指定房间号")}

未指定房间号

Swift使用自动引用计数(ARC)这一机制来跟踪和管理应用程序的内存

通常情况下我们不需要去手动释放内存,因为ARC会在类的实例不再被使用时,自动释放其占用的内存。

但在有些时候我们还是需要在代码中实现内存管理。

classPerson{letname:Stringinit(name:String){self.name=nameprint("\(name)开始初始化")}deinit{print("\(name)被析构")}}//值会被自动初始化为nil,目前还不会引用到Person类的实例varreference1:Personvarreference2:Personvarreference3:Person//创建Person类的新实例reference1=Person(name:"Runoob")//赋值给其他两个变量,该实例又会多出两个强引用reference2=reference1reference3=reference1//断开第一个强引用reference1=nil//断开第二个强引用reference2=nil//断开第三个强引用,并调用析构函数reference3=nil

Runoob开始初始化Runoob被析构

在上面的例子中,ARC会跟踪你所新创建的Person实例的引用数量,并且会在Person实例不再被需要时销毁它。

然而,我们可能会写出这样的代码,一个类永远不会有0个强引用。这种情况发生在两个类实例互相保持对方的强引用,并让对方不被销毁。这就是所谓的循环强引用。

下面展示了一个不经意产生循环强引用的例子。例子定义了两个类:Person和Apartment,用来建模公寓和它其中的居民:

classPerson{letname:Stringinit(name:String){self.name=name}varapartment:Apartmentdeinit{print("\(name)被析构")}}classApartment{letnumber:Intinit(number:Int){self.number=number}vartenant:Persondeinit{print("Apartment#\(number)被析构")}}//两个变量都被初始化为nilvarrunoob:Personvarnumber73:Apartment//赋值runoob=Person(name:"Runoob")number73=Apartment(number:73)//意感叹号是用来展开和访问可选变量runoob和number73中的实例//循环强引用被创建runoob!.apartment=number73number73!.tenant=runoob//断开runoob和number73变量所持有的强引用时,引用计数并不会降为0,实例也不会被ARC销毁//注意,当你把这两个变量设为nil时,没有任何一个析构函数被调用。//强引用循环阻止了Person和Apartment类实例的销毁,并在你的应用程序中造成了内存泄漏runoob=nilnumber73=nil

Swift提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题:

弱引用和无主引用允许循环引用中的一个实例引用另外一个实例而不保持强引用。这样实例能够互相引用而不产生循环强引用。

对于生命周期中会变为nil的实例使用弱引用。相反的,对于初始化赋值后再也不会被赋值为nil的实例,使用无主引用。

classModule{letname:Stringinit(name:String){self.name=name}varsub:SubModuledeinit{print("\(name)主模块")}}classSubModule{letnumber:Intinit(number:Int){self.number=number}weakvartopic:Moduledeinit{print("子模块topic数为\(number)")}}vartoc:Modulevarlist:SubModuletoc=Module(name:"ARC")list=SubModule(number:4)toc!.sub=listlist!.topic=toctoc=nillist=nil

ARC主模块子模块topic数为4

classStudent{letname:Stringvarsection:Marksinit(name:String){self.name=name}deinit{print("\(name)")}}classMarks{letmarks:Intunownedletstname:Studentinit(marks:Int,stname:Student){self.marks=marksself.stname=stname}deinit{print("学生的分数为\(marks)")}}varmodule:Studentmodule=Student(name:"ARC")module!.section=Marks(marks:98,stname:module!)module=nil

ARC学生的分数为98

循环强引用还会发生在当你将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了实例。这个闭包体中可能访问了实例的某个属性,例如self.someProperty,或者闭包中调用了实例的某个方法,例如self.someMethod。这两种情况都导致了闭包"捕获"self,从而产生了循环强引用。

下面的例子为你展示了当一个闭包引用了self后是如何产生一个循环强引用的。例子中定义了一个叫HTMLElement的类,用一种简单的模型表示HTML中的一个单独的元素:

classHTMLElement{letname:Stringlettext:StringlazyvarasHTML:()->String={iflettext=self.text{return"<\(self.name)>\(text)"}else{return"<\(self.name)/>"}}init(name:String,text:String=nil){self.name=nameself.text=text}deinit{print("\(name)isbeingdeinitialized")}}//创建实例并打印信息varparagraph:HTMLElement=HTMLElement(name:"p",text:"hello,world")print(paragraph!.asHTML())

HTMLElement类产生了类实例和asHTML默认值的闭包之间的循环强引用。

实例的asHTML属性持有闭包的强引用。但是,闭包在其闭包体内使用了self(引用了self.name和self.text),因此闭包捕获了self,这意味着闭包又反过来持有了HTMLElement实例的强引用。这样两个对象就产生了循环强引用。

解决闭包引起的循环强引用:在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用。

当闭包和捕获的实例总是互相引用时并且总是同时销毁时,将闭包内的捕获定义为无主引用。

相反的,当捕获引用有时可能会是nil时,将闭包内的捕获定义为弱引用。

如果捕获的引用绝对不会置为nil,应该用无主引用,而不是弱引用。

前面的HTMLElement例子中,无主引用是正确的解决循环强引用的方法。这样编写HTMLElement类来避免循环强引用:

classHTMLElement{letname:Stringlettext:StringlazyvarasHTML:()->String={[unownedself]iniflettext=self.text{return"<\(self.name)>\(text)"}else{return"<\(self.name)/>"}}init(name:String,text:String=nil){self.name=nameself.text=text}deinit{print("\(name)被析构")}}//创建并打印HTMLElement实例varparagraph:HTMLElement=HTMLElement(name:"p",text:"hello,world")print(paragraph!.asHTML())//HTMLElement实例将会被销毁,并能看到它的析构函数打印出的消息paragraph=nil

hello,world

p被析构

Swift语言类型转换可以判断实例的类型。也可以用于检测实例类型是否属于其父类或者子类的实例。

Swift中类型转换使用is和as操作符实现,is用于检测值的类型,as用于转换类型。

类型转换也可以用来检查一个类是否实现了某个协议。

类型转换用于检测实例类型是否属于特定的实例类型。

你可以将它用在类和子类的层次结构上,检查特定类实例的类型并且转换这个类实例的类型成为这个层次结构中的其他类型。

classSubjects{varphysics:Stringinit(physics:String){self.physics=physics}}classChemistry:Subjects{varequations:Stringinit(physics:String,equations:String){self.equations=equationssuper.init(physics:physics)}}classMaths:Subjects{varformulae:Stringinit(physics:String,formulae:String){self.formulae=formulaesuper.init(physics:physics)}}letsa=[Chemistry(physics:"固体物理",equations:"赫兹"),Maths(physics:"流体动力学",formulae:"千兆赫")]letsamplechem=Chemistry(physics:"固体物理",equations:"赫兹")print("实例物理学是:\(samplechem.physics)")print("实例方程式:\(samplechem.equations)")letsamplemaths=Maths(physics:"流体动力学",formulae:"千兆赫")print("实例物理学是:\(samplemaths.physics)")print("实例公式是:\(samplemaths.formulae)")

实例物理学是:固体物理实例方程式:赫兹实例物理学是:流体动力学实例公式是:千兆赫

类型检查使用is关键字。

操作符is来检查一个实例是否属于特定子类型。若实例属于那个子类型,类型检查操作符返回true,否则返回false。

实例物理学是:固体物理实例方程式:赫兹实例物理学是:流体动力学实例公式是:千兆赫化学科目包含2个主题,数学包含3个主题

向下转型,用类型转换操作符(as或as!)

当你不确定向下转型可以成功时,用类型转换的条件形式(as)。条件形式的类型转换总是返回一个可选值(optionalvalue),并且若下转是不可能的,可选值将是nil。

只有你可以确定向下转型一定会成功时,才使用强制形式(as!)。当你试图向下转型为一个不正确的类型时,强制形式的类型转换会触发一个运行时错误。

实例物理学是:固体物理实例方程式:赫兹实例物理学是:流体动力学实例公式是:千兆赫化学主题是:'固体物理',赫兹数学主题是:'流体动力学',千兆赫化学主题是:'热物理学',分贝数学主题是:'天体物理学',兆赫数学主题是:'微分方程',余弦级数

Swift为不确定类型提供了两种特殊类型别名:

只有当你明确的需要它的行为和功能时才使用Any和AnyObject。在你的代码里使用你期望的明确的类型总是更好的。

实例物理学是:固体物理实例方程式:赫兹实例物理学是:流体动力学实例公式是:千兆赫化学主题是:'固体物理',赫兹数学主题是:'流体动力学',千兆赫化学主题是:'热物理学',分贝数学主题是:'天体物理学',兆赫数学主题是:'微分方程',余弦级数整型值为12Pi值为3.14159Any实例主题'固体物理',兆赫

在一个switch语句的case中使用强制形式的类型转换操作符(as,而不是as)来检查和转换到一个明确的类型。

扩展就是向一个已有的类、结构体或枚举类型添加新功能。

扩展可以对一个类型添加新的功能,但是不能重写已有的功能。

Swift中的扩展可以:

extensionSomeType{//加到SomeType的新功能写到这里}

一个扩展可以扩展一个已有类型,使其能够适配一个或多个协议,语法格式如下:

extensionSomeType:SomeProtocol,AnotherProctocol{//协议实现写到这里}

扩展可以向已有类型添加计算型实例属性和计算型类型属性。

下面的例子向Int类型添加了5个计算型实例属性并扩展其功能:

extensionInt{varadd:Int{returnself+100}varsub:Int{returnself-10}varmul:Int{returnself*10}vardiv:Int{returnself/5}}letaddition=3.addprint("加法运算后的值:\(addition)")letsubtraction=120.subprint("减法运算后的值:\(subtraction)")letmultiplication=39.mulprint("乘法运算后的值:\(multiplication)")letdivision=55.divprint("除法运算后的值:\(division)")letmix=30.add+34.subprint("混合运算结果:\(mix)")

加法运算后的值:103减法运算后的值:110乘法运算后的值:390除法运算后的值:11混合运算结果:154

扩展可以向已有类型添加新的构造器。

这可以让你扩展其它类型,将你自己的定制类型作为构造器参数,或者提供该类型的原始实现中没有包含的额外初始化选项。

扩展可以向类中添加新的便利构造器init(),但是它们不能向类中添加新的指定构造器或析构函数deinit()。

structsum{varnum1=100,num2=200}structdiff{varno1=200,no2=100}structmult{vara=sum()varb=diff()}letcalc=mult()print("mult模块内\(calc.a.num1,calc.a.num2)")print("mult模块内\(calc.b.no1,calc.b.no2)")letmemcalc=mult(a:sum(num1:300,num2:500),b:diff(no1:300,no2:100))print("mult模块内\(memcalc.a.num1,memcalc.a.num2)")print("mult模块内\(memcalc.b.no1,memcalc.b.no2)")extensionmult{init(x:sum,y:diff){_=x.num1+x.num2_=y.no1+y.no2}}leta=sum(num1:100,num2:200)print("Sum模块内:\(a.num1,a.num2)")letb=diff(no1:200,no2:100)print("Diff模块内:\(b.no1,b.no2)")

mult模块内(100,200)mult模块内(200,100)mult模块内(300,500)mult模块内(300,100)Sum模块内:(100,200)Diff模块内:(200,100)

扩展可以向已有类型添加新的实例方法和类型方法。

下面的例子向Int类型添加一个名为topics的新实例方法:

extensionInt{functopics(summation:()->()){for_in0..

扩展模块内扩展模块内扩展模块内扩展模块内内型转换模块内内型转换模块内内型转换模块内

这个topics方法使用了一个()->()类型的单参数,表明函数没有参数而且没有返回值。

定义该扩展之后,你就可以对任意整数调用topics方法,实现的功能则是多次执行某任务:

通过扩展添加的实例方法也可以修改该实例本身。

结构体和枚举类型中修改self或其属性的方法必须将该实例方法标注为mutating,正如来自原始实现的修改方法一样。

下面的例子向Swift的Double类型添加了一个新的名为square的修改方法,来实现一个原始值的平方计算:

extensionDouble{mutatingfuncsquare(){letpi=3.1415self=pi*self*self}}varTrial1=3.3Trial1.square()print("圆的面积为:\(Trial1)")varTrial2=5.8Trial2.square()print("圆的面积为:\(Trial2)")varTrial3=120.3Trial3.square()print("圆的面积为:\(Trial3)")

圆的面积为:34.210935圆的面积为:105.68006圆的面积为:45464.070735

扩展可以向一个已有类型添加新下标。

以下例子向Swift内建类型Int添加了一个整型下标。该下标[n]返回十进制数字

extensionInt{subscript(varmulttable:Int)->Int{varno1=1whilemulttable>0{no1*=10--multtable}return(self/no1)%10}}print(12[0])print(7869[1])print(786543[2])

265

扩展可以向已有的类、结构体和枚举添加新的嵌套类型:

extensionInt{enumcalc{caseaddcasesubcasemultcasedivcaseanything}varprint:calc{switchself{case0:return.addcase1:return.subcase2:return.multcase3:return.divdefault:return.anything}}}funcresult(numb:[Int]){foriinnumb{switchi.print{case.add:print("10")case.sub:print("20")case.mult:print("30")case.div:print("40")default:print("50")}}}result([0,1,2,3,4,7])

102030405050

协议规定了用来实现某一特定功能所必需的方法和属性。

任意能够满足协议要求的类型被称为遵循(conform)这个协议。

类,结构体或枚举类型都可以遵循协议,并提供具体实现来完成协议定义的方法和功能。

协议的语法格式如下:

protocolSomeProtocol{//协议内容}

要使类遵循某个协议,需要在类型名称后加上协议名称,中间以冒号:分隔,作为类型定义的一部分。遵循多个协议时,各协议之间用逗号,分隔。

structSomeStructure:FirstProtocol,AnotherProtocol{//结构体内容}

如果类在遵循协议的同时拥有父类,应该将父类名放在协议名之前,以逗号分隔。

classSomeClass:SomeSuperClass,FirstProtocol,AnotherProtocol{//类的内容}

协议用于指定特定的实例属性或类属性,而不用指定是存储型属性或计算型属性。此外还必须指明是只读的还是可读可写的。

protocolclassa{varmarks:Int{getset}varresult:Bool{get}funcattendance()->Stringfuncmarkssecured()->String}protocolclassb:classa{varpresent:Bool{getset}varsubject:String{getset}varstname:String{getset}}classclassc:classb{varmarks=96letresult=truevarpresent=falsevarsubject="Swift协议"varstname="Protocols"funcattendance()->String{return"The\(stname)hassecured99%attendance"}funcmarkssecured()->String{return"\(stname)hasscored\(marks)"}}letstuddet=classc()studdet.stname="Swift"studdet.marks=98studdet.markssecured()print(studdet.marks)print(studdet.result)print(studdet.present)print(studdet.subject)print(studdet.stname)

98truefalseSwift协议Swift

有时需要在方法中改变它的实例。

例如,值类型(结构体,枚举)的实例方法中,将mutating关键字作为函数的前缀,写在func之前,表示可以在该方法中修改它所属的实例及其实例属性的值。

protocoldaysofaweek{mutatingfuncshow()}enumdays:daysofaweek{casesun,mon,tue,wed,thurs,fri,satmutatingfuncshow(){switchself{casesun:self=sunprint("Sunday")casemon:self=monprint("Monday")casetue:self=tueprint("Tuesday")casewed:self=wedprint("Wednesday")casemon:self=thursprint("Thursday")casetue:self=friprint("Friday")casesat:self=satprint("Saturday")default:print("NOSuchDay")}}}varres=days.wedres.show()

Wednesday

协议可以要求它的遵循者实现指定的构造器。

protocolSomeProtocol{init(someParameter:Int)}

protocoltcpprotocol{init(aprot:Int)}

你可以在遵循该协议的类中实现构造器,并指定其为类的指定构造器或者便利构造器。在这两种情况下,你都必须给构造器实现标上"required"修饰符:

classSomeClass:SomeProtocol{requiredinit(someParameter:Int){//构造器实现}}protocoltcpprotocol{init(aprot:Int)}classtcpClass:tcpprotocol{requiredinit(aprot:Int){}}

使用required修饰符可以保证:所有的遵循该协议的子类,同样能为构造器规定提供一个显式的实现或继承实现。

如果一个子类重写了父类的指定构造器,并且该构造器遵循了某个协议的规定,那么该构造器的实现需要被同时标示required和override修饰符:

protocoltcpprotocol{init(no1:Int)}classmainClass{varno1:Int//局部变量init(no1:Int){self.no1=no1//初始化}}classsubClass:mainClass,tcpprotocol{varno2:Intinit(no1:Int,no2:Int){self.no2=no2super.init(no1:no1)}//因为遵循协议,需要加上"required";因为继承自父类,需要加上"override"requiredoverrideconvenienceinit(no1:Int){self.init(no1:no1,no2:0)}}letres=mainClass(no1:20)letshow=subClass(no1:30,no2:50)print("resis:\(res.no1)")print("resis:\(show.no1)")print("resis:\(show.no2)")

resis:20resis:30resis:50

尽管协议本身并不实现任何功能,但是协议可以被当做类型来使用。

协议可以像其他普通类型一样使用,使用场景:

protocolGenerator{typealiasmembersfuncnext()->members}varitems=[10,20,30].generate()whileletx=items.next(){print(x)}forlistsin[1,2,3].map({iini*5}){print(lists)}print([100,200,300])print([1,2,3].map({iini*10}))

10203051015[100,200,300][10,20,30]

我们可以可以通过扩展来扩充已存在类型(类,结构体,枚举等)。

扩展可以为已存在的类型添加属性,方法,下标脚本,协议等成员。

protocolAgeClasificationProtocol{varage:Int{get}funcagetype()->String}classPerson{letfirstname:Stringletlastname:Stringvarage:Intinit(firstname:String,lastname:String){self.firstname=firstnameself.lastname=lastnameself.age=10}}extensionPerson:AgeClasificationProtocol{funcfullname()->String{varc:Stringc=firstname+""+lastnamereturnc}funcagetype()->String{switchage{case0...2:return"Baby"case2...12:return"Child"case13...19:return"Teenager"caseletxwherex>65:return"Elderly"default:return"Normal"}}}

协议能够继承一个或多个其他协议,可以在继承的协议基础上增加新的内容要求。

protocolInheritingProtocol:SomeProtocol,AnotherProtocol{//协议定义}

protocolClassa{varno1:Int{getset}funccalc(sum:Int)}protocolResult{funcprint(target:Classa)}classStudent2:Result{funcprint(target:Classa){target.calc(1)}}classClassb:Result{funcprint(target:Classa){target.calc(5)}}classStudent:Classa{varno1:Int=10funccalc(sum:Int){no1-=sumprint("学生尝试\(sum)次通过")ifno1<=0{print("学生缺席考试")}}}classPlayer{varstmark:Result!init(stmark:Result){self.stmark=stmark}funcprint(target:Classa){stmark.print(target)}}varmarks=Player(stmark:Student2())varmarksec=Student()marks.print(marksec)marks.print(marksec)marks.print(marksec)marks.stmark=Classb()marks.print(marksec)marks.print(marksec)marks.print(marksec)

学生尝试1次通过学生尝试1次通过学生尝试1次通过学生尝试5次通过学生尝试5次通过学生缺席考试学生尝试5次通过学生缺席考试

你可以在协议的继承列表中,通过添加class关键字,限制协议只能适配到类(class)类型。

该class关键字必须是第一个出现在协议的继承列表中,其后,才是其他继承协议。格式如下:

protocolSomeClassOnlyProtocol:class,SomeInheritedProtocol{//协议定义}

protocolTcpProtocol{init(no1:Int)}classMainClass{varno1:Int//局部变量init(no1:Int){self.no1=no1//初始化}}classSubClass:MainClass,TcpProtocol{varno2:Intinit(no1:Int,no2:Int){self.no2=no2super.init(no1:no1)}//因为遵循协议,需要加上"required";因为继承自父类,需要加上"override"requiredoverrideconvenienceinit(no1:Int){self.init(no1:no1,no2:0)}}letres=MainClass(no1:20)letshow=SubClass(no1:30,no2:50)print("resis:\(res.no1)")print("resis:\(show.no1)")print("resis:\(show.no2)")

Swift支持合成多个协议,这在我们需要同时遵循多个协议时非常有用。

语法格式如下:

protocol

protocolStname{varname:String{get}}protocolStage{varage:Int{get}}structPerson:Stname,Stage{varname:Stringvarage:Int}funcshow(celebrator:protocol){print("\(celebrator.name)is\(celebrator.age)yearsold")}letstudname=Person(name:"Priya",age:21)print(studname)letstud=Person(name:"Rehan",age:29)print(stud)letstudent=Person(name:"Roshan",age:19)print(student)

Person(name:"Priya",age:21)Person(name:"Rehan",age:29)Person(name:"Roshan",age:19)

你可以使用is和as操作符来检查是否遵循某一协议或强制转化为某一类型。

下面的例子定义了一个HasArea的协议,要求有一个Double类型可读的area:

protocolHasArea{vararea:Double{get}}//定义了Circle类,都遵循了HasArea协议classCircle:HasArea{letpi=3.1415927varradius:Doublevararea:Double{returnpi*radius*radius}init(radius:Double){self.radius=radius}}//定义了Country类,都遵循了HasArea协议classCountry:HasArea{vararea:Doubleinit(area:Double){self.area=area}}//Animal是一个没有实现HasArea协议的类classAnimal{varlegs:Intinit(legs:Int){self.legs=legs}}letobjects:[AnyObject]=[Circle(radius:2.0),Country(area:243_610),Animal(legs:4)]forobjectinobjects{//对迭代出的每一个元素进行检查,看它是否遵循了HasArea协议ifletobjectWithArea=objectasHasArea{print("面积为\(objectWithArea.area)")}else{print("没有面积")}}

面积为12.5663708面积为243610.0没有面积

Swift提供了泛型让你写出灵活且可重用的函数和类型。

Swift标准库是通过泛型代码构建出来的。

Swift的数组和字典类型都是泛型集。

你可以创建一个Int数组,也可创建一个String数组,或者甚至于可以是任何其他Swift的类型数据数组。

以下实例是一个非泛型函数exchange用来交换两个Int值:

//定义一个交换两个变量的函数funcexchange(inouta:Int,inoutb:Int){lettemp=aa=bb=temp}varnumb1=100varnumb2=200print("交换前数据:\(numb1)和\(numb2)")exchange(&numb1,b:&numb2)print("交换后数据:\(numb1)和\(numb2)")

交换前数据:100和200交换后数据:200和100

泛型函数可以访问任何类型,如Int或String。

以下实例是一个泛型函数exchange用来交换两个Int和String值:

funcexchange(inouta:T,inoutb:T){lettemp=aa=bb=temp}varnumb1=100varnumb2=200print("交换前数据:\(numb1)和\(numb2)")exchange(&numb1,b:&numb2)print("交换后数据:\(numb1)和\(numb2)")varstr1="A"varstr2="B"print("交换前数据:\(str1)和\(str2)")exchange(&str1,b:&str2)print("交换后数据:\(str1)和\(str2)")

交换前数据:100和200交换后数据:200和100交换前数据:A和B交换后数据:B和A

这个函数的泛型版本使用了占位类型名字(通常此情况下用字母T来表示)来代替实际类型名(如Int、String或Double)。占位类型名没有提示T必须是什么类型,但是它提示了a和b必须是同一类型T,而不管T表示什么类型。只有exchange(::)函数在每次调用时所传入的实际类型才能决定T所代表的类型。

另外一个不同之处在于这个泛型函数名后面跟着的占位类型名字(T)是用尖括号括起来的()。这个尖括号告诉Swift那个T是exchange(::)函数所定义的一个类型。因为T是一个占位命名类型,Swift不会去查找命名为T的实际类型。

Swift允许你定义你自己的泛型类型。

自定义类、结构体和枚举作用于任何类型,如同Array和Dictionary的用法。

structTOS{varitems=[T]()mutatingfuncpush(item:T){items.append(item)}mutatingfuncpop()->T{returnitems.removeLast()}}vartos=TOS()tos.push("Swift")print(tos.items)tos.push("泛型")print(tos.items)tos.push("类型参数")print(tos.items)tos.push("类型参数名")print(tos.items)letdeletetos=tos.pop()

["Swift"]["Swift","泛型"]["Swift","泛型","类型参数"]["Swift","泛型","类型参数","类型参数名"]

structTOS{varitems=[T]()mutatingfuncpush(item:T){items.append(item)}mutatingfuncpop()->T{returnitems.removeLast()}}vartos=TOS()tos.push("Swift")print(tos.items)tos.push("泛型")print(tos.items)tos.push("类型参数")print(tos.items)tos.push("类型参数名")print(tos.items)//扩展泛型TOS类型extensionTOS{varfirst:T{returnitems.isEmptynil:items[items.count-1]}}ifletfirst=tos.first{print("栈顶部项:\(first)")}

["Swift"]["Swift","泛型"]["Swift","泛型","类型参数"]["Swift","泛型","类型参数","类型参数名"]栈顶部项:类型参数名

类型约束指定了一个必须继承自指定类的类型参数,或者遵循一个特定的协议或协议构成。

你可以写一个在一个类型参数名后面的类型约束,通过冒号分割,来作为类型参数链的一部分。这种作用于泛型函数的类型约束的基础语法如下所示(和泛型类型的语法相同):

funcsomeFunction(someT:T,someU:U){//这里是函数主体}

//函数可以作用于查找一字符串数组中的某个字符串funcfindStringIndex(array:[String],_valueToFind:String)->Int{for(index,value)inarray.enumerate(){ifvalue==valueToFind{returnindex}}returnnil}letstrings=["cat","dog","llama","parakeet","terrapin"]ifletfoundIndex=findStringIndex(strings,"llama"){print("llama的下标索引值为\(foundIndex)")}

llama的下标索引值为2

Swift中使用typealias关键字来设置关联类型。

protocolContainer{//定义了一个ItemType关联类型typealiasItemTypemutatingfuncappend(item:ItemType)varcount:Int{get}subscript(i:Int)->ItemType{get}}//遵循Container协议的泛型TOS类型structTOS:Container{//originalStackimplementationvaritems=[T]()mutatingfuncpush(item:T){items.append(item)}mutatingfuncpop()->T{returnitems.removeLast()}//conformancetotheContainerprotocolmutatingfuncappend(item:T){self.push(item)}varcount:Int{returnitems.count}subscript(i:Int)->T{returnitems[i]}}vartos=TOS()tos.push("Swift")print(tos.items)tos.push("泛型")print(tos.items)tos.push("参数类型")print(tos.items)tos.push("类型参数名")print(tos.items)

["Swift"]["Swift","泛型"]["Swift","泛型","参数类型"]["Swift","泛型","参数类型","类型参数名"]

类型约束能够确保类型符合泛型函数或类的定义约束。

你可以在参数列表中通过where语句定义参数的约束。

你可以写一个where语句,紧跟在在类型参数列表后面,where语句后跟一个或者多个针对关联类型的约束,以及(或)一个或多个类型和关联类型间的等价(equality)关系。

下面的例子定义了一个名为allItemsMatch的泛型函数,用来检查两个Container实例是否包含相同顺序的相同元素。

如果所有的元素能够匹配,那么返回一个为true的Boolean值,反之则为false。

protocolContainer{typealiasItemTypemutatingfuncappend(item:ItemType)varcount:Int{get}subscript(i:Int)->ItemType{get}}structStack:Container{//originalStackimplementationvaritems=[T]()mutatingfuncpush(item:T){items.append(item)}mutatingfuncpop()->T{returnitems.removeLast()}//conformancetotheContainerprotocolmutatingfuncappend(item:T){self.push(item)}varcount:Int{returnitems.count}subscript(i:Int)->T{returnitems[i]}}funcallItemsMatch(someContainer:C1,anotherContainer:C2)->Bool{//检查两个Container的元素个数是否相同ifsomeContainer.count!=anotherContainer.count{returnfalse}//检查两个Container相应位置的元素彼此是否相等foriin0..()tos.push("Swift")print(tos.items)tos.push("泛型")print(tos.items)tos.push("Where语句")print(tos.items)vareos=["Swift","泛型","Where语句"]print(eos)

["Swift"]["Swift","泛型"]["Swift","泛型","Where语句"]["Swift","泛型","Where语句"]

访问控制可以限定其他源文件或模块中代码对你代码的访问级别。

你可以明确地给单个类型(类、结构体、枚举)设置访问级别,也可以给这些类型的属性、函数、初始化方法、基本类型、下标索引等设置访问级别。

协议也可以被限定在一定的范围内使用,包括协议里的全局常量、变量和函数。

访问控制基于模块与源文件。

模块指的是以独立单元构建和发布的Framework或Application。在Swift中的一个模块可以使用import关键字引入另外一个模块。

源文件是单个源码文件,它通常属于一个模块,源文件可以包含多个类和函数的定义。

Swift为代码中的实体提供了三种不同的访问级别:public、internal、private。

访问级别

定义

Public

可以访问自己模块中源文件里的任何实体,别人也可以通过引入该模块来访问源文件里的所有实体。

Internal

:可以访问自己模块中源文件里的任何实体,但是别人不能访问该模块中源文件里的实体。

Private

只能在当前源文件中使用的实体,称为私有实体。

public为最高级访问级别,private为最低级访问级别。

publicclassSomePublicClass{}internalclassSomeInternalClass{}privateclassSomePrivateClass{}publicvarsomePublicVariable=0internalletsomeInternalConstant=0privatefuncsomePrivateFunction(){}

除非有特殊的说明,否则实体都使用默认的访问级别internal。

函数的访问级别需要根据该函数的参数类型和返回类型的访问级别得出。

下面的例子定义了一个名为someFunction全局函数,并且没有明确地申明其访问级别。

funcsomeFunction()->(SomeInternalClass,SomePrivateClass){//函数实现}

函数中其中一个类SomeInternalClass的访问级别是internal,另一个SomePrivateClass的访问级别是private。所以根据元组访问级别的原则,该元组的访问级别是private。

privatefuncsomeFunction()->(SomeInternalClass,SomePrivateClass){//函数实现}

将该函数申明为public或internal,或者使用默认的访问级别internal都是错误的。

枚举中成员的访问级别继承自该枚举,你不能为枚举中的成员单独申明不同的访问级别。

比如下面的例子,枚举Student被明确的申明为public级别,那么它的成员Name,Mark的访问级别同样也是public:

publicenumStudent{caseName(String)caseMark(Int,Int,Int)}varstudDetails=Student.Name("Swift")varstudMarks=Student.Mark(98,97,95)switchstudMarks{case.Name(letstudName):print("学生名:\(studName).")case.Mark(letMark1,letMark2,letMark3):print("学生成绩:\(Mark1),\(Mark2),\(Mark3)")}

学生成绩:98,97,95

子类的访问级别不得高于父类的访问级别。比如说,父类的访问级别是internal,子类的访问级别就不能申明为public。

publicclassSuperClass{privatefuncshow(){print("超类")}}//访问级别不能低于超类internal>publicinternalclassSubClass:SuperClass{overrideinternalfuncshow(){print("子类")}}letsup=SuperClass()sup.show()letsub=SubClass()sub.show()

超类子类

常量、变量、属性不能拥有比它们的类型更高的访问级别。

比如说,你定义一个public级别的属性,但是它的类型是private级别的,这是编译器所不允许的。

同样,下标也不能拥有比索引类型或返回类型更高的访问级别。

如果常量、变量、属性、下标索引的定义类型是private级别的,那么它们必须要明确的申明访问级别为private:

privatevarprivateInstance=SomePrivateClass()

常量、变量、属性、下标索引的Getters和Setters的访问级别继承自它们所属成员的访问级别。

Setter的访问级别可以低于对应的Getter的访问级别,这样就可以控制变量、属性或下标索引的读写权限。

classSamplepgm{privatevarcounter:Int=0{willSet(newTotal){print("计数器:\(newTotal)")}didSet{ifcounter>oldValue{print("新增加数量\(counter-oldValue)")}}}}letNewCounter=Samplepgm()NewCounter.counter=100NewCounter.counter=800

计数器:100新增加数量100计数器:800新增加数量700

我们可以给自定义的初始化方法申明访问级别,但是要不高于它所属类的访问级别。但必要构造器例外,它的访问级别必须和所属类的访问级别相同。

如同函数或方法参数,初始化方法参数的访问级别也不能低于初始化方法的访问级别。

Swift为结构体、类都提供了一个默认的无参初始化方法,用于给它们的所有属性提供赋值操作,但不会给出具体值。

默认初始化方法的访问级别与所属类型的访问级别相同。

classclassA{requiredinit(){vara=10print(a)}}classclassB:classA{requiredinit(){varb=30print(b)}}letres=classA()letshow=classB()

103010

如果想为一个协议明确的申明访问级别,那么需要注意一点,就是你要确保该协议只在你申明的访问级别作用域中使用。

如果你定义了一个public访问级别的协议,那么实现该协议提供的必要函数也会是public的访问级别。这一点不同于其他类型,比如,public访问级别的其他类型,他们成员的访问级别为internal。

publicprotocolTcpProtocol{init(no1:Int)}publicclassMainClass{varno1:Int//localstorageinit(no1:Int){self.no1=no1//initialization}}classSubClass:MainClass,TcpProtocol{varno2:Intinit(no1:Int,no2:Int){self.no2=no2super.init(no1:no1)}//Requiresonlyoneparameterforconvenientmethodrequiredoverrideconvenienceinit(no1:Int){self.init(no1:no1,no2:0)}}letres=MainClass(no1:20)letshow=SubClass(no1:30,no2:50)print("resis:\(res.no1)")print("resis:\(show.no1)")print("resis:\(show.no2)")

你可以在条件允许的情况下对类、结构体、枚举进行扩展。扩展成员应该具有和原始类成员一致的访问级别。比如你扩展了一个公共类型,那么你新加的成员应该具有和原始成员一样的默认的internal访问级别。

或者,你可以明确申明扩展的访问级别(比如使用privateextension)给该扩展内所有成员申明一个新的默认访问级别。这个新的默认访问级别仍然可以被单独成员所申明的访问级别所覆盖。

泛型类型或泛型函数的访问级别取泛型类型、函数本身、泛型类型参数三者中的最低访问级别。

publicstructTOS{varitems=[T]()privatemutatingfuncpush(item:T){items.append(item)}mutatingfuncpop()->T{returnitems.removeLast()}}vartos=TOS()tos.push("Swift")print(tos.items)tos.push("泛型")print(tos.items)tos.push("类型参数")print(tos.items)tos.push("类型参数名")print(tos.items)letdeletetos=tos.pop()

任何你定义的类型别名都会被当作不同的类型,以便于进行访问控制。一个类型别名的访问级别不可高于原类型的访问级别。

比如说,一个private级别的类型别名可以设定给一个public、internal、private的类型,但是一个public级别的类型别名只能设定给一个public级别的类型,不能设定给internal或private级别的类型。

THE END
1.HTML5新元素菜鸟教程为了更好地处理今天的互联网应用,HTML5添加了很多新元素及功能,比如: 图形的绘制,多媒体内容,更好的页面结构,更好的形式 处理,和几个api拖放元素,定位,包括网页 应用程序缓存,存储,网络工作者,等。 新元素 标签描述 标签定义图形,比如图表和其他图像。该标签基于 JavaScript 的绘图 API 新多媒体元素 标签https://www.runoob.com/html/html5-new-element.html
2.菜鸟教程app下载runoob菜鸟教程(编程学习软件)v1.0安卓版下载官网:官方网址 评分: 权限:点击查看 厂商:菜鸟教程 包名:io.dcloud.www.runoob.com MD5:7859918BC708768DBDDA136E07EFAF6B 安卓版下载 电脑版下载 手机扫码下载 详情介绍 菜鸟教程app也就是菜鸟教程的移动端,让大家使用手机就可以快捷学习编程,无需在到网页上去搜索,非常方便,它给大家提供了非常多的编程学习资料https://www.jb51.net/softs/923766.html
3.PHP多维数组"菜鸟教程", "http://www.runoob.com" ), "google"=>array ( "Google 搜索", "http://www.google.com" ), "taobao"=>array ( "淘宝", "http://www.taobao.com" ) ); print(""); // 格式化输出数组 print_r($sites); print(""); ?> 上面的数组https://blog.csdn.net/xiaoniu0168/article/details/144321329
4.练习使用路径此程序需要搜索扩展名为“.json”的所有文件,而不是只查找 sales.json 文件。 为此,请使用 path.extname 方法检查文件扩展名。在终端中运行以下命令,将“stores/201/sales.json”文件重命名为“stores/sales/totals.json”。 Bash 复制 mv stores/20https://docs.microsoft.com/zh-cn/training/modules/nodejs-files/5-exercise-paths/?WT.mc_id=nodebeginner-blog-cxa&ns-enrollment-type=learningpath&ns-enrollment-id=learn.build-javascript-applications-nodejs
5.ansible配置windows5986mob6454cc7225b4的技术博客ansibel学习总结 基本语法 列表和字典层级要分明,冒号后面加空格,如下面的例子,刚开始学习的时候,冒号后面没有加空格,导致失败。 - hosts: httpd remote_user: root tasks: - name: install httpd yum: name=httpd state=present - name: start httpd service https://blog.51cto.com/u_16099283/12822446
6.示例代码函数计算(FC)访问其他服务 监控报警 实践教程 安全合规 开发参考 服务支持 首页函数计算函数计算 FC 3.0快速入门示例代码 示例代码 更新时间:2024-12-11 15:18:43 产品详情 我的收藏 函数计算为您提供丰富的示例代码,您可以在创建或配置函数时,快速选择您需要的函数代码。本文提供适用于函数计算的各种类型的示例代码列表。 使用https://help.aliyun.com/zh/functioncompute/fc-3-0/getting-started/sample-code
7.菜鸟教程官网,菜鸟教程(www.runoob.com)提供了编程的基础技术教程菜鸟教程(www.runoob.com)提供了编程的基础技术教程, 介绍了HTML、CSS、Javascript、Python,Java,Ruby,C,PHP , MySQL等各种编程语言的基础知识。 同时本站中也提供了大量的在 菜鸟教程 学的不仅是技术,更是梦想 w3cschool,HTML,CSS,PHP,DOM,JavaScript,jQuery,XML,AJAX,ASP.NET,W3C,MySQL,SQL,jquery mobilehttps://www.info35.com/url/1396.html
8.runoob离线手册下载runoob菜鸟教程离线版下载免费版小编为您推荐:runoob离线手册编程手册runoob离线手册是可以离线查看菜鸟网络旗下的各种建站编程的教程手册chm版,丰富的专业知识让你学习使用各种web前端以及后端的工具,有需要的朋友就来IT猫扑下载吧! 官方介绍 菜鸟教程提供了最全的基础编程技术教程。菜鸟教程的Slogan学的不仅是技术,更是梦想!致力于推广各种编程语言技术https://www.itmop.com/downinfo/156421.html
9.GitHubhoucy/tutorialsfromrunoob:菜鸟教程Runoob教程-html .gitignore download_tutorials.py readme.md requirements.txt README TutorialsFromRunoob 这是一个网络爬虫项目,功能是从菜鸟教程下载并分类的所有教程。官网上的教程暂不支持自动翻页(自己比较懒 = =), 还有自己刚开始学习python,所有就写了这个爬虫。 https://github.com/houcy/tutorials-from-runoob
10.runoob菜鸟手册文章目录一篇快速入门HTML一篇快速入门CSS一篇快速入门JS演练前要明白的css盒子模型(在后面的例子中经常用到)实战演练—另类登录网页(1)实战演练—另类登录网页(2)实战演练—另类登录网页(3)实战演练—花里胡哨搜索框深入学习网站推荐—[菜鸟教程](https://www.runoob.com/)深入学习网站教程—[W3school](https://wwwhttps://www.iteye.com/resource/u010296897-10201481
11.Runoob菜鸟教程Runoob特点 1. 多样的教程:菜鸟教程涵盖了多个编程语言和技术领域的教程,包括但不限于Java、Python、C++、HTML、CSS、JavaScript、SQL等。无论你是想学习一门新的编程语言,还是深入了解某个技术领域,菜鸟教程都能提供相应的教程资源。 2. 简单易懂:菜鸟教程以简单易懂的方式解释复杂的概念和技术,适合初学者https://www.colostar.cn/links/12863.html
12.菜鸟教程官网菜鸟教程提供了基础编程技术教程。 菜鸟教程(www.runoob.com)提供了编程的基础技术教程, 介绍了HTML、CSS、Javascript、Python,Java,Ruby,C,PHP , MySQL等各种编程语言的基础知识。 同时本站中也提供了大量的在线实例,通过实例,您可以更好的学习编程。 菜https://www.lynelo.com/showinfo/74-1675-0.html
13.适合自学成才的22个教程网站「附资源」3. RUNOOB——菜鸟教程 网页设计和分类都非常清爽的一个技术教程网站,内容丰富,无论是菜鸟还是老手在这里都能有所收获。 网站目前积累了非常多的技术文章,都可以通过菜鸟笔记中查看,通过用户笔记也可以查看其他用户分享的技术内容。 网站也汇聚了很多在线工具,可以方便开发者们直接查找使用。 https://weibo.com/ttarticle/p/show?id=2309404500431668641846
14.runoob此脚本用来下载runoob教程为pdf文件,可用来给学习者打印或者离线学习.pdf文件已经下载至 runoob 文件夹.若想下载至您的本地,请运行 python3 runoob_crawl.py 您可设置pdf内样式 在clean.js设置html的字体,宽度,样式,再保存到pdf 运行时会包含的错误: ERROR:gpu_process_transport_factory.cc(967)] Lost UI sharedhttps://gitee.com/jiecst/runoob-pdf/
15.菜鸟教程php在线编辑器怎么用?Worktile社区在菜鸟教程的PHP在线编辑器中,我们可以方便地进行PHP代码的编写和运行。下面是使用菜鸟教程PHP在线编辑器的步骤: 1. 打开菜鸟教程的官网(www.runoob.com)并点击页面上方的“工具”菜单。2. 在工具页面中找到PHP在线编辑器,并点击进入。3. 在编辑器的代码区域中,可以直接输入你的PHP代码。可以在代码区域的左侧选择https://worktile.com/kb/ask/195512.html
16.react框架菜鸟教程,前端小白必看React 菜鸟教程:菜鸟教程是一个非常适合初学者的学习平台,也有 React 的教程。您可以在菜鸟教程上找到 React 的入门教程和实例代码:https://www.runoob.com/react/react-tutorial.html 视频教程:如果您更喜欢通过视频学习,有很多优秀的 React 视频教程可供选择,例如 YouTube 上的免费教程或付费的在线教育平台上的课http://www.apppark.cn/t-46598.html
17.Node.js安装配置菜鸟教程本章节我们将向大家介绍在 Windows 和 Linux 上安装 Node.js 的方法。 本安装教程以 Node.js v4.4.3 LTS(长期支持版本) 版本为例。 Node.js 安装包及源码下载地址为:https://nodejs.org/en/download/。 你可以根据不同平台https://www.wolai.com/johnny/29787BkCUVVG5KbQ67ZyzA
18.菜鸟教程www.runoob.com网站地址:www.runoob.com 网站链接:进入网站 服务器IP:0.0.0.0 相关信息:菜鸟教程(https://top.118281.com)提供了基础编程技术教程。包括了HTML、CSS、Javascript、PHP、C、Python等各种基础编程教程。同时网站中也提供了大量的在线实例,通过实例,您可以更好地学习如何建站。网站致力于推广各种编程语言技术,所有资源是https://top.080210.com/siteinfo/96525.html
19.node.js搭建blog1.安装node.js参考菜鸟教程 http://www.runoob.com/nodejs/nodejs-install-setup.html 2.设置node.js环境变量,两个 2.1 2.2 path添加node的安装位置 4.2 使用Express生成项目框架 4.2.1 安装 Express 在Express v3.x 之前,还内置许多中间件,但在 v4.x 后,除了 static 都被分离为单独的模块,这也是许多初学https://www.jianshu.com/p/c890b7d56383