结构与枚举
认识结构(struct)
struct属于值类型,具有拷贝语义(赋值和传参)
struct不支持面向对象,主要用于定义轻量级数值类型;class支持面向对象,主要用于设计有丰富关系的组件系统(有继承、多态等复杂设计模式)。
struct有传参拷贝成本,不要定义尺寸过大的结构;class有ARC内存管理成本。
不要再struct内定义引用类型属性(会改变struct的值拷贝语义)。
class RPoint{
var x:Int
var y:Int
init(x:Int, y:Int){ //class的初始化器,必须写。
self.x=x
self.y=y
}
deinit{
print("clean up")
}
}
struct SPoint{
var x:Int
var y:Int
var z:RPoint
}
var rp=RPoint(x:10, y:20)
//struct有默认按成员初始化器
var sp1=SPoint(x:10, y:20, z: RPoint(x: 100,y: 200))
var sp2=sp1
print(" \(sp1.x), \(sp1.y), \(sp1.z.x), \(sp1.z.y)") //" 10, 20, 100, 200\n"
print(" \(sp2.x), \(sp2.y), \(sp2.z.x), \(sp2.z.y)") //" 10, 20, 100, 200\n"
sp1.x+=10
sp1.y+=10
sp1.z.x+=10
sp1.z.y+=10
print(" \(sp1.x), \(sp1.y), \(sp1.z.x), \(sp1.z.y)") //" 20, 30, 110, 210\n"
print(" \(sp2.x), \(sp2.y), \(sp2.z.x), \(sp2.z.y)") //" 10, 20, 110, 210\n"
struct VS class
相同点:
都可以定义一下成员:属性、方法、下标、初始化器。
都支持类型扩展、协议。
不同点:
类支持继承和多态,结构不支持。
类必须自己定义初始化器,结构会有默认的按成员初始化器
类支持析构器,结构不支持
类的实例在对上,由ARC负责释放;结构的实例在栈上,栈结束会自动释放,不参与ARC管理。
类支持引用相等比较(===与!==),结构不支持
认识枚举(enum)
enum用于定义一组相关的值成员,enum同属于值类型,具有拷贝语义(赋值与传参)。
//定义枚举类型,换行每一行都要用case
enum Color {
case Red
case Green
case Blue
}
//或者可以不换行,用一个case就可以
enum ComplexColor {
case Red,Green,Blue,Alpha
}
//枚举实例的使用
var c1 = Color.Red
var c2:Color
c2 = .Green //也可以写成 c2 = Color.Green
可以使用switch-case语句处理enum,但case必须包含所有的枚举值,否则需要用default。
func print (color: Color) {
switch color {
case .Red:
print("Red Color!")
case .Green:
print("Green Color")
}
}
可以为enum指定原始值,即raw value, 类型可以是字符、字符串、整数、或者浮点数。数值默认从0开始,字符串与枚举值名称相同。
//指定原始类型。默认从0开始,这里设置Monday = 1那么就从1开始了
enum WeekDay: Int {
case Monday = 1, Tuesday, Wednesday, Thursday, Friday, Satutday, Sunday
}
//用原生值来初始化,rawValue是默认值
var day: Weekday?
day = WeekDay(rawValue: 7)
//获取一个rawValue
var data = WeekDay.Saturday.rawValue
enum支持关键值,可以设置不同类型的值成员,类似联合数据结构
//枚举关联值
class Point {
var x=0
var y=0
init(x:Int, y:Int) {
self.x = x
self.y = y
}
}
enum Position {
case Number(Point)
case Name(String)
}
var p1 = Position.Number(Point(x: 123, y: 588))
var p2 = Position.Name("Shanghai People's Square")
//不同类型的值成员枚举类型的使用,根据不同的类型做出不同的操作
func print(position: Position) {
switch position {
case .Number (let number):
print("[\(number.x), \(number.y)]")
case .Name (let name):
print(name)
}
}
enum还可以定义以下成员:计算属性、方法、初始化器。
//定义计算属性、方法、初始化器
enum Sex {
case Male
case Female
//初始化器
init? (symbol: Character) {
switch symbol {
case "M"
self = .Male
case "F"
self = .Female
default:
return nil
}
}
stactic var Default: Sex {
return Sex.Male
}
func show () {
switch self {
case Male:
print("I am Male")
case Female:
print("I am Female")
}
}
}
var sex = Sex.Female
sex.show()
var defaultSex = Sex.Default
var newSex = Sex(symbol: "F")
继承与多态
Inheritance
继承:子类自动继承基类的属性、方法、下标
只有类支持继承,结构和枚举不支持继承
继承同时支持实例成员和类型成员
class Shape{
var no=0
func move() {
print("NO: \(no) Shape.move")
}
}
class Rectangle: Shape{
var leftUp=Point()
var width=0
var height=0
}
继承的两层含义:
成员复用:子类复用基类成员
var r1=Rectangle()
var c1=Circle()
//成员复用
r1.no++
r1.move()
c1.no++
c1.move()
类型抽象:将子类当作父类来使用(IS-A关系准则)
func process(shape: Shape){
shape.no++
shape.move()
}
var r1=Rectangle()
var c1=Circle()
//类型抽象
process(c1)
process(r1)
var s:Shape
s=c1
s=r1
继承的内存模型
class Base{
var x=10
var y=20
static var min=1000
func invoke(){
print("Base.invoke")
}
}
/*
func invoke(self:Base){
print("Base.invoke")
}*/
class Sub: Base{
var z=30
static var max=2000
}
var b=Base()
var s=Sub()
print(Base.min) //1000
print(Sub.min) //1000 这句等于print(Base.min)
Base.min++
print(Base.min) //1001
print(Sub.min) //1001
b.invoke()// invoke(self: b)
s.invoke()// invoke(self: s)
认识多态 Polymorphism
多态:子类在同一行为接口下,表现不同的实现方式。
子类使用override关键字来表达多态定义。
可以重写的成员有:方法、属性、下标;包括实例成员和类型成员。
在子类代码中,可以使用super来调用基类的实现
class Shape{
var no=0
func move() {
print("Shape.move")
}
}
class Rectangle: Shape{
override var no: Int {
get{
print("Rectangle.no.get()")
return super.no
}
set{
print("Rectangle.no.set()")
super.no=newValue
}
}
override func move() {
print("Rectangle.move")
}
}
在成员上使用final阻止子类override该成员;在类上使用final阻止该类被继承
class Shape{
var no=0
//func show()不能被子类重写
final func show(){
print("Shape.show")
}
//0x000640
func move() {
print("Shape.move")
}
}
func process(shape: Shape){
shape.no++
shape.move() //根据实际类型来调用
}
//变量有双重身份:
//1. 声明类型
//2. 实际类型
var sp: Shape
sp=Shape()
sp.move() // JMP GetVirtualMethod(sp) 二次指针间接运算
sp.show() // JMP 0x000340
sp=Rectangle()
sp.move() // JMP GetVirtualMethod(sp) 二次指针间接运算
process(sp)
sp=Circle()
sp.move()
process(sp)
继承中的init和deinit
初始化器 init
如果子类没有定义初始化器,则将自动继承基类的初始化器
如果子类定义了初始化器,则不再继承,此时子类初始化器必须调用基类的一个初始化器。如果手工不调用,编译器将自动生成调用
如果子类初始化器与基类初始化器原型一致,必须使用override
在子类中使用基类属性,必须确保首先调用基类初始化器
析构器 deinit
如果子类没有定义析构器,会自动继承基类析构器
子类析构器执行完毕后,会自动调用基类析构器(无法手工调用)
子类析构器自动具有多态性
class Base{
var data1:Int
init(){
data1=100
print("\(data1)")
print("Base.init")
}
deinit{
print("Base.deinit")
}
}
class Sub: Base{
var data2=200
override init(){
super.init()
print("\(data1), \(data2)")
print("Sub.init")
}
deinit{
print("Sub.deinit")
}
}
func process(){
var b:Base
b=Base()
print("----------")
var s:Base
s=Sub()
print("----------")
}
process()
协议
认识协议 Protocol
协议:类型的合同约定,只描述外部接口,不提供具体实现
协议可以包含以下成员:属性、方法、初始化器、下标、操作符
一个类型可以实现多个协议,协议可以应用在如下类型上。但可以添加class关键字标明协议只能应用在类上:类、结构、枚举
//定义协议
protocol Drawable{
var discription: String{
get
}
func draw()
init()
subscript(index: Int) -> Int {
get
}
func ==(lhs: Self, rhs: Self) -> Bool
}
//实现协议
class Point: Drawable{
var x:Int
var y:Int
required init(){
x=10
y=10
}
var discription: String{
return "[\(x), \(y)]";
}
func draw(){
print(self.discription)
}
subscript(index: Int) -> Int {
return 0
}
}
使用协议
协议本质上是一种类型,可以作为声明类型,但不能创建实例
协议变量的内存模型遵从实际类型的内存模型
引用类型传参、拷贝采用传引用方式
值类型转参、拷贝采用传值方式
检查协议类型
使用is检查类型是否实现了协议
使用as?和as!将类型下溯转型为协议
protocol AProtocol{
func display()
}
class Base{
var no=0
}
class Sub:Base,AProtocol{
func display(){
print(no)
}
}
//Compile-time Type 编译时类型,声明类型
var item1, item2:Base
//Runtime Type 运行时类型,实际类型
item1=Base()
item2=Sub()
if(item1 is AProtocol){
print("the runtime type of item1 conforms Shape protocol")
}
if(item2 is AProtocol){
print("the runtime type of item2 conforms Shape protocol")
}
var item3:AProtocol?
var item4=Sub()
item3 = item1 as? AProtocol
item3 = item2 as? AProtocol
item3=item4
协议中的属性
协议中可以定义只读属性,也可以定义读写属性
协议中可以定义实例属性,也可以定义类型属性
协议中只能定义变量属性,不能定义常量属性
实现协议时,并不要求实现为存储属性,还是计算属性
协议中的方法
协议可以定义实例方法、也可以定义类型方法
协议中的方法不能定义参数的默认值
针对值类型的mutating协议方法
值类型(struct和enum)实现的实例方法如果要更改实例本身,需要在协议方法的定义中标明mutating关键字,同时在方法实现时也添加mutating关键字
添加了mutating的协议方法,对类的实现方法无影响。在类内实现mutating协议方法时,无需再添加mutating关键字
协议中的初始化器
协议中可以定义初始化器,但不可以定义析构器
当class中实现协议定义的初始化器时,需要添加required关键字
标明子类也需要提供该初始化器
如果定义为final类,则不需要required关键字
协议可以定义可失败的初始化器init?,具体实现时可以失败,也可以非失败
更多协议形式
协议继承
一个协议可以继承一个或多个协议
实现自协议的类型,可必须实现父协议中约定的成员
协议组合
可以使用protocol<A,B,...>来组合多个协议
实现组合协议的类型,必须实现组合协议中的每一个协议
组合协议是一个临时类型
可选协议
协议的某些成员可以定义为optional,不必实现
可选协议只能应用于class,不能应用于struct和enum
可选协议必须标明@objc特性(即使不需要和Objective-C互操作)
protocol AProtocol{
func display()
}
//协议继承
protocol BProtocol: AProtocol{
func invoke()
}
protocol CProtocol{
func excute()
}
class ClassA:BProtocol{
func display(){
print("display")
}
func invoke(){
print("invoke")
}
}
class ClassB:AProtocol,CProtocol{
func display(){
print("display")
}
func excute(){
print("excute")
}
}
//协议组合
func process( item: protocol<AProtocol,CProtocol>){
item.display()
item.excute()
}
var b=ClassB()
process(b)
//可选协议
@objc protocol DProtocol {
optional var discription: String { get }
func move()
}
class ClassC: DProtocol{
@objc func move(){
}
}
字符串
认识字符串 String
String是一个Unicode编码、16位字符的字符序列
String与NSString支持无缝互操作
String被定义为struct,值类型,拷贝时具有值语义
String是一个struct,但内部包含一个指向堆上的“内容指针”,其指向的对象存放真正的字符串内容。
使用字符串
使用var和let来控制字符串的可变性
var str1 = "Hello"
let str2 = ",Swift"
获取子串中的字符
//枚举字符
for item in str1.characters{
print(item)
}
for index in str1.characters.indices {
print(str1[index])
}
print(str1[str1.startIndex])
//print(str1[str1.endIndex])
print(str1[str1.startIndex.successor()])
print(str1[str1.endIndex.predecessor()])
let index = str1.startIndex.advancedBy(4)
print(str1[index])
使用append和+链接字符串
//连接字符串
str1.appendContentsOf(",World")
var str3=str1+str2
str1+=str2
var char:Character="!"
str1.append(char)
使用字符转义
//字符转义
let heart = "\u{2665}"
print(heart)
let words = "\tHello"
print(words)
字符串插值(String Interpolation)
//字符串插值
var x=100
var y=200.0
var text="[\(x),\(y)]"
copy-on-write 共享技术
同一个字符串内容拷贝到不同的变量中时,“内容指针”不变,即不同变量“共享”同一份堆内存。从而实现“节省内存”。
如果某一个变量的字符串内容改变时,先将原来堆内存拷贝一份,“内容指针”指向新的拷贝,然后再更改新的拷贝。从而确保“正确性”。
copy-on-write的目的是实现“内容相同的字符串共享内存,同时又支持字符串随时改变”。
最佳实践:尽量不改变字符串,尽量使用常量字符串let
缓存容量与增长
字符串初始化后,会分配一个缓存容量capacity,其长度一般大于实际的字符数量
当字符串增长时,如果实际需求大于capacity,其capacity会以二倍的方式指数增长。伴随的代价:
分配新的堆内存2*capacity
将原来堆内存上的内容拷贝到新内存
释放原来堆内存
最佳实践:估计好capacity,预先分配好一定容量,避免以后capacity的增长
str.reserveCapacity(10000) //分配了10000个字节给capacity
集合类型
集合类型有数组(Array),集合(Set),字典(Dictionary)三种。
认识数组
Array是一个有序的元素序列,支持随机存取,支持动态更新长度。
索引从0开始,索引访问越界会抛出运行时异常。
Array被定义为Struct,值类型,拷贝时具有值语义。
Array是一个Struct,但内部包含一个指向堆上的“元素指针”,其指向的对象存放真正的数组元素。
如果数组元素为值类型,拷贝数组时,元素包含值本身。
如果数组元素为引用类型,拷贝数组是,元素引用指向相同的对象。
使用数组
使var和let来控制数组的常量性:数组长度和元素内容都不能更改。
//变量数组和常量数组
var array5=[1,2,3]
let array6=[1,2,3]
数组遍历
使用for循环访问array[index]需要检查索引越界,具有性能代价
尽可能使用for-in遍历数组元素;或使用Array.enumerate()遍历索引;二者编译器会优化掉索引检查
尽量避免使用insert和remove操作,因为会改变数组元素序列,涉及到大量的内存拷贝操作,代价较高。
//数组操作
for item in array5{
print(item)
}
for(index, value) in array5.enumerate(){
print("\(index): \(value)")
}
for index in 0..<array5.count {
print(array5[index])
}
for var index=0;index<array5.count;index++ {
print(array5[index])
}
缓存容量与增长
数组初始化后,会分配一个缓存容量capacity,其长度一般大于实际的元素数量
当数组长度增加时,如果实际需求大于capacity,其capacity会以近似二倍的方式指数增长。伴随的代价:
分配新的堆内存2*capacity
将原来堆内存上的元素拷贝到新内存
释放原来的堆内存
最佳实践:估计好capacity,预先分配好一定容量,避免以后capacity的增长
copy-on-write 共享技术:与String的实现原理一样,详情请看String的copy-on-write 共享技术。
认识集合
Set是一个无序的集合,其存储的值不能重复
Set中的元素必须有哈希值,即支持Hashable协议
Set被定义为Struct,值类型,拷贝时具有值语义
Set也是一个Struct,但内部包含一个指向堆上的“元素指针”,其指向的对象存放真正的元素。
var words = Set<String>()
words.insert("Hello")
words.insert("Swift")
words.insert("Language")
words.insert("Swift")
认知字典
Dictionary是一个存储key-value的无序的集合,key唯一,value可重复
Dictionary中的key必须有哈希值,即支持Hashable协议
Dictionary被定义为Struct,值类型,拷贝时具有值语义
Dictionary也是一个Struct,但内部包含一个指向堆上的“元素指针”,其指向的对象存放真正的元素。
var dictionary1 = [String:Int]()
var dictionary2 : Dictionary<String,Int>
dictionary2=["Jason":36, "Tom":31, "Marty":44]