Swift的get与set与willSet与didSet详解

最近写程序越来越会偷懒了,可能也是因为对swift越来越熟悉了吧,以至于最近连if都不想写了都直接用三元运算符了。。这也是今天这篇文章的导火索,就是因为我有两个布尔变量,来判断是否是pulling和headPulling(简称p和hp),hp为true时p一定也为true,但p为true时hp就不一定为true,因为还会有footPulling的情况,你问为什么不是三个布尔变量?因为我懒啊!但我还想更懒,我完全不想去管p,而是想只改hp就可以完全满足我判断究竟是fp还是hp以及p的情况,该怎么样呢?于是我就想起来了以前看到过的get和set,但是虽然抄了过来,但具体意义什么的完全没深究过,这次就研究了一下!

 swift的计算属性

Google了一番才知道get和set这两位的术语是:Computed Properties. 即计算属性。其实从名字就可以看出来,其真正的作用是计算,而非存储。不少从OC转来的开发者可能一开始会将其按照原本的getter和setter方法来理解而导致一些疑惑。计算属性真正的用处是在于利用这两个方法间接的获取或者改变其他的变量或属性。

get

get方法在这两个方法中是必须的,意即如果要声明计算属性,则必须存在get方法,get方法的作用是在开发者用dot文法获取该变量的值的时候,返回一个固定的或经过计算的值。

set

set方法是一个可选方法,其会传入一个与所属变量类型相同的参数,这个参数的值即使用中被赋予的值,在set文法中,可以利用获取的参数的值,去计算并设定其他变量的值。如果要使用set方法,则必须包含get方法。

以下是官方开发者文档中给出的例子:

struct Point {
var x = 0.0, y = 0.0
}
struct Size {
var width = 0.0, height = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set(newCenter) {
origin.x = newCenter.x - (size.width / 2)
origin.y = newCenter.y - (size.height / 2)
}
}
}
var square = Rect(origin: Point(x: 0.0, y: 0.0),
size: Size(width: 10.0, height: 10.0))
let initialSquareCenter = square.center
square.center = Point(x: 15.0, y: 15.0)
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
// Prints "square.origin is now at (10.0, 10.0)"

其实只要稍微仔细看一下就可以理解get和set的用法,所需要注意的就是如下几点:

1. 无论是set还是get都不可以改变(或返回)自身的值。这个错误不会被编辑器发现,但会是运行时错误而被抛出异常。

2. set可以忽略参数声明而直接使用newValue的默认值,如上面的例子可以被改写成这样

set {
origin.x = newValue.x - (size.width / 2)
origin.y = newValue.y - (size.height / 2)
}

3. 如果要声明计算属性则必须声明get方法,只声明get方法的计算属性成为只读计算属性,将来的使用中该属性无法被赋值而只能获取其在get方法中返回的值。同时,只读计算属性可以简化写法,不用再按标准方式声明get方法。例如:

struct Cuboid {
var width = 0.0, height = 0.0, depth = 0.0
var volume: Double {
return width * height * depth
}
}
let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
// Prints "the volume of fourByFiveByTwo is 40.0"

以上便是计算属性的用法了,可是仔细一想就能发现,我其实需要的并不是计算属性,尽管我确实是想在我的hp改动的时候联动的改动p,这是计算属性中的set方法可以做到的事情,但是同时我并不需要get方法,但恰恰get方法又是计算属性所必须的,所以再深究一下就能发现我们这次的正题:willSet和didSet。

属性观察者

正如其名,willSet和didSet这两个方法会观察属性的值的改变,并在特定的时候做出特定的反应。每一次属性的值的改变都很调用设定好的属性观察者的方法,就算新的值和属性越来的值相同也不例外。

除了定义为lazy的存储属性外,这两个观察者方法可以在任何其他存储属性中声明。对于继承的情况,观察者方法同样适用于继承的存储属性,同时还增加了继承的计算属性,当然,你不用为不重写的父计算属性添加属性观察者,因为你可以直接在set中做到观察者所能做的事情。

同时需要注意,这两个方法之间没有必然的关系,你可以同时声明这两个方法或只声明其中任意一个。

但是这两个方法的调用时间和传入参数却有很大不同:

willSet方法会在新值被储存前调用,官方文档中使用了“just before”这个词,可能意思就是这个方法调用完了该变量就会立刻存储新值吧。同时willSet方法接收的参数是即将被存储的新值,即newValue,如果希望简化方法的话可以不书写参数而直接用dot文法调用该变量。

而didSet方法会在新值被存储后调用,官方文档中使用了“immediately”这个词,同上的猜测就是这个方法会在存储完新值后立刻调用。与willSet不同的是,didSet中的参数是改变之前值,即oldValue,同样允许简化书写。

以下是官方的例子:

class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// About to set totalSteps to 200
// Added 200 steps
stepCounter.totalSteps = 360
// About to set totalSteps to 360
// Added 160 steps
stepCounter.totalSteps = 896
// About to set totalSteps to 896
// Added 536 steps

这个方法就可以完美解决我遇到的问题,我只要在hp的willSet观察者方法里直接设定p的值就好了,有关get和set以及willSet和didSet也就说得差不多了,这两个知识点并不是什么难点,但感觉真正会实用的人也是比较少的,所以特来分享一下。

iOS求道录 – swift开发问题总结 – 1

从最开始学习iOS开始,就习惯把遇到的问题记录下来,不知不觉也积累了不少,我相信也许其他人也会遇到这样那样的问题,可以大家拿出来一起分享,或者有问题我们一起解决,或者觉得我的解决方法存在问题,也欢迎指正。每个篇文章更新10个问题。若评论中存在好的问题,我也会在征求评论作者同意之后更新到文章之中。另外抱歉起了个比较中二的名字,其实感觉不会有那么厉害。(:з」∠)

我就按照我记录的顺序来了,因为是刚刚入门时候就开始记录了,所以可能前面的问题看起来都会比较幼稚

1.如何修改Tabbar item的selected color

首先是简单的方法,整体修改选中后颜色,在随便哪个tabbar的子VC里使用如下代码都可以修改其选中后颜色,下面修改为蓝色

self.tabBarController?.tabBar.tintColor = UIColor.blueColor()

而如果是希望不同的item使用不同的选中后颜色,则可以利用storyboard这样快捷实现(前提是你利用storyboard实现TabbarVC。。)
选中某个tabbaritem之后,在有侧边栏里这样修改:

blog-1

2.判断字符串之间关系

利用rangeofstring函数,其会返回一个布尔变量,示例:

var myString = "This is a string test"
if myString.rangeOfString("string") {
    print("exists")
}

3.将控件的默认语言修改为中文

点击工程名→寻找Info选项→在其下拉栏中寻找localization→将其value修改为china

4.tableview cell无法正常显示

这个真的是初学者问题,还弄了半天,也是当时的Xcode有点小问题,所以浪费了很多时间,就是tableview的delegate的有关section和row的数量的两个函数的返回值要设为非0,当时的Xcode版本,如果改变numberOfSection这个函数的返回值(在TableviewController中,已经默认提供了这两个函数且返回值均为0)为非0,则会报错,但是这个错误在点击运行之后就会消失,当时并不知道,看到报错以为不能改,就改回0,然后各种百度谷歌也找不到答案,还找到了很多奇葩的理由来解释自己的错误,现在看看好蠢。

5.在storyboard中关联class文件的问题

在storyboard中拖入的是什么controller(例如TableviewController),其所关联的class就必须是该类型的view controller,否则在使用segue跳转是会报错并导致程序崩溃。

这个也是早期的问题了,现在我使用storyboard的话所有的controller都是基础的UIViewController,因为遇到过很多问题,即比如如果使用UITableViewController无法正常显示广告条的问题。

6.如何添加storyboard辅助线

storyboard中,双击某个view,再按shift+command+ -会添加横向的辅助线,shift+command+ | 会添加纵向的辅助线。删除辅助线的方法就是点击辅助线后快速移出view到旁边的空白处

7.引用protocol时报错

主要是在关联delegate和datasource时发现的,以前遇到这种情况通常都去搜索引擎了,后来发现自己可以很轻松解决:

这种情况是你关联了这两个对应的数据源,却没有实现他的回调方法,像tableview和pickerview等等都需要调用特定的回调方法才会不报错。

在这种情况下的方法,就是:右键点击datasource →jump to definition→寻找该方法下不带option标签的func→复制这些func到view controller,实现之,即可。

以上是当时写的解决办法

8.将Int等其他类型的变量变为字符串输出

当时真的是很郁闷,我本身是学软件出身的,但完全不记得转义符这个东西。。。找了很多办法想把一个int在swift里转化为string来输出,最后突然想起了转义符,然后就没有然后了。。

“\(times[row])”,这样就可以输出原本为int数组的times中得值了。

当然现在也知道了format函数的用法,但就不在这个问题下面讨论了

9.读取与写入plist的方法

var partArray : NSArray?   //声明存储plist文件数据的数组
let ban = NSBundle.mainBundle()//获取mainbundle的值,这里面保存了plist文件的地址(注意plist文件必须创建在主文件路径下才可以)
        let plistPath = ban.pathForResource("myNumber" , ofType : "plist”)//连接plist文件
        let partFromP = NSMutableDictionary(contentsOfFile: plistPath!)//将plist文件存入一个数组
        partArray = partFromP?.objectForKey("肩部") as? NSArray  //通过key值寻找值

早时自己写着玩的时候,遇到了数据存储问题,当时觉得coredata太麻烦,于是用plist来做存储。。现在发现coredata会了之后比plist的存储简单太多,毕竟要用plist来存东西是要自己时刻记着数据的表结构的。。
不过还是提供一下关于plist写入的方法:

IOS设备写入plist文件时,不能使用mainbundle来寻找文件路径,这样只会导致写入失败,以下是正确地寻找文件路径的方法:

let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as NSArray
let documentsDirectory = paths.objectAtIndex(0) as! NSString
let path = documentsDirectory.stringByAppendingPathComponent("GameData.plist")
//以上三行获取文件地址就不细说了,下面这行是将文件中的值保存到一个NSDictionary中
let mydict = NSDictionary(contentsOfFile: path)
//获取用户数组
var partArray : NSArray = mydict?.objectForKey("users") as! NSArray
//需要新加的数据
var object = [NSDictionary]()
object.append(["part" : "上臂" , "move" : "肩部挺举" ,"group":"3","times":"15"])
//返回一个新的数组
partArray =  partArray.arrayByAddingObjectsFromArray(object)

10.NSArray的arrayByAddingObject方法解释

这个方法会返回一个新的数组,而原数组未改变,所以可以这样更新原数组

partArray1 =  partArray1?.arrayByAddingObject(object)