版本记录
版本号 | 时间 |
---|---|
V1.0 | 2017.08.01 |
前言
我是swift2.0的时候开始接触的,记得那时候还不是很稳定,公司的项目也都是用oc做的,并不对swift很重视,我自己学了一段时间,到现在swift3.0+已经出来了,自己平时也不写,忘记的也差不多了,正好项目这段时间已经上线了,不是很忙,我就可以每天总结一点了,希望对自己对大家有所帮助。在总结的时候我会对比oc进行说明,有代码的我会给出相关比对代码。
1. swift简单总结(一)—— 数据简单值和类型转换
2. swift简单总结(二)—— 简单值和控制流
3. swift简单总结(三)—— 循环控制和函数
4. swift简单总结(四)—— 函数和类
5. swift简单总结(五)—— 枚举和结构体
6. swift简单总结(六)—— 协议扩展与泛型
7. swift简单总结(七)—— 数据类型
8. swift简单总结(八)—— 别名、布尔值与元组
9. swift简单总结(九)—— 可选值和断言
10. swift简单总结(十)—— 运算符
11. swift简单总结(十一)—— 字符串和字符
12. swift简单总结(十二)—— 集合类型之数组
13. swift简单总结(十三)—— 集合类型之字典
14. swift简单总结(十四)—— 控制流
15. swift简单总结(十五)—— 控制转移语句
16. swift简单总结(十六)—— 函数
17. swift简单总结(十七)—— 闭包(Closures)
18. swift简单总结(十八)—— 枚举
19. swift简单总结(十九)—— 类和结构体
20. swift简单总结(二十)—— 属性
21. swift简单总结(二十一)—— 方法
22. swift简单总结(二十二)—— 下标脚本
23. swift简单总结(二十三)—— 继承
24. swift简单总结(二十四)—— 构造过程
25. swift简单总结(二十五)—— 构造过程
26. swift简单总结(二十六)—— 析构过程
27. swift简单总结(二十七)—— 自动引用计数
28. swift简单总结(二十八)—— 可选链
29. swift简单总结(二十九)—— 类型转换
30.swift简单总结(三十)—— 嵌套类型
31.swift简单总结(三十一)—— 扩展
协议
OC
中就有协议,比如下面几个:
@protocol UITableViewDelegate<NSObject, UIScrollViewDelegate>
@optional
// Display customization
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section NS_AVAILABLE_IOS(6_0);
- (void)tableView:(UITableView *)tableView willDisplayFooterView:(UIView *)view forSection:(NSInteger)section NS_AVAILABLE_IOS(6_0);
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath NS_AVAILABLE_IOS(6_0);
- (void)tableView:(UITableView *)tableView didEndDisplayingHeaderView:(UIView *)view forSection:(NSInteger)section NS_AVAILABLE_IOS(6_0);
- (void)tableView:(UITableView *)tableView didEndDisplayingFooterView:(UIView *)view forSection:(NSInteger)section NS_AVAILABLE_IOS(6_0);
// Variable height support
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section;
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section;
同样,在swift
中也存在协议。
协议protocol
用于定义完成某项任务或功能所必须的方法和属性,协议实际上并不提供这些功能或任务的具体实现implementation
,而只是用来描述这些实现应该是什么样的。类、结构体和枚举通过提供协议所要求的方法,属性的具体实现来采用adopt
协议,任意能够满足协议要求的类型被称为协议的遵循者
。
协议可以要求其遵循者提供特定的属性,实例方法、类方法、操作符或下标脚本等。
下面主要从这几个方面说一下协议。
- 协议的语法
Protocol Syntax
- 对属性的规定
Property Requirements
- 对方法的规定
Method Requirements
- 对突变方法的规定
Mutating Method Requirements
- 对构造器的规定
Initializer Requirements
- 协议类型
Protocol as Types
- 委托代理模式
Delegation
- 在扩展中添加协议成员
Adding Protocol Conformance with an Extension
- 通过扩展补充协议声明
Declaring Protocol Adoption with an Extension
- 集合中的协议类型
Collections of Protocol Types
- 协议的继承
Protocol Inheritance
- 类专属协议
Class - Only Protocol
- 协议合成
Protocol Composition
- 检验协议的一致性
Checking for Protocol Conformance
- 对可选协议的规定
Optional Protocol Requirements
协议的语法
协议的定义方式与类、结构体、枚举
的定义非常类似,如下所示。
protocol SomeProtocol {
//协议内容
}
在类型名称后面加上协议名称,中间加上冒号:
分割即可实现协议,实现多个协议时,各协议之间用逗号,
分割,如下所示。
struct SomeStructure : FirstProtocol, AnotherProtocol{
//结构体内容
}
如果一个类在含有父类
的同时也采用了协议,应当把父类
放在所有的协议
之前,如下所示。
class someClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
//类的内容
}
对属性的规定
协议可以规定其遵循者提供特定名称与类型的实例属性 instance property
或类属性 type property
,而不管其是存储型属性stored property
还是计算型属性calculate property
。
还要注意:
- 如果协议要求属性是可读写的,那么这个属性不能是常量存储型属性或只读计算型属性。
- 如果协议要求属性是只读的
gettable
,那么计算型属性或存储型属性都能满足协议对属性的规定,即使为只读属性实现了写方法也依然有效settable
。
协议中的属性经常被加以var
前缀声明其为变量属性,在声明后加上{set get}
来表示属性是可读写的,只读的属性则写作{get}
,如下所示。
protocol SomeProtocol {
var mustBeSettable : Int{
get set
}
var doesNotNeedToBeSettable : Int = {
get
}
}
通常,在协议的定义中使用class
前缀表示该属性为类成员;在枚举和结构体实现协议中,需要使用static
关键字作为前缀。
protocol SomeProtocol {
class var someTypeProperty : Int{
get set
}
}
下面的是一个含有一个实例属性要求的协议。
protocol SomeProtocol {
var someString : String{
get
}
}
下面就是一个实现了SomeProtocol
协议的简单结构体。
class JJPracticeVC: UIViewController {
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.lightGray
let john = Person(someString: "John")
print(john)
}
}
struct Person : SomeProtocol{
var someString : String
}
protocol SomeProtocol {
var someString : String{
get
}
}
下面看输出结果
Person(someString: "John")
下面在看一下复杂的例子。
class JJPracticeVC: UIViewController {
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.lightGray
var starship = StarShip(name: "Enterprise", prefix: "USS")
print(starship.someString)
}
}
class StarShip : SomeProtocol {
var prefix : String?
var name : String
init(name : String, prefix : String? = nil) {
self.name = name
self.prefix = prefix
}
var someString: String{
return (prefix != nil ? prefix! + "" : "") + name
}
}
下面看输出结果
USSEnterprise
对方法的规定
协议可以要求其遵循者实现某些指定的实例方法或类方法,这些方法作为协议一部分,像普通的方法一样清晰的放在协议的定义中,而不需要大括号和方法体。
注意:协议中的方法支持变长参数variadic parameter
,不支持参数默认值default value
。
协议中类方法的定义与类属性的定义相似,在协议定义的方法前置class
关键字来表示,当在枚举或结构体实现类方法时,需要使用static
关键字来代替。
protocol SomeProtocol {
class func someTypeMethod()
}
如下所示,定义了含有一个实例方法的协议。
protocol RandomNumberGenerator {
func random() -> Double
}
下面我们看一个类,遵循了RandomNumberGenerator
协议的类,该类实现了一个叫做线性同余生成器的伪随机数算法。
class JJPracticeVC: UIViewController {
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.lightGray
let generator = LinearCongruentialGenerator()
let value = generator.random()
print(value)
}
}
class LinearCongruentialGenerator: RandomNumberGenerator {
var lastRandom = 42.0
let m = 139968.0
let a = 3877.0
let c = 29573.0
func random() -> Double {
lastRandom = (lastRandom * a + c).truncatingRemainder(dividingBy: m)
return lastRandom / m
}
}
下面我们看一下输出结果
0.37464991998171
对突变方法的规定
有时候不得不在方法中更改实例的所属类型,在基于值类型 value types
(结构体、枚举)的实例方法中,将mutating
关键字作为函数的前缀,写在func
之前,表示可以在该方法中修改实例及其属性的所属类型。
如果协议中的实例方法打算改变其遵循者实例的类型,那么在协议定义时需要在方法前加上mutating
关键字,才能是结构体和枚举来采用并满足协议中对方法的规定。
注意:用类实现协议中的mutating
方法时,不用写mutating
关键字;用结构体、枚举实现协议中的mutating
方法时必须写mutating
关键字。
先看一下下面的协议
protocol Togglable {
mutating func toggle()
}
下面调用一下
class JJPracticeVC: UIViewController {
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.lightGray
var lightSwitch = LightSwitch.off
lightSwitch.toggle()
print(lightSwitch)
}
}
enum LightSwitch : Togglable{
case off, on
mutating func toggle() {
switch self {
case .off:
self = .on
case .on:
self = .off
}
}
}
下面看输出结果
on
对构造器的规定
协议可以要求它的遵循类型实现特定的构造器,你可以像书写普通的构造器那样,在协议的定义里写下构造器的需求,但不需要写花括号和构造器实体。
protocol SomeProtocol {
init(someParameter : Int)
}
协议构造器规定在类中的实现,你可以在遵循该协议的类中实现构造器,并指定其为类的特定构造器或者便携构造器,在这两种情况下你都必须给构造器实现标上required
修饰符。
class SomeClass : SomeProtocol{
required init(someParameter: Int) {
//构造器实现
}
}
使用required
修饰符可以保证,所有的遵循该协议的子类,同样能为构造器规定提供一个显式的实现或继承实现。
注意:如果类已经被final
修饰符所标示,你就不需要在协议构造器规定的实现中使用required
修饰符,因为final
类不能有子类。
如果一个子类重写了父类的指定构造器,并且该构造器遵循了某个协议的规定,那么该构造器的实现需要被同时标示为required
和override
修饰符。
看一下简单的例子。
class someSubClass : SomeSuperClass, SomeProtocol{
required override init() {
//构造器实现
}
}
class SomeSuperClass {
init() {
//协议定义
}}
protocol SomeProtocol {
init()
}
协议类型
尽管协议本身并不实现任何功能,但是协议可以被当做类型来使用。
主要有下面几种情况:
- 协议类型作为函数、方法或构造器中的参数类型或返回值类型
- 协议类型个作为常量、变量或属性的类型
- 协议类型作为数组、字典或其他容器中的元素类型
注意:协议是一种类型,因为协议类型的名称应与其他类型Int、Double、String
的写法相同,驼峰命名法。
下面是一个简单的例子。
class JJPracticeVC: UIViewController {
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.lightGray
var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
for _ in 1...5{
print("random roll is \(d6.roll())")
}
}
}
class Dice{
let sides : Int
let generator : RandomNumberGenerator
init(sides : Int, generator : RandomNumberGenerator) {
self.sides = sides
self.generator = generator
}
func roll() -> Int{
return Int(generator.random() * Double(sides)) + 1
}
}
class LinearCongruentialGenerator: RandomNumberGenerator {
var lastRandom = 42.0
let m = 139968.0
let a = 3877.0
let c = 29573.0
func random() -> Double {
lastRandom = (lastRandom * a + c).truncatingRemainder(dividingBy: m)
return lastRandom / m
}
}
protocol RandomNumberGenerator {
func random() -> Double
}
下面看输出结果
random roll is 3
random roll is 5
random roll is 4
random roll is 5
random roll is 4
委托代理模式
委托是一种设计模式,允许类或结构体将一些需要它们负责的功能交由委托给其他的类型的实例。委托模式的实现很简单,定义协议来封装那些需要被委托的函数和方法,使其遵循者拥有这些被委托的函数和方法。
委托模式可以用来响应特定的动作或接收外部数据源提供的数据,而无需要知道外部数据源所属类型。
在扩展中添加协议成员
可以通过扩展(Extension)
来扩展已存在类型,扩展可以为已存在的类型添加属性、方法、下标脚本、协议等成员。
注意:通过扩展已存在的类型遵循协议时,该类型的所有实例也会随之添加协议中的方法。
下面看一下代码。
protocol TextRepresentable {
func asText() -> String
}
上面协议TextRepresentable
中包含一个方法asText
。
通过扩展为上面类Dice
类遵循这个协议TextRepresentable
。
extension Dice : TextRepresentable{
func asText() -> String {
return "A \(sides) - sided dice"
}
}
从现在开始,Dice
类型实例可被当作TextRepresentable
类型。
通过扩展补充协议声明
当一个类型已经实现了协议中的所有要求,却没有声明时,可以通过扩展来补充协议声明。
struct Hamster {
var name : String
func asText() -> String {
return "A hamster named \(name)"
}
}
extension Hamster : TextRepresentable{}
从现在起,Hamster
的实例可以作为TextRepresentable
类型使用。
下面我们调用一下
class JJPracticeVC: UIViewController {
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.lightGray
let simonTheHamster = Hamster(name: "Simon")
let somethingTextRepresentable : TextRepresentable = simonTheHamster
print(somethingTextRepresentable.asText())
}
}
下面我们看一下输出结果
A hamster named Simon
注意:即使满足了协议的所有要求,类型也不会自动转变,因此你必须为它做出明显的协议声明。
集合中的协议类型
协议类型可以被集合使用,表示集合中的元素均为协议类型。
可以如下定义
let things : [TextRepresentable] = [game, d12, simonTheHamster]
如下所示,things
数组可以被直接遍历,并调用其中元素的asText()
函数。每个成员thing
被当做是TextRepresentable
类型,因此能够且仅能调用asText()
方法。
协议的继承
协议能够继承一到多个其他协议,语法与类的继承相似,多个协议之间用逗号,
分隔。
下面我们看一下是如何定义的。
protocol InheritingProtocol : SomeProtocol, AnotherProtocol {
//协议定义
}
下面看一个简单的例子。
protocol PrettyTextRepresentable : TextRepresentable {
func asPrettyText() -> String
}
PrettyTextRepresentable
协议继承了TextRepresentable
,遵循PrettyTextRepresentable
协议的同时,也需要遵循TextRepresentable
协议。
类专属协议
你可以在协议的继承列表中,通过添加class
关键字,限制协议只能适配到类class
类型,(结构体和枚举不能遵循该协议),该class
关键字必须是第一个出现在协议的继承列表中,其后,才是其他继承协议。
下面看一下定义方法。
protocol SomeClassOnlyProtocol : class, SomeInheritedProtocol{
//class - only protocol definition goes here
}
当协议需求定义的行为,要求或假设它的遵循类型必须是引用语义而非值语义时,应该采用类专属协议。
协议合成
一个协议可以由多个协议采用protocol <SomeProtocol, AnotherProtocol>
这样的格式进行组合,称为协议合成(protocol composition)
。
下面来个例子。
class JJPracticeVC: UIViewController {
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.lightGray
let birthdayPerson = Person(name: "Allen", age: 20)
wishHappyBirthday(celebrator: birthdayPerson)
}
}
protocol Named {
var name : String {
get
}
}
protocol Aged {
var age : Int{
get
}
}
struct Person : Named, Aged {
var name : String
var age : Int
}
func wishHappyBirthday(celebrator : protocol<Named, Aged>){
print("Happy birthday \(celebrator.name) - you are \(celebrator.age)!")
}
wishHappyBirthday
函数的形参celebrator
的类型为protocol<Named, Aged>
,可以传入任意遵循这两个协议的类型的实例。
检验协议的一致性
使用is
和as
操作符来检查协议的一致性或转化协议类型。
-
is
操作符用来检查实例是否遵循了某个协议 -
as?
返回一个可选值,当实例遵循协议时,返回该协议类型,否则返回nil
-
as
用以强制向下转型
下面看一下
@objc protocol HasArea{
var area : Double {
get
}
}
注意:@objc
用来表示协议是可选的,也可以用来表示暴露给OC
的代码,@objc
型协议只对类有效,因此只能在类中检查协议的一致性。
class CirCle : HasArea{
let pi = 3.1415927
var radius : Double
var area : Double{
return pi * radius * radius
}
init(radius : Double) {
self.radius = radius
}
}
class Country : HasArea{
var area : Double
init(area : Double) {
self.area = area
}
}
如下所示,Animal
是一个没有实现HasArea
协议的类。
class Animal{
var legs : Int
init(legs : Int) {
self.legs = legs
}
}
CirCle
、Country
和Animal
没有一个相同的基类,因而采用AnyObject
类型的数组来装载在它们的实例,如下所示:
let objects : [AnyObject] = [
CirCle(radius: 2.0),
Country(area: 243_610),
Animal(legs: 4)
]
下面看一下数组遍历
class JJPracticeVC: UIViewController {
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.lightGray
let objects : [AnyObject] = [
CirCle(radius: 2.0),
Country(area: 243_610),
Animal(legs: 4)
]
for object in objects {
if let objectWithArea = object as? HasArea{
print("Area is \(objectWithArea.area)")
}
else {
print("Something that does not have an area")
}
}
}
}
下面我们看一下输出结果
Area is 12.5663708
Area is 243610.0
Something that does not have an area
objects
数组中元素的类型并不会因为向下转型而改变,它们仍然是CirCle
、Country
、Animal
类型,然而,当它们被赋值给objectWithArea
常量时,则只被视为HasArea
类型,因此只有area
属性能够被访问。
对可选协议的规定
可选协议含有可选成员,其遵循者可以选择是否实现这些成员,在协议中使用@optional
关键字作为前缀来定义可选成员。这一点和OC
中是一样的。
可选协议在调用时使用可选链
。
注意:可选协议只能在含有@objc
前缀的协议中生效,且@objc
的协议只能被类
遵循。
@objc protocol CounterDataSource{
@objc optional func incrementForCount(count : Int) -> Int
@objc optional var fixedIncrement : Int{
get
}
}
CounterDataSource
含有incrementForCount
可选方法和fixedIncrement
可选属性。
下面看一个简单代码
class Counter{
var count = 0
var dataSource : CounterDataSource?
func increment(){
if let amount = dataSource?.incrementForCount?(count: count) {
count += amount
}
else if let amount = dataSource?.fixedIncrement
{
count += amount
}
}
}
count
属性用于存储当前的值,increment
方法用来为count
赋值,increment
方法通过可选链,尝试从两种可选成员中获取count
。
- 由于
dataSource
可能为nil
,因此,在dataSource
后面加上了?
标记表明只在dataSource
非空的时候才能调用incrementForCount
方法。 - 即使
dataSource
存在,但是也无法保证其是否实现了incrementForCount
方法,因此在incrementForCount
方法后面也加上?
标记。 - 当
incrementForCount
不能被调用时,尝试使用可选属性fixedIncrement
来代替。
下面看一个简单例子。
class ThreeSource : CounterDataSource{
let fixedIncrement = 3
}
使用ThreeSource
作为数据源实例化一个Counter
。
class JJPracticeVC: UIViewController {
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.lightGray
var counter = Counter()
counter.dataSource = ThreeSource()
for _ in 1...4 {
counter.increment()
print(counter.count)
}
}
}
下面看输出结果
3
6
9
12
在看一下下面的例子。
class JJPracticeVC: UIViewController {
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.lightGray
var counter = Counter()
counter.count = -4
counter.dataSource = TowardsZeroSource()
for _ in 1...5 {
counter.increment()
print(counter.count)
}
}
}
class TowardsZeroSource : CounterDataSource{
func incrementForCount(count : Int) -> Int {
if count == 0 {
return 0
}
else if count < 0 {
return 1
}
else {
return -1
}
}
}
下面看输出结果
-3
-2
-1
0
0
后记
未完,待续~~~