示例代码来源于 《iOS 11 Programming Fundamentals with Swift》
使用Define and Call函数进行变量初始化,Define and Call函数内容在下节。
let timed : Bool = {
if val == 1 {
return true
} else {
return false
}
}()
使用lazy关键字可以让变量在使用的时候再初始化。
class MyView : UIView {
lazy var arrow = self.arrowImage()
func arrowImage () -> UIImage {
// ... big image-generating code goes here ...
}
}
如果没有lazy关键字,是不能通过编译的,因为MyView还没有初始化,不能调用实例方法。
一个比较常用的技术手段是结合使用define and call的函数来初始化lazy的成员变量:
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
}()
无论是类成员还是全局变量,都可以定义成计算变量,就是在使用的时候计算一个值出来,而不是保存这个值。也就是getter和setter。
var now : String {
get {
return Date().description
}
set {
print(newValue)
}
}
var now : String {
return Date().description
}
和OC的KVO类似。
var s = "whatever" {
willSet {
print(newValue)
}
didSet {
print(oldValue)
// self.s = "something else"
}
}
Swift中可以在函数体中定义函数,如果一个函数B只被函数A使用,那可以将B函数定义在函数An内。
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中函数是对象,所以函数也是可以作为参数传递的。
func doThis(_ f:() -> ()) {
f()
}
func whatToDo() {
print("I did it")
}
doThis(whatToDo)
同样,函数可以作为函数的返回值。函数作为参数或者返回值的一个好处是可以增加间接性,使得在调用的时候不必知道函数的定义,只要知道函数的签名就行。
一个比较实用的例子:
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相关的部分是可以提取的,因为每次画图都一样。定义一个函数:
func imageOfSize(_ size:CGSize, _ whatToDraw:() -> ()) -> UIImage {
UIGraphicsBeginImageContextWithOptions(size, false, 0)
whatToDraw()
let result = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return result
}
上边的函数就是纯粹的图片制作过程,将画图的部分“外包”了出去,调用者将画图部分的函数传入即可。
使用代码:
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, height:20), drawing)
iOS 10已经有一个函数叫做UIGraphicsBeginImageContext和imageOfSize提供差不多的功能。
函数作为返回值可以引出一个模式,类似于微型的工厂模式:
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。
UIView.animate(withDuration:0.4,
animations: {
() -> () in
self.myButton.frame.origin.y += 20
},
completion: {
(finished:Bool) -> () in
print("finished: \(finished)")
})
匿名函数很像OC中的block,说明下上边的代码。
UIView.animate(withDuration: animations: completion:)中的aimations参数是一个()->Void类型的函数,compeltion是一个(Bool)->Void类型的函数。使用者可以定义两个这样类型的函数然后传入,也可以在调用的原位定义一个匿名函数。() -> () in 这行代码的意思是下边的函数是一个() -> ()类型。匿名函数的参数和返回值写在了大括号的第一行。
匿名函数有很多书写的简化规则,看到能读懂就行,不必非得使用这些语法糖。
UIView.animate(withDuration:0.4,
animations: {
// * (no in line)
self.myButton.frame.origin.y += 20
}, completion: {
(finished:Bool) in
print("finished: \(finished)")
})
同样,在completion参数中省略的返回值。
UIView.animate(withDuration:0.4,
animations: {
// * (no in line)
self.myButton.frame.origin.y += 20
}, completion: {
finished in
print("finished: \(finished)")
})
UIView.animate(withDuration:0.4,
animations: {
self.myButton.frame.origin.y += 20
}, completion: {
print("finished: \($0)")
})
UIView.animate(withDuration:0.4,
animations: {
self.myButton.frame.origin.y += 20
}) {
print("finished: \($0)")
}
当匿名函数是函数的最后一个参数的时候,可以将匿名函数的大括号部分写在函数调用之后。
有了匿名函数,上边画图的代码可以写在一起:
let image = imageOfSize(CGSize(width:45, height:20), {
() -> () in
let p = UIBezierPath( roundedRect: CGRect(x:0, y:0, width:45, height:20), cornerRadius: 8)
p.stroke()
})
就是定义函数的同时调用函数,这种形式:
{
// ... code goes here
}()
和匿名函数相似,不同的是,匿名函数提供的是一个过程,Define and Call提供的是一个过程的结果,例子:
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或者嵌套函数。有两个好处,一个是可见的范围缩小了,避免冲突和误用,二是可以捕获上下文的变量,不需要定义一系列参数传递。
看一个函数:
func countAdder(_ f: @escaping () -> ()) -> () -> () {
var ct = 0
return {
ct = ct + 1
print("count is \(ct)")
f()
}
}
这个函数接受一个()->()函数,返回一个()->()函数。作用是调用传入的函数f,并且记录打印函数f调用的次数。
使用:
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意思就是在函数执行完之后也要保留这些捕获的变量。
还使用画图的例子,上面的画图例子的圆角半径为8是写死的,如果不想写死,代码应该这样写:
func makeRoundedRectangleMaker(_ sz:CGSize, _ r:CGFloat) -> () -> UIImage {
return {
imageOfSize(sz) {
let p = UIBezierPath( roundedRect: CGRect(origin:CGPoint.zero, size:sz),
cornerRadius: r)
p.stroke()
}
}
}
let maker = makeRoundedRectangleMaker(CGSize(width:45, height:20), 8)
这里还有另外一种思路:
func makeRoundedRectangleMaker(_ sz:CGSize) -> (CGFloat) -> UIImage {
return {
r in
imageOfSize(sz) {
let p = UIBezierPath( roundedRect: CGRect(origin:CGPoint.zero, size:sz),
cornerRadius: r)
p.stroke()
}
}
}
let maker = makeRoundedRectangleMaker(CGSize(width:45, height:20))
self.iv.image = maker(8)
这段代码的不同之处在于makeRoundedRectangleMaker接受的还是一个参数,但是返回的函数多了一个参数CGFloat。闭包内同样也捕获了返回函数的参数r,在使用的时候再提供r的值。这种方式就叫做Curried Function。
继承的写法:
// Cat是Kitten的基类
class Dog {
func barkAt(cat:Kitten) {}
}
class NoisyDog : Dog {
override func barkAt(cat:Cat) {} // or Cat?
}
初始化函数的继承
上述规则可以这样理解,如果基类的初始化函数能满足子类的初始化需求,那么子类可以不写初始化函数,使用基类的。另外,子类也可以定义一些辅助函数(convenience initializer)来使用基类的designated initializer初始化。如果子类定义了自己的designated initializer 那么很可能的情况是基类的designated initializer已经不能满足子类的初始化需求了,那么所有的基类的designated initializer就不能成为子类的初始化函数了。(但是在子类的designated initializer中必须调用基类的designated initializer)。因为基类的convenience initializer中肯定是调用了基类的designated initializer,所以子类如果重写了所有的designated initializer,那么基类的convenience initializer中的调用就变成了调用子类的designated initializer,所以又可以初始化子类了,所以就可以继承了。
required 初始化函数
在基类中被标记为required的初始化函数,子类必须也有这个初始化函数。“必须有”的意思是:如果子类不能根据上述规则通过继承拥有这个初始化函数,就必须重写它。
这条规则的使用场景:
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关键字,要求所有子类都实现这个初始化函数。
class Dog {
var name : String
required init(name:String) {
self.name = name
}
}
属性和方法的重写
属性分为两类,一类是普通变量属性,一类是计算变量属性。
class Dog {
var name : String //普通变量属性
var age : Float { //计算变量属性,只读
return 1 + 10
}
required init(name:String) {
self.name = name
}
}
子类可以使用计算变量重写基类的普通变量属性,但是不能改变访问权限
class NoisyDog : Dog {
override var name: String{
set { self.name = newValue } //如果注释掉这一句,编译错误
get { return "dog" }
}
}
子类可以重写基类的计算变量的属性,并且可以改变访问权限
class NoisyDog : Dog {
override var age: Float{
set { self.age = newValue } //可以添加setter
get { return 20 }
}
}
向下类型转换
let d : Dog? = NoisyDog()
let d2 = d as? NoisyDog
d2?.beQuiet()
类型判断
let d : Dog? = NoisyDog()
if d is NoisyDog {
let d2 = d as! NoisyDog
d2.beQuiet()
}
上述判断和转化代码可以合并成一句:
if let d2 = d as? NoisyDog{
print(d2)
}
Swift中一个类实现一个Protocol需要显式声明。Protocol可以被struct或者enum实现。
protocol Flier {
func fly()
}
protocol的组合
如果一个函数接受一个参数,要求这个参数实现两个Protocol,可以使用Protocol的组合
func f(_ x: CustomStringConvertible & CustomDebugStringConvertible) {
}
protocol和class也可以组合
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实现。
@objc protocol Flier {
@objc optional var song : String {get}
@objc optional func sing()
}
class Bird : Flier {
func sing() {
print("tweet")
}
}
Bird只实现了一个sing方法。在这种情况下,Swift不知道song属性是不是安全的。
let f : Flier = Bird()
let s = f.song // s is an Optional wrapping a String
s将是一个String?而不是String,如果协议中song本身就是String?那么s将是一个String??。这一点对于方法的返回值也适用。
@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
对于方法的调用需要先拆箱
let f : Flier = Bird()
f.sing?()
Literal Convertibles
Swift中字面变量的转换是靠实现了一个或者多个协议来实现的。
struct Nest : ExpressibleByIntegerLiteral {
var eggCount : Int = 0
init() {}
init(integerLiteral val: Int) {
self.eggCount = val
}
}
Nest实现了ExpressibleByIntegerLiteral协议,就可以使用Integer的字面变量来表示一个Nest,比如有这样一个函数:
func reportEggs(_ nest:Nest) {
print("this nest contains \(nest.eggCount) eggs")
}
接受的是一个Nest参数,就可以传入一个字面变量
reportEggs(4) // this nest contains 4 eggs
ExpressibleByIntegerLiteral,这个协议要求实现init(integerLiteral:)方法。Nest实现了。
注意,这个协议是关于字面变量的,并不是Int类型到Nest的转换。下边代码是不能通过编译的:
var x = 4
reportEggs(x)
其他的字面变量协议有:
- ExpressibleByNilLiteral
- ExpressibleByBooleanLiteral
- ExpressibleByIntegerLiteral
- ExpressibleByFloatLiteral
- ExpressibleByStringLiteral
- ExpressibleByExtendedGraphemeClusterLiteral
- ExpressibleByUnicodeScalarLiteral
- ExpressibleByArrayLiteral
- ExpressibleByDictionaryLiteral
类型是对象实例的模板,泛型就是类型的模板。不同的是,对象的的实例化是在运行期间进行的,模板的实例化是在编译期间进行的。
泛型函数
func dogMakerAndNamer<WhatType:Dog>(_:WhatType.Type) -> WhatType {
let d = WhatType.init(name:"Fido")
return d
}
在这个函数中,WhatType是一个占位符,在编译的时候会替换成真正的类型。对这个类型的要求是继承自Dog.
Optional就是通过泛型来实现的,它是一个泛型的enum。
enum Optional<Wrapped> { case none case some(Wrapped) init(_ some: Wrapped) // ... }
泛型协议
Self
如果一个protocol的方法声明中使用了Self,这个Self表示实现这个protocol的那个类的当前实例本身。相当于在类的实现中的self。也就是说Self是protocol中的self(因为此时不知道具体类型是啥)。
associated type
protocol Flier {
associatedtype Other
func flockTogetherWith(_ f:Other)
func mateWith(_ f:Other)
}
一个具体的类实现这个protocol的时候需要将Other的部分替换成具体的类型:
struct Bird : Flier {
func flockTogetherWith(_ f:Bird) {}
func mateWith(_ f:Bird) {}
}
上边的字面变量协议就是泛型协议。
public protocol ExpressibleByIntegerLiteral {
associatedtype IntegerLiteralType
public init(integerLiteral value: Self.IntegerLiteralType)
}
泛型类
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")
可以使用多个类型的泛型
func flockTwoTogether<T, U>(_ f1:T, _ f2:U) {}
泛型的约束
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协议。
显示的实例化泛型
protocol Flier {
associatedtype Other
}
struct Bird : Flier {
typealias Other = String
}
或者
class Dog<T> {
var name : T?
}
let d = Dog<String>()
组合使用泛型协议和泛型对象
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
泛型的继承
class Dog<T> {
func speak(_ what:T) {}
}
class NoisyDog<T> : Dog<T> {}
或者在继承的时候就实例化
class NoisyDog : Dog<String> {
override func speak(_ what:String) {}
}
Associated Type Chains
protocol Fighter {
associatedtype Enemy where Enemy : Fighter
}
Enemy 占位符要求也是一个Fighter,因为Enemy本身是在Fighter中使用的,所以需要使用where语句。
实例化
struct Soldier : Fighter {
typealias Enemy = Archer
}
struct Archer : Fighter {
typealias Enemy = Soldier
}
struct Camp<T:Fighter> {
var spy : T.Enemy?
}
spy这个变量的类型是Fighter的Enemy,但是因为Fighter也还没确定,所以使用T.Enemy。
Enemy是一个占位符,Fighter是一个泛型。在Camp中,T也是占位符,代表一个实现了Fighter的类型。所以spy是一个<占位符>.<占位符>的形式。随后的实例化的时候,会替换所有的占位符。
var c = Camp<Soldier>()
c.spy = Archer()
泛型约束的where语句
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 {}
更复杂些的约束
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。
flockTogether(Bird()) // okay
flockTogether(Insect()) // compile error
因为Insect的Other是Bird,而Bird不是Equatable的。所以第二句编译错误。
如果使用”==”操作符,意思是类型必须是后边的类型。
func flockTogether<T> (_ f:T) where T:Flier, T.Other == Walker {}
T.Other必须是Walker类型,不能是subclass。
Swfit中append(contentsOf:)函数的实现就使用了==操作符
mutating func append<S>(contentsOf newElements: S)
where S:Sequence, S.Element == Character
Extensions类似于OC中的Category,能给已有的类添加功能。
extension Array {
mutating func shuffle () {
for i in (0..<self.count).reversed() {
let ix1 = i
let ix2 = Int(arc4random_uniform(UInt32(i+1)))
self.swapAt(ix1, ix2)
}
}
}
如果扩展一个protocol,就可以添加属性,还可以提供方法的实现
protocol Flier { } extension Flier {
func fly() {
print("flap flap flap")
}
}
struct Bird : Flier { }
Bird继承了fly的实现。但是这种继承的方法没有多态的特性
let i = Insect()
i.fly() // whirr
let f : Flier = Insect()
f.fly() // flap flap flap (!!)
如果仍想要多态的特性,需要在原来的protocol中加上fly方法
protocol Flier {
func fly() // *
}
extension Flier {
func fly() {
print("flap flap flap")
}
}
也可以给泛型添加extension,如,使用extension给Array添加一个求最小值的方法
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这个函数
let b = [UIColor.blue, UIColor.red]
b.myMin() // 编译错误
Any
Any是Swift中的通用类型,但需要一个Any对象的时候,可以使用任何的类型。和OC交互的时候,OC中的id类型都可以表示为Any。比如UserDefault,NSCoding中的参数。
let ud = UserDefaults.standard
ud.set("howdy", forKey:"greeting")
ud.set(Date(), forKey:"now")
取出来使用的需要进行类型转换:
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。
可以看一下类型转换的过程
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 AnyObject
AnyObject的使用也和id类似:
class Dog {
@objc var noise : String = "woof"
@objc func bark() -> String {
return "woof"
}
}
class Cat {}
let c : AnyObject = Cat()
let s = c.noise
let s = c.noise这句话是可以编译过的,等价于OC中的[c noise],向一个id发送消息,在编译期间并不检查。
如果调用的时候加一个?号,那么即使实际的类型没有实现方法,也不会crash,但是返回的值是String?
let c : AnyObject = Cat()
let s = c.bark?()
能这样用的前提条件是方法或者属性标记为@objc,或者类本身就是NSObject的子类。
AnyClass
AnyClass是AnyObject的子类,对应OC中的Class。
class Dog {
@objc static var whatADogSays : String = "woof"
}
class Cat {}
let c : AnyClass = Cat.self
let s = c.whatADogSays