示例代码来源于 《iOS 11 Programming Fundamentals with Swift》
变量
Define and Call函数初始化
使用Define and Call函数进行变量初始化,Define and Call函数内容在下节。
@H_301_10@let timed : Bool = { if val == 1 { return true } else { return false } }()懒惰初始化
使用lazy关键字可以让变量在使用的时候再初始化。
@H_301_10@class MyView : UIView { lazy var arrow = self.arrowImage() func arrowImage () -> UIImage { // ... big image-generating code goes here ... } }如果没有lazy关键字,是不能通过编译的,因为MyView还没有初始化,不能调用实例方法。
一个比较常用的技术手段是结合使用define and call的函数来初始化lazy的成员变量:
@H_301_10@lazy var prog : UIProgressView = { let p = UIProgressView(progressViewStyle: .default) p.alpha = 0.7 p.trackTintColor = UIColor.clear p.progressTintColor = UIColor.black p.frame = CGRect(x:0,y:0,width:self.view.bounds.size.width,height:20) p.progress = 1.0 return p }()- lazy的成员变量初始化不是线程安全的。
- lazy的成员变量不能是只读的。
- lazy的成员变量也不能加观察者。
计算变量
无论是类成员还是全局变量,都可以定义成计算变量,就是在使用的时候计算一个值出来,而不是保存这个值。也就是getter和setter。
@H_301_10@var now : String { get { return Date().description } set { print(newValue) } }- 计算变量必须是var的,必须指定类型。
- getter函数必须返回同类型的值。
- setter函数中的newValue就是被赋予的值。也可以使用set(val)指定。
- 如果没有setter函数,变量是只读的。
- 必须有getter
- 没有setter的时候可以简写
变量观察者
和OC的KVO类似。
@H_301_10@var s = "whatever" { willSet { print(newValue) } didSet { print(oldValue) // self.s = "something else" } }- 在变量被改变之前willSet会被调用,newValue表示新的值
- 在变量被改变之后didSet会被调用,oldValue表示原来的值
- 在初始化的时候和didSet中不会触发willSet和didSet
- 如果变量是计算变量(有setter)则不能用willSet和didSet,因为可以在setter中做这些事情。
函数
函数嵌套
Swift中可以在函数体中定义函数,如果一个函数B只被函数A使用,那可以将B函数定义在函数An内。
@H_301_10@func checkPair(_ p1:Piece,and p2:Piece) -> Path? { // ... if arr.count > 0 { func distance(_ pt1:Point,_ pt2:Point) -> Double { 1 // utility to learn physical distance between two points let deltax = pt1.0 - pt2.0 let deltay = pt1.1 - pt2.1 return Double(deltax * deltax + deltay * deltay).squareRoot() } for thisPath in arr { var thisLength = 0.0 for ix in thisPath.indices.dropLast() { thisLength += distance(thisPath[ix],thisPath[ix+1]) 2 } // ... } } // ... }函数作为参数和返回值
因为Swift中函数是对象,所以函数也是可以作为参数传递的。
@H_301_10@func doThis(_ f:() -> ()) { f() } func whatToDo() { print("I did it") } doThis(whatToDo)同样,函数可以作为函数的返回值。函数作为参数或者返回值的一个好处是可以增加间接性,使得在调用的时候不必知道函数的定义,只要知道函数的签名就行。
一个比较实用的例子:
@H_301_10@let size = CGSize(width:45,height:20) UIGraphicsBeginImageContextWithOptions(size,false,0) let p = UIBezierPath( roundedRect: CGRect(x:0,y:0,width:45,height:20),cornerRadius: 8) p.stroke() let result = UIGraphicsGetImageFromCurrentImageContext()! UIGraphicsEndImageContext()上边这段代码是创建一个矩形的图片。这段代码中和UIGraphXXX相关的部分是可以提取的,因为每次画图都一样。定义一个函数:
@H_301_10@func imageOfSize(_ size:CGSize,_ whatToDraw:() -> ()) -> UIImage { UIGraphicsBeginImageContextWithOptions(size,0) whatToDraw() let result = UIGraphicsGetImageFromCurrentImageContext()! UIGraphicsEndImageContext() return result }上边的函数就是纯粹的图片制作过程,将画图的部分“外包”了出去,调用者将画图部分的函数传入即可。
使用代码:
@H_301_10@func drawing() { let p = UIBezierPath( roundedRect: CGRect(x:0,y:0,width:45,height:20),cornerRadius: 8) p.stroke() } let image = imageOfSize(CGSize(width:45,drawing)iOS 10已经有一个函数叫做UIGraphicsBeginImageContext和imageOfSize提供差不多的功能。
函数作为返回值可以引出一个模式,类似于微型的工厂模式:
@H_301_10@func makeRoundedRectangleMaker(_ sz:CGSize) -> () -> UIImage { func f () -> UIImage { let im = imageOfSize(sz) { let p = UIBezierPath(roundedRect: CGRect(origin:CGPoint.zero,size:sz),cornerRadius: 8) p.stroke() } return im } return f } let maker = makeRoundedRectangleMaker(CGSize(width:45,height:20)) self.iv.image = maker()上述代码返回一个函数,这个函数是一个ImageMaker,调用maker能生成一个Image,这个Image的大小是制作Maker时候决定的,也就是makeRoundedRectangleMaker的参数sz。
匿名函数
@H_301_10@UIView.animate(withDuration:0.4,animations: { () -> () in self.myButton.frame.origin.y += 20 },completion: { (finished:Bool) -> () in print("finished: \(finished)") })UIView.animate(withDuration: animations: completion:)中的aimations参数是一个()->Void类型的函数,compeltion是一个(Bool)->Void类型的函数。使用者可以定义两个这样类型的函数然后传入,也可以在调用的原位定义一个匿名函数。() -> () in 这行代码的意思是下边的函数是一个() -> ()类型。匿名函数的参数和返回值写在了大括号的第一行。
匿名函数有很多书写的简化规则,看到能读懂就行,不必非得使用这些语法糖。
- 如果返回值编译器能够知道,那么就可以省略返回值,如果没有参数也可以省略参数,如果返回值和参数都能省略,那么in也可以省略,所以,上边的animations参数第一行是可以省略的:
同样,在completion参数中省略的返回值。
- 对于completion来说,参数类型也可以省略,因为参数类型在UIView.animate函数的参数中已经标明了,所以可以写成:
- 更进一步,参数的名字也可以使用
0, 1…取代,这样参数的名字也可以省略:
- 还有一种常见的变体要认识:
当匿名函数是函数的最后一个参数的时候,可以将匿名函数的大括号部分写在函数调用之后。
@H_301_10@let image = imageOfSize(CGSize(width:45,height:20),{ () -> () in let p = UIBezierPath( roundedRect: CGRect(x:0,y:0,width:45,cornerRadius: 8) p.stroke() })Define and Call
@H_301_10@{ // ... code goes here }()和匿名函数相似,不同的是,匿名函数提供的是一个过程,Define and Call提供的是一个过程的结果,例子:
@H_301_10@content.addAttribute( .paragraphStyle,value: { let para = NSMutableParagraphStyle() para.headIndent = 10 para.firstLineHeadIndent = 10 // ... more configuration of para ... return para }(),range:NSRange(location:0,length:1))其中value参数是通过一个Define and Call的函数算出的结果。这种一次性的函数适合使用Define and Call或者嵌套函数。有两个好处,一个是可见的范围缩小了,避免冲突和误用,二是可以捕获上下文的变量,不需要定义一系列参数传递。
函数装饰器 & escaping
看一个函数:
@H_301_10@func countAdder(_ f: @escaping () -> ()) -> () -> () { var ct = 0 return { ct = ct + 1 print("count is \(ct)") f() } }这个函数接受一个()->()函数,返回一个()->()函数。作用是调用传入的函数f,并且记录打印函数f调用的次数。
使用:
@H_301_10@func greet () { print("howdy") } let countedGreet = countAdder(greet) countedGreet() //count is 1,howdy countedGreet() //count is 2,howdy countedGreet() //count is 3,howdy内部的匿名函数捕获了临时变量ct,并保存下来,以后的每次调用都会增加ct,并维护这个结果。从结果上看countAdder给greet会这样签名的函数增加了方法,类似一个微型的装饰器模型。
@escaping,@escaping的意思是这个函数参数在当前函数执行完成后还可能被调用,在上边的例子中,f随着匿名函数被返回,在后续的调用中执行。这种情况下,f参数需要被标记为@escaping,否则会有编译错误。因为使用者传入的参数f可能是一个闭包,可能捕获了一些临时变量,那么标记为@escaping意思就是在函数执行完之后也要保留这些捕获的变量。
Curried Functions
还使用画图的例子,上面的画图例子的圆角半径为8是写死的,如果不想写死,代码应该这样写:
@H_301_10@func makeRoundedRectangleMaker(_ sz:CGSize,_ r:CGFloat) -> () -> UIImage { return { imageOfSize(sz) { let p = UIBezierPath( roundedRect: CGRect(origin:CGPoint.zero,cornerRadius: r) p.stroke() } } } let maker = makeRoundedRectangleMaker(CGSize(width:45,8)这里还有另外一种思路:
@H_301_10@func makeRoundedRectangleMaker(_ sz:CGSize) -> (CGFloat) -> UIImage { return { r in imageOfSize(sz) { let p = UIBezierPath( roundedRect: CGRect(origin:CGPoint.zero,height:20)) self.iv.image = maker(8)这段代码的不同之处在于makeRoundedRectangleMaker接受的还是一个参数,但是返回的函数多了一个参数CGFloat。闭包内同样也捕获了返回函数的参数r,在使用的时候再提供r的值。这种方式就叫做Curried Function。
继承 & 多态
- Swift中只有Class可以继承
- Swift是单继承的。
- 没有一个像OC中NSObject这样的公共基类。
- 可以将一个class标记为final,这样这个class就不能被继承。
继承的写法:
@H_301_10@// Cat是Kitten的基类 class Dog { func barkAt(cat:Kitten) {} } class NoisyDog : Dog { override func barkAt(cat:Cat) {} // or Cat? }子类的初始化
初始化函数的继承
- 如果子类没有定义初始化函数,那么所有的初始化函数继承自基类。(子类可以没有初始化函数的必要条件是使用基类的函数就能把所有的成员都初始化了)
- 如果子类没有designated initializer,可以定义自己的convenience initializer,继承基类的designated initializer。
- 如果子类定义了自己的designated initializer,那么所有基类的designated initializer和convenience initializer都不会被继承了。
- 如果子类重写了基类所有的designated initializer,那么基类的convenience initializer又可以被子类继承了。
上述规则可以这样理解,如果基类的初始化函数能满足子类的初始化需求,那么子类可以不写初始化函数,使用基类的。另外,子类也可以定义一些辅助函数(convenience initializer)来使用基类的designated initializer初始化。如果子类定义了自己的designated initializer 那么很可能的情况是基类的designated initializer已经不能满足子类的初始化需求了,那么所有的基类的designated initializer就不能成为子类的初始化函数了。(但是在子类的designated initializer中必须调用基类的designated initializer)。因为基类的convenience initializer中肯定是调用了基类的designated initializer,所以子类如果重写了所有的designated initializer,那么基类的convenience initializer中的调用就变成了调用子类的designated initializer,所以又可以初始化子类了,所以就可以继承了。
- 重写基类的convenience initializer,不需要override关键字,重写基类的designated initializer需要使用override关键字。
- 基类的初始化函数可以被重写成failable的初始化函数(就是返回?类型的实例的初始化函数),但是反过来不行。
在基类中被标记为required的初始化函数,子类必须也有这个初始化函数。“必须有”的意思是:如果子类不能根据上述规则通过继承拥有这个初始化函数,就必须重写它。
这条规则的使用场景:
@H_301_10@func dogMakerAndNamer(_ whattype:Dog.Type) -> Dog { let d = whattype.init(name:"Fido") // compile error return d }dogMakerAndNamer是一个工厂函数,根据传入的dog的具体类型来创建一个dog出来,那么如果子类没有init(name:)这个初始化函数,这个工厂函数就行不通了。解决的办法就是在基类Dog的init(name:)前边加上required关键字,要求所有子类都实现这个初始化函数。
@H_301_10@class Dog { var name : String required init(name:String) { self.name = name } } @H_301_10@class Dog { var name : String //普通变量属性 var age : Float { //计算变量属性,只读 return 1 + 10 } required init(name:String) { self.name = name } }子类可以使用计算变量重写基类的普通变量属性,但是不能改变访问权限
@H_301_10@class NoisyDog : Dog { override var name: String{ set { self.name = newValue } //如果注释掉这一句,编译错误 get { return "dog" } } }子类可以重写基类的计算变量的属性,并且可以改变访问权限
@H_301_10@class NoisyDog : Dog { override var age: Float{ set { self.age = newValue } //可以添加setter get { return 20 } } }向下类型转换
@H_301_10@let d : Dog? = NoisyDog() let d2 = d as? NoisyDog d2?.beQuiet()类型判断
@H_301_10@let d : Dog? = NoisyDog() if d is NoisyDog { let d2 = d as! NoisyDog d2.beQuiet() }上述判断和转化代码可以合并成一句:
@H_301_10@if let d2 = d as? NoisyDog{ print(d2) }Protocol
Swift中一个类实现一个Protocol需要显式声明。Protocol可以被struct或者enum实现。
@H_301_10@protocol Flier { func fly() }- 一个类可以实现多个Protocol。
- protocol中可以定义类属性,使用static关键字。
- 如果Protocol 中的一个方法可能会改变属性,并且这个Protocol设计允许被struct或者enum实现,那么这个方法应该使用mutating关键字。
- Protocol相当于提供了另一个维度的继承体系,所以类型转换和继承体系的语法相同。
protocol的组合
如果一个函数接受一个参数,要求这个参数实现两个Protocol,可以使用Protocol的组合
@H_301_10@func f(_ x: CustomStringConvertible & CustomDebugStringConvertible) { @H_502_934@}protocol和class也可以组合
@H_301_10@protocol MyViewProtocol : class { func doSomethingCool() } class ViewController: UIViewController { var v: (UIView & MyViewProtocol)? // ... }ViewController 的构造函数接受一个参数,要求这个参数是UIView类型,并且实现了MyViewProtocol协议。
protocol MyViewProtocol : class,class关键字表示只能被class类型的实现。
Optional Protocol Method
是为了和OC 的Protocol兼容。必须标记为@objc,并且标记了@objc的protocol只能被class类型实现,不能被struct和enum实现。
@H_301_10@@objc protocol Flier { @objc optional var song : String {get} @objc optional func sing() } class Bird : Flier { func sing() { print("tweet") } }Bird只实现了一个sing方法。在这种情况下,Swift不知道song属性是不是安全的。
@H_301_10@let f : Flier = Bird() let s = f.song // s is an Optional wrapping a Strings将是一个String?而不是String,如果协议中song本身就是String?那么s将是一个String??。这一点对于方法的返回值也适用。
@H_301_10@@objc protocol Flier { @objc optional var song : String? {get} @objc optional func sing() } let f : Flier = Bird() let s = f.song // s is an Optional wrapping an Optional wrapping a String @H_301_10@let f : Flier = Bird() f.sing?()Literal Convertibles
Swift中字面变量的转换是靠实现了一个或者多个协议来实现的。
@H_301_10@struct Nest : ExpressibleByIntegerLiteral { var eggCount : Int = 0 init() {} init(integerLiteral val: Int) { self.eggCount = val } }Nest实现了ExpressibleByIntegerLiteral协议,就可以使用Integer的字面变量来表示一个Nest,比如有这样一个函数:
@H_301_10@func reportEggs(_ nest:Nest) { print("this nest contains \(nest.eggCount) eggs") }接受的是一个Nest参数,就可以传入一个字面变量
@H_301_10@reportEggs(4) // this nest contains 4 eggsExpressibleByIntegerLiteral,这个协议要求实现init(integerLiteral:)方法。Nest实现了。
注意,这个协议是关于字面变量的,并不是Int类型到Nest的转换。下边代码是不能通过编译的:
@H_301_10@var x = 4 reportEggs(x)其他的字面变量协议有:
- ExpressibleByNilLiteral
- ExpressibleByBooleanLiteral
- ExpressibleByIntegerLiteral
- ExpressibleByFloatLiteral
- ExpressibleByStringLiteral
- ExpressibleByExtendedGraphemeClusterLiteral
- ExpressibleByUnicodeScalarLiteral
- ExpressibleByArrayLiteral
- ExpressibleByDictionaryLiteral
泛型
类型是对象实例的模板,泛型就是类型的模板。不同的是,对象的的实例化是在运行期间进行的,模板的实例化是在编译期间进行的。
泛型函数
@H_301_10@func dogMakerAndNamer<WhatType:Dog>(_:WhatType.Type) -> WhatType { let d = WhatType.init(name:"Fido") return d }在这个函数中,WhatType是一个占位符,在编译的时候会替换成真正的类型。对这个类型的要求是继承自Dog.
Optional就是通过泛型来实现的,它是一个泛型的enum。
@H_301_10@enum Optional<Wrapped> { case none case some(Wrapped) init(_ some: Wrapped) // ... }
泛型协议
Self
如果一个protocol的方法声明中使用了Self,这个Self表示实现这个protocol的那个类的当前实例本身。相当于在类的实现中的self。也就是说Self是protocol中的self(因为此时不知道具体类型是啥)。
associated type
@H_301_10@protocol Flier { associatedtype Other func flockTogetherWith(_ f:Other) func mateWith(_ f:Other) }一个具体的类实现这个protocol的时候需要将Other的部分替换成具体的类型:
@H_301_10@struct Bird : Flier { func flockTogetherWith(_ f:Bird) {} func mateWith(_ f:Bird) {} }上边的字面变量协议就是泛型协议。
@H_301_10@public protocol ExpressibleByIntegerLiteral { associatedtype IntegerLiteralType public init(integerLiteral value: Self.IntegerLiteralType) }泛型类
@H_301_10@struct HolderOfTwoSameThings<T> { var firstThing : T var secondThing : T init(thingOne:T,thingTwo:T) { self.firstThing = thingOne self.secondThing = thingTwo } } let holder = HolderOfTwoSameThings(thingOne:"howdy",thingTwo:"getLost")可以使用多个类型的泛型
@H_301_10@func flockTwoTogether<T,U>(_ f1:T,_ f2:U) {@H_502_934@}泛型的约束
@H_301_10@protocol Flier { func fly() } protocol Flocker { associatedtype Other : Flier // * func flockTogetherWith(f:Other) } struct Bird : Flocker,Flier { func fly() {} func flockTogetherWith(f:Bird) {} }Flocker定义了一个protocol,它的associatedtype要求必须是一个Flier,所以Bird要想采用Flocker协议,就必须也采用Flier协议。
显示的实例化泛型
@H_301_10@protocol Flier { associatedtype Other } struct Bird : Flier { typealias Other = String }或者
@H_301_10@class Dog<T> { var name : T? } let d = Dog<String>()组合使用泛型协议和泛型对象
@H_301_10@protocol Flier { init() } struct Bird : Flier { init() {} } struct FlierMaker<T:Flier> { static func makeFlier() -> T { return T() } } let f = FlierMaker<Bird>.makeFlier() // returns a Bird泛型的继承
@H_301_10@class Dog<T> { func speak(_ what:T) {} } class NoisyDog<T> : Dog<T> {}或者在继承的时候就实例化
@H_301_10@class NoisyDog : Dog<String> { override func speak(_ what:String) {} }Associated Type Chains
@H_301_10@protocol Fighter { associatedtype Enemy where Enemy : Fighter }Enemy 占位符要求也是一个Fighter,因为Enemy本身是在Fighter中使用的,所以需要使用where语句。
实例化
@H_301_10@struct Soldier : Fighter { typealias Enemy = Archer } struct Archer : Fighter { typealias Enemy = Soldier } @H_301_10@struct Camp<T:Fighter> { var spy : T.Enemy? }spy这个变量的类型是Fighter的Enemy,但是因为Fighter也还没确定,所以使用T.Enemy。
Enemy是一个占位符,Fighter是一个泛型。在Camp中,T也是占位符,代表一个实现了Fighter的类型。所以spy是一个<占位符>.<占位符>的形式。随后的实例化的时候,会替换所有的占位符。
@H_301_10@var c = Camp<Soldier>() c.spy = Archer()泛型约束的where语句
@H_301_10@func flyAndWalk<T: Flier> (_ f:T) {} func flyAndWalk<T> (_ f:T) where T: Flier {} func flyAndWalk2<T: Flier & Walker> (_ f:T) {} func flyAndWalk2<T> (_ f:T) where T: Flier & Walker {} func flyAndWalk2a<T> (_ f:T) where T: Flier,T: Walker {}更复杂些的约束
@H_301_10@protocol Flier { associatedtype Other } struct Bird : Flier { typealias Other = String } struct Insect : Flier { typealias Other = Bird } func flockTogether<T> (_ f:T) where T:Flier,T.Other:Equatable {}flockTogether有一个类型占位符T,where语句中要求T是Flier的,因为Flier本身有一个占位符Other,还可以要求这个Other是可比较的。于是使用:T.Other:Equatable。
@H_301_10@flockTogether(Bird()) // okay flockTogether(Insect()) // compile error因为Insect的Other是Bird,而Bird不是Equatable的。所以第二句编译错误。
如果使用”==”操作符,意思是类型必须是后边的类型。
@H_301_10@func flockTogether<T> (_ f:T) where T:Flier,T.Other == Walker {}T.Other必须是Walker类型,不能是subclass。
Swfit中append(contentsOf:)函数的实现就使用了==操作符
@H_301_10@mutating func append<S>(contentsOf newElements: S) where S:Sequence,S.Element == CharacterExtensions
Extensions类似于OC中的Category,能给已有的类添加功能。
- extension不能重写原来的方法,但是可以进行函数重载
- extension不能添加stored property,但是可以添加compute property,可以添加static或者class的属性,无论是不是计算属性都可以
- extension不能添加designated initializer但是可以添加convenience initializer。
如果扩展一个protocol,就可以添加属性,还可以提供方法的实现
@H_301_10@protocol Flier { } extension Flier { func fly() { print("flap flap flap") } } struct Bird : Flier { }Bird继承了fly的实现。但是这种继承的方法没有多态的特性
@H_301_10@let i = Insect() i.fly() // whirr let f : Flier = Insect() f.fly() // flap flap flap (!!)如果仍想要多态的特性,需要在原来的protocol中加上fly方法
@H_301_10@protocol Flier { func fly() // * } extension Flier { func fly() { print("flap flap flap") } }也可以给泛型添加extension,如,使用extension给Array添加一个求最小值的方法
@H_301_10@extension Array where Element:Comparable { func myMin() -> Element { var minimum = self[0] for ix in 1..<self.count { if self[ix] < minimum { minimum = self[ix] } } return minimum } } let a = [1,4,5,0,8] print(a.myMin())myMin这个函数只有在Comparable对象实例化的Array中才有(Array本身是泛型,初始化的时候会将泛型实例化),如果Array中保存的对象不是Comparable的,那么就不会看见myMin这个函数
@H_301_10@let b = [UIColor.blue,UIColor.red] b.myMin() // 编译错误Umbrella Types
Any
Any是Swift中的通用类型,但需要一个Any对象的时候,可以使用任何的类型。和OC交互的时候,OC中的id类型都可以表示为Any。比如UserDefault,NSCoding中的参数。
取出来使用的需要进行类型转换:
@H_301_10@let ud = UserDefaults.standard let d = ud.object(forKey:"now") as? Date if d != nil { // ... }AnyObject
AnyObject是一个空protocol,所有的类型都自动实现了这个protocol。在Swift中Any是可以代表任何类型,AnyObject代表任何对象类型,Swift中任何类型都是对象类型,所以没有区别,但是OC中不是,所以AnyObject等价于OC中的id。
可以看一下类型转换的过程
@H_301_10@let s = "howdy" as AnyObject // String to NSString to AnyObject let i = 1 as AnyObject // Int to NSNumber to AnyObject let r = CGRect() as AnyObject // CGRect to NSValue to AnyObject let d = Date() as AnyObject // Date to NSDate to AnyObject let b = Bird() as AnyObject // Bird (struct) to Boxed type to AnyObjectAnyObject的使用也和id类似:
@H_301_10@class Dog { @objc var noise : String = "woof" @objc func bark() -> String { return "woof" } } class Cat {} let c : AnyObject = Cat() let s = c.noiselet s = c.noise这句话是可以编译过的,等价于OC中的[c noise],向一个id发送消息,在编译期间并不检查。
如果调用的时候加一个?号,那么即使实际的类型没有实现方法,也不会crash,但是返回的值是String?
@H_301_10@let c : AnyObject = Cat() let s = c.bark?()能这样用的前提条件是方法或者属性标记为@objc,或者类本身就是NSObject的子类。
AnyClass
AnyClass是AnyObject的子类,对应OC中的Class。