Swift中级

亲,我的简书已不再维护和更新了,所有文章都迁移到了我的个人博客:https://mikefighting.github.io/,欢迎交流。

本文源于对WWDC2014--Intermediate Swift的总结。

为什么需要Optional?

我们在对某个对象操作时可能会返回错误的结果:比如我们将某个字符串转为Int型,执行下面的指令:

let age = response.toInt()

这response如果是用户输入的,可能会输入不确定的数值Do you konw?那么就会得到错误的结果,在OC中遇到类似情况我们怎么处理呢?我们有如下的值可以表示错误:


但是你必须从不同的接口中去选择相应的错误类型,并且要记住这些错误类型,并且如果你忘记了判断,那么编译器是不会提醒你的。为了解决这个问题,Swift中引入了Optional的概念,将这种可能是nil的值进行打包。它可以表示上述所有错误的类型,同时,如果我们使用Optional,就一定要对它进行拆包,使用!号,如果不拆包会造成编译器错误,这就在编译期杜绝了可能在运行时出的错误,如下所示:
Need Unwrap

也可以使用Optional Binding将判断是否有值和拆包结合在一起使用:if let

var neighbors = ["Alex", "Anna", "Madison", "Dave"] let index = findIndexOfString("Anna", neighbors)if let indexValue = index { 
    println("Hello, \(neighbors[indexValue])")
} else {
    println("Must've moved away")
}

当然我们还可进一步使用Optional Binding--Optional Chain:

Optional Chain Binding

在Optional Chain中,只要其中一个Optional的值是nil,那么整个Optional Chain都将是nil,并且不会再执行接下来的取值,如果不是nil则继续执行。这样让我们的代码更加简洁更加安全。
Swift中的Optional其实是个枚举:

enum Optional<T> {
   case None
   case Some(T)
}

Swift中的内存管理

在Swift中也用的是ARC,也容易出现循环引用,这时需要使用weak属性。需要注意的是

weak引用的类型是Optional的。
Binding该Optional Type将会产生一个强引用。
如果仅仅是判断即用if判断,则不会产生强引用。

例如:

if let tenant = apt.tenant {
  tenant.buzzIn()
 } 

但是有些时候我们同时需要weak,又同时需要非Optional的。那么该怎么办?我们需要unowned属性,它也是weak的。

class Person {
    var card: CreditCard?
}class CreditCard {unowned let holder: Person 
    init(holder: Person) {
        self.holder = holder
  } 
} 

这说明holder没持指向Person,但是holder离开了Person它就不存在了。unowned很像unsafe unretain

Swift中的初始化

在Swift的初始化中需要谨记

所有的变量在使用前必须初始化
设定完自己所有的变量之后再调用Super的初始化方法

在下面这个初始化的例子中:

init wrong

这样在init方法中没有初始化完自己的hasTurbo变量就直接调用super方法是会在编译的时候报错的,Swift为什么要这么做呢?因为可能会出现如下的情况:
why init wrong

也就是说在父类的init方法中可能会调用filGasTank()这个方法,而这个方法被子类所覆盖了,所以这时候就可能发生意向不到的bug。
初始化方法的覆盖也可能会产生问题:
比如我们有这样一个Car的类:

class Car {
    var paintColor: Color
    func fillGasTank() {...}
    init(color: Color) {
        paintColor = color
        fillGasTank()
    }
} 
class RaceCar: Car {
    var hasTurbo: Bool
    init(color: Color, turbo: Bool) {
        hasTurbo = turbo
        super.init(color: color)
} 
    convenience init(color: Color) {
        self.init(color: color, turbo: true)
} 
    convenience init() {
        self.init(color: Color(gray: 0.4))
} } 

class FormulaOne: RaceCar {
    let minimumWeight = 642
    
    // inherited from RaceCar
    /*init(color: Color, turbo: Bool) {
        hasTurbo = turbo
        super.init(color: color)
    }
    convenience init(color: Color) {
        self.init(color: color, turbo: true)
    }
    convenience init() {
        self.init(color: Color(gray: 0.4))
    }
    */
} 

上面注释的内容是从父类中继承过来的,如果我们在子类中调用convenience init(color: Color)这个方法的时候,想让turbo这个参数的默认值为false,这时候我们就需要覆盖掉父类的convenience init方法了。这时我们需要实现自己的designed initializer

class FormulaOne: RaceCar {
    let minimumWeight = 642
    init(color: Color) {
        super.init(color: color, turbo: false)
} 
    // not inherited from RaceCar
    /*init(color: Color, turbo: Bool)
    convenience init()
    */
} 

这样以后被注释的内容就不会再被继承了。就会直接掉用子类的designed init方法了。

懒加载属性

如果我们的某个属性需要很大的性能消耗,那么我们希望在使用的时候再创建该类,那么我们不必像在OC中那样重写其get方法,我们只需要在变量声明的前面加上lazy关键字即可。

lazy var color:UIColor = UIColor.red

这样就可以声明了一个懒加载的属性了。

Closures

基本用法

Swift中Array的sort方法实现了Closure,我们来看下:

var clients = ["Pestov", "Buenaventura", "Sreeram", "Babbage"]

clients.sort({(a: String, b: String) -> Bool in 
return a < b }) 

println(clients)
// [Babbage, Buenaventura, Pestov, Sreeram]

这样就实现了数组中的元素排序。
但是基于Swift强大的类型推断功能,我们可以将其简化为:

clients.sort({ a, b in
return a < b
})

因为这个Closure是有返回值的,所以编译器可以再次推断,所以我们可以这样写

clients.sort({ a, b in a < b })

编译器还可以推断出其参数值,所以,我们这里可以写成

clients.sort({$0 < $1})

因为我们还有尾随闭包,所以我们可以进一步简化

clients.sort{$0 < $1}

Functional Programming

我们有很多函数式编程的高阶函数可以供调用:

let result = words.filter{ $0.hasSuffix("gry")}.map{$0.uppercaseString}

这样我们就可以找到所有以gry结尾的单词,并且将其转化为大写字母。如果这时结果是

ANGRY
HUNGRY

我们还可以调用reduce方法将其和成一个字符串

let reducedResult = result.reduce("HULK"){"\($0) \($1)"}

这时结果如下:

HULK ANGRY HUNGRY

函数值

比高可以传递一个函数,例如:

 numbers.map {
        println($0)
} 
numbers.map(println)    // 可以将一个函数传递过去


var indexes = NSMutableIndexSet()
numbers.map {
    indexes.addIndex($0)
} 

numbers.map (indexes.addIndex) // 可以将一个Method传过去

闭包是一个ARC对象

我们可以声明一个Closure属性:

var onTempratureChange: (Int) -> Void = {}
func logTemperatureDifferences(initial: Int) {
    var prev = initial
    onTemperatureChange = { next in
        println("Changed \(next - prev)°F")
prev = next 
} 

因为function也是closure,那么我们可以这样写:

func logTemperatureDifferences(initial: Int) {
    var prev = initial
    func log(next: Int) {
        println("Changed \(next - prev)°F")
prev = next } 
    onTemperatureChange = log

闭包的循环引用问题

和OC中的Block一样,Swift中也会出现循环引用的问题,我们来看看怎样解决:

class TemperatureNotifier {
    var onChange: (Int) -> Void = {}
    var currentTemp = 72
    init() {
        onChange = { temp in
currentTemp = temp   } // error: requires explicit 'self' 
  } 
} 

如果出现上面的循环引用问题,编译器会直接报错的,所以我们可以用上文提到的unowned来解决。我们可以将init()方法用下面的来取代:

init() { 
    unowned let uSelf = self
    onChange = { temp in
      uSelf.currentTemp = temp
    }

但是这样写还会出现一个问题,就是如果别处有一份逻辑一样的代码,某个人不注意拷贝过来了忘记将self改成uSelf,或者这个方法很长,写到下面的是忘记了将self改成uSelf,那么就会出现内存泄漏的问题。为了解决这个问题Swift中提出了下面的优雅做法:

init() {onChange = {[unowned self] temp in 
self.currentTemp = temp 
} } 

Pattern Matching

switch中可以有范围,字符串和数字,并且Enum中可以关联属性,比如:

// case中含有范围
func describe(value: Int) { switch value { 
case 0...4: println("a few") 
case 5...12: println("a lot") 
      default:
        println("a ton")
} } 
// case中

enum TrainStatus {
    case OnTime
    case Delayed(Int)
}

使用的时候如下:

switch trainStatus {
  case .OnTime:
} 
  println("on time")
case .Delayed(let minutes)
                        :
println("delayed by \(minutes) minutes")

我们可以对这个delay做各种各样的匹配:

switch trainStatus {
  case .OnTime:
    println("on time")
  case .Delayed(1):
    println("nearly on time")
  case .Delayed(2...10):
    println("almost on time, I swear")
  case .Delayed(_):
    println("it'll get here when it's ready")

Pattern Compose

也就是说Pattern可以组合出现,一个Pattern中可以包含其它的Pattern,比如对上文的TrainStatus再做以Pattern Compose:

enum VacationStatus {
    case Traveling(TrainStatus)
    case Relaxing(daysLeft: Int)
} 

switch vacationStatus {
  case .Traveling(.OnTime):
    tweet("Train's on time! Can't wait to get there!")
  case .Traveling(.Delayed(1...15)):
    tweet("Train is delayed.")
  case .Traveling(.Delayed(_)):
    tweet("OMG when will this train ride end #railfail")
  default:
  print("relaxing")

Type Pattern

Pattern不仅仅可以作用于Enum,还可以作用于动态的类型,如:Class

func tuneUp(car: Car) {
    switch car {
      case let formulaOne as FormulaOne:
        formulaOne.enterPit()
      case let raceCar as RaceCar:
        if raceCar.hasTurbo { raceCar.tuneTurbo() }
        fallthrough
      default:
        car.checkOil()
        car.pumpTires()
} } 

这样在多态中就会变得非常有用了。

Tuple Patterns

Tuple pattern有其极其强大的功能,其可以对tuple的各个数值做以类型匹配。

let color = (1.0, 1.0, 1.0, 1.0)
switch color {
  case (0.0, 0.5...1.0, let blue, _):
    println("Green and \(blue * 100)% blue")
  case let (r, g, b, 1.0) where r == g && g == b:
    println("Opaque grey \(r * 100)%")

我们甚至可以对其中的各个数值做以相应的模式匹配。

Pattern Matching的应用PList校验

比如我们有下面的方法来校验Plist中的内容是否有效

func stateFromPlist(list: Dictionary<String, AnyObject>)
  -> State?
stateFromPlist(["name": "California",
                "population": 38_040_000,
                "abbr": "CA"])

这时我们要对population的值做以限制,如果是字符串返回nil,如果是超过某个范围的时候返回nil,如果是abbr中字母的个数大于2时候我们也返回nil,利用tuple pattern matching的强大特性,我们可以这样去做:

func stateFromPlist(list: Dictionary<String, AnyObject>)
  -> State? {

switch (list["name"], list["population"], list["abbr"]) { 
case ( 
        .Some(let listName as NSString), 
        .Some(let pop as NSNumber),
        .Some(let abbr as NSString)
      ) where abbr.length == 2:
    return State(name: listName, population: pop, abbr: abbr)
  default:
return nil 
   } 
} 

这就利用了tuple和限制想结合的方式优雅的解决了这个问题。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容