Swift4 学习笔记——高级篇
变量Define and Call函数初始化使用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,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,drawing)
函数作为返回值可以引出一个模式,类似于微型的工厂模式: 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,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,cornerRadius: 8)
p.stroke()
})
Define and Call就是定义函数的同时调用函数,这种形式: {
// ... 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或者嵌套函数。有两个好处,一个是可见的范围缩小了,避免冲突和误用,二是可以捕获上下文的变量,不需要定义一系列参数传递。 函数装饰器 & escaping看一个函数: 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意思就是在函数执行完之后也要保留这些捕获的变量。 Curried Functions还使用画图的例子,上面的画图例子的圆角半径为8是写死的,如果不想写死,代码应该这样写: 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)
这里还有另外一种思路: 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。 继承 & 多态
继承的写法: // 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)
}
ProtocolSwift中一个类实现一个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协议。
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)
其他的字面变量协议有: 泛型类型是对象实例的模板,泛型就是类型的模板。不同的是,对象的的实例化是在运行期间进行的,模板的实例化是在编译期间进行的。 泛型函数 func dogMakerAndNamer<WhatType:Dog>(_:WhatType.Type) -> WhatType {
let d = WhatType.init(name:"Fido")
return d
}
在这个函数中,WhatType是一个占位符,在编译的时候会替换成真正的类型。对这个类型的要求是继承自Dog.
泛型协议 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
ExtensionsExtensions类似于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() // 编译错误
Umbrella TypesAny 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 可以看一下类型转换的过程 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 class Dog {
@objc static var whatADogSays : String = "woof"
}
class Cat {}
let c : AnyClass = Cat.self
let s = c.whatADogSays (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |