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)

A new easy way to Screenshot&Share

Put a website in your Favorites or Bookmark? Now you can put it in your photos!

  • Support both website and markdown files

    Easy share present your extremely easy way to get a screenshot from a website or even markdown files. Open the website or markdown file and click the button and you will get your screenshot

  • Full size screenshot

    The screenshot you take will be full size of the website you opened, so you can share with your friends on social apps, or if you want, save to your photos so the next time you want to review the website you won’t have to open your safari and search the bookmark.

  • Efficient ways to open a URL

    We present many easy ways for you to open a URL. You can scan a QR code with a URL. Or you can input the address manually. What’s more, if you copy a URL from safari or some other apps, you don’t have to paste it, our app will recognize it automatically and auto paste it for you.

  • Efficient markdown editor

    We also present a efficient markdown keyboard for users who want to write a markdown article. And of course several markdown themes you can switch easily

 

  • The url you copy should be start with ‘http://’ or ‘https://’

PresentViewController提供一个半透明的viewcontroller

相信不少人的项目里都会有这样一个需求,例如做个自己的actionsheet,或者弹出个自己做的分享页面,苹果自己的办法就是用presentviewcontroller提供一个新的controller,例如alertController。而当我们需要自己做的时候,就可以有两种方法,一种是addSubview,同时利用动画来实现,这样也可以做到很不错的效果,但毕竟不是官方做法,所以这里不做详解,我们主要来讨论下官方用法,利用presentViewController的方法来展示一个新的controller。

很多人可能会直接就这样写了:

let vc = whatEverNameViewController()
vc.view.alpha = 0.5
self.presentViewController(vc, animated: true, completion: nil)

这样很简单,你会发现你得到了一个黑屏,于是你转而Google一下,发现绝大多数解决方法都是说利用storyboard ID来声明VC,当然这样确实是一个解决办法,但如果就是没用storyboard而是用手写的VC呢,于是继续Google,发现要设定modalPresentationStyle,于是你手指翻动,加上了这样一句:

self.modalPresentationStyle = .CurrentContext

加上之后跑一遍,结果只有动画过程中半透明,动画结束了还是黑屏,原来,这个只是IOS7之前的写法,在IOS8及以后的版本中,apple为了配合自己的alertController,将这个方法从rootViewController中移到了展示的controller中,所以正确的写法现在变成了这样:

let vc = whatEverNameViewController()
vc.view.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5)
vc.modalPresentationStyle = .OverCurrentContext
vc.modalTransitionStyle = .CoverVertical
self.presentViewController(vc, animated: true, completion: nil)

注意到一个小小的不同了么?是的,不要直接设定view的alpha值,这样会导致所有的subview都变得透明,而应该是backgroundColor的alpha值,这样subview便不会受到影响。
至于怎么返回上一个VC,其实只要使用dismissViewControllerAnimated这个方法就可以,加个tap手势或者加个按钮事件都可以

Swift 2.2 is available now!

在这个让人略失误的let us loop you in 发布会之后,苹果开放了包含Swift 2.2 版本的Xcode7.3版本的下载。这是Swift开源之后的第一次发布官方版本,这也是第一个包含非Apple官方程序员贡献者的版本,根据Release notes的统计,该版本共包含了212位非Apple官方程序员的贡献,包括但不限于以下几个:

虽然在之前的snapshot版本中,swift2.2包含了swift自己的包管理工具Swift Product Manager(以下称SPM),但是包含在Xcode7.3中的swift2.2并未包含SPM工具,想使用最新的swift toolchain管理包的同学只能去下载swift3.0的snapshot了

有关Swift2.2版本方面的代码方面的改变:

  • 移除C语言风格的for循环,同时移除++和–操作符,包括前置与后置,将在3.0版本中彻底停用
  • 函数方法将不能直接声明为柯里化参数列表, 而是需要返回一个相同参数的函数方法.

这里用代码举例解释一下:

func doGET(url: String, completionHandler: ([String]?, NSError?) -> ()) {
    // do a GET HTTP request and call the completion handler when receiving the response
}
func completionHandler(results: [String]?, error: NSError?) {
    self.results = results
    self.resultLabel.text = "Got all items"
    self.tableView.reloadData()
}

func getAll() {
    doGET("http://blog.swiftflamel.com", completionHandler)
}

这样的写法在swift2.1及以前都是被认同的,但是在2.2版本中,这种写法将无法被编辑器识别,将直接报错,而之后只能使用如下这种写法:

func completionHandler(text: String) -> ([String]?, NSError?) -> () {
    return {results, error in
        self.results = results
        self.resultLabel.text = text
        self.tableView.reloadData()
    }
}
func getAll() {
    doGET("http://blog.swiftflamel.com", completionHandler("Got all items"))
}

而在swift2.1版本及以前,以上的写法还可以用写法上更简单的柯里化函数来写:

func completionHandler(text: String)(results: [String]?, error: NSError?) {
    self.results = results
    self.resultLabel.text = text
    self.tableView.reloadData()
}

而如今柯里化函数也不被允许了,会被编辑器要求替换为第二种方法的写法

  • 将函数选择器(selectors)的用法由原来的Selector(“doSomething”)或者大多数像我一样的直接”doSomething”改为#selector(doSomething),并且现在会在编译时检查这一写法
  • 大部分关键词现在都可以被用来作为函数的参数名
  • 现在在协议(protocol)中可以声明联合类型(Associated types)了,任何遵守协议的类或结构体可以定义具体的类型

关于Associatedtypes的解释:意思就是现在你可以通过“associatedtype”关键词在协议中声明一个(或多个)变量,这个变量的具体类型将在继承了该协议的类或结构体中来定义,来看一下官方的例子:

protocol Container {
    associatedtype ItemType
    mutating func append(item: ItemType)
    var count: Int { get }
    subscript(i: Int) -> ItemType { get }
}
//使用的时候就这样:
struct IntStack: Container {
    // original IntStack implementation
    var items = [Int]()
    mutating func push(item: Int) {
        items.append(item)
    }
    mutating func pop() -> Int {
        return items.removeLast()
    }
    // conformance to the Container protocol
    typealias ItemType = Int
    mutating func append(item: Int) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Int {
        return items[i]
    }
}

这样应该就能大概明白associated type的用法了吧,更多详细深层的用法可以去查看官方文档:
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Generics.html

有关Xcode:

2.pic

Editor里只有这一条,但我相信这会让绝大多数iOS开发者非常兴奋了,对于升级为对Release notes不感兴趣的开发者,相信它们打了几个代码下去之后就会发现这令人惊喜的改变,不错,现在的Xcode自带的代码补全也加入了模糊匹配功能,而且表现很优秀,Fuzzy Autocomplete是一个非常优秀的代码补全插件,但是我在用了一段时间之后不得已从我的Xcode卸载了它,因为当应用的文件规模变大时它的性能需求也变得非常高,我最新的15”rMbp也会被弄得异常卡顿,甚至让我有了使用Windows系统的感觉。但最新版Xcode的这个自动补全功能使用起来非常顺畅,在大项目中依然不会卡顿,立刻去升级Xcode7.3吧,享受一下。

 

版权所有,转载请先联系flamelswift@gmail.com