昨天整理code,顺手写了个UIColor和16进制RGB表示的颜色转换。由于UIColor中的RGBA范围是0..1,所以里面用到了一些乘除法,和强制类型转换:

CGFloat(Float(r)/255.0)
Int(r*255)

写完以后测试了一下貌似没什么问题,就睡觉去了。后来突然想到,1/255并不是一个有理数,而且Int()做的是取地板而不是四舍五入,会不会在这种转换的过程中因为精度的问题造成数据错误呢?于是写了一小段测试代码:

//精度测试,确认转换不会丢失信息
let a  = Array.init(0...255)
let b = a.map{ CGFloat(Float($0)/255.0) }
let c = b.map{ Int($0*255) }
a.elementsEqual(c)

还好结果是True,这个转换过程并没有出错的机会。但我怎么可能就此罢休?通过修改a的初始化条件发现,乘除255的时候,在算257的时候就出错了。按理说1/255并不是一个很小的数字,如果准确的话,16位的存储结构是可以保留足够信息的,所以我尝试这打印了一下各个数据类型的精度:

sizeof(Int)*8//64
sizeof(UInt)*8//64
sizeof(UInt16)*8//16
sizeof(Int32)*8//32

sizeof(Float)*8//32
sizeof(Double)*8//64
sizeof(CGFloat)*8//64

sizeof(Character)*8//72
sizeof(String)*8//192
sizeof(CGRect)*8//256
sizeof(UIColor)*8//64

第一组相当的make sense,Int默认就是64位;第二组是实数部分,令我惊讶的是原来CGFloat也是64位的,而Float反而比它差只有32位。第三组的结果比较值得玩味:Swift里面的String搞得比较复杂,是什么都不奇怪,但我以为Character至少会是8,结果却是72;UIColor本身是由4个CGFloat构成,其size却不是4倍的CGFloat;倒是CGRect比较可以解释,因为它是CGSize和CGPoint组合而成,实质上也是4个CGFloat。

阅读全文 »

在从TableCell跳转到具体新闻页面的时候,我遇到了一个问题:整个流程的时序跟我想的不太一样。因为我需要在点击Cell的时候知道这是哪个Cell,再将它作为参数传递给下一个View,所以我实现了函数 didSelectRowAtIndexPath,然后设好一个private的变量,供prepareForSegue调用。可是事实上的流程却变成了:

prepareForSegue() -> next.viewDidLoad() -> didSelectRowAtIndexPath()

也就是说当Cell的点击发生,会先调用StroyBaord中设置的Segue,新的View初始化完成后再回调didSelectRowAtIndexPath,导致我在viewDidLoad()中拿不到想要的数据。
在网上搜了一下,有人也遇到了同样的问题,他的解决方法是:
1) 将 ->NextView的Segue改为 ->NextViewController(NextView的代理)
2) 在StoryBoard中配置此Segue的ID为“ABC”
3) 在didSelectRowAtIndexPath()准备好数据之后手动调用转场:

//跳转到下一个页面,识别“ABC”
self.performSegueWithIdentifier(“ABC”, sender: self)

实测这个方法是有效的,不过我总感觉不够漂亮……于是打开Dash把所有tableView()的相关Callback都过了一遍,挑出几个加打印实测,

阅读全文 »

要实现不同界面的跳转,简单的办法是在StroyBoard中直接拖动控件。按住^将触发跳转的控件拖到待触发的View中,松开鼠标在弹出的菜单中选择对应的选项(跳转为View),一个segue(大致译为“转场、过渡”)就完成了:

而要实现在两个ViewController之间传数据,在这种建立segue的情况下也很简单。假设我们需要在从ViewA(ControllerA代理)跳转去ViewB(ControllerB代理)时传递一个Int变量id,那么先要在ControllerB中定义.id属性,然后在ControllerA中实现:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
//print(“this is segue \(segue.identifier), \(sender.debugDescription)")
ControllerB.id = passValue
}

这样的方式适合参数比较少或者已经打包的情况,缺点是必须将待传递的参数在ControllerB中暴露出来。

假如你有一个自定义类的列表,想要对它排序,怎么办?在新闻APP中,我的新闻列表就是一个自定义class的数组,用户一会儿要最新、一会儿要更多旧数据的可能性,使得它需要按时间有序。我是懒人,自己一个个拿key比较写起来觉得好麻烦,Swift这么高级的语言,应该有别的更好的办法才对!又祭出官方文档找灵感,然后看到了:Comparable
Comparable是一个协议,遵守这个协议的所有类型,都可以直接比大小。而对于自定义的类型,遵守它就行了:

class MyClass : Comparable {
    var key : Int = 0
}
//在Class定义之外实现
func < (left: MyClass, right: MyClass) -> Bool {
    return left.key < right.key
}
func == (left: MyClass, right: MyClass) -> Bool{
    return left.key == right.key
}   

我们只要实现\<== 两个函数,编译器会自动帮我们判断>和!=的情况。于是,现在这样的排序或者比较都是合法的了:超easy!😄

var a : MyClass = []
//TODO:a填了一些数据之后……
a.sort()
a[1] > a[0]

在定义各种接口的过程中,我碰到了这样一个问题:我希望定义一个只读的属性,却找不到合适的前缀(原谅我是Class白痴)。翻了一遍Class的相关文档,找到了个曲线救国的办法:计算属性。
所谓的计算属性,就是自己定义一个属性的名字和get/set方法,外人看不见get/set的实现,只能看作是一个普通属性来使用。而如果只实现了get部分,这个属性对外而言就是只读的了。
我的数据结构大致这样:

class myclass {
    private var data =  ( api : typeX, list:  typeY,  post: typeZ,  channel: typeA) (xxx, list: yyy, post: zzz, .news)
}

我想要把这一组相关数据打包成元组使用,又只想把其中某些暴露出来,并且部分是只读,那么就将其设成private并且在类中实现:

var list : typeY {//只有get,实现只读封装
    get {
        return data.list
    }
}

var ch : typeA {
    get {
        return data.channel
    }
    set (channel) {//这次可读写了
        data.channel = channel
    }
}

现在,外部可以用myclass.list来获取list,或者用myclass.ch来改变channel了

怎么从JSON拿数据学会了,那么接下来就是正式打造一个自己的新闻客户端了。虽然我Swift语法还没认真学完,但实在是心痒痒,就跳过一点基础,先来个实战吧!经过这个礼拜,我的APP也基本成型了。网上关于怎么做一个新闻客户端的例子很多,我就不赘述了,只是把过程中碰到的一些小问题和心得、我自己无法很容易搜到完美答案的写一写。过程中的弯路肯定没少走,但经验的收获也是不少的。

关于这个APP使用的数据结构,其实我是重构过的。新闻一般都是有两种数据源:新闻列表(List),和单条新闻(Post)详细信息。开始我在playground写了对应的List&Post数据结构,以及处理数据的函数。打印测试成功后,往project里面放的时候……不忍直视!作为一个初学者,我往viewDidLoad()里面塞了几乎所有的流程调用,以至于第一次运行成功在模拟器中看到数据的时候,我最大的感觉不是兴奋,而是我怎么把code写得这么丑陋!😖
功能不行可以再学习怎么做,但代码太丑是真真正正不能忍!我曾经的经验如是告诉我。我试着模块化各种数据和过程,一番思考和尝试后,变成了现在的结构:

DataManager是整个业务的最顶层类,它提供了很少的几个接口供ViewController使用:请求最新列表、请求更多列表数据、请求某个新闻、少量设置,并且设置也都是有default值的。考虑到将来有可能支持更多的新闻网站,GetURL和数据解析部分也是独立可更换的模块。在数据结构部分,我定义了一个Item类型,包括几个通用的属性,以及几个通用函数的实现,然后让ListItem,PostItem,CommentItem分别继承。这样既逻辑清晰,减少重复代码,也便于调试。所有上层不需要知道的参数都封装在内部,只有一个Manger和它的API,以及几个配置属性暴露在外。
也许这样的抽象定义过程,对于老手来说是不值一提的基础,就像吃饭要先煮饭一样简单。但我想对于刚刚接触APP开发的新人来说,如何更好的定义数据结构和API,恰当地抽象各种概念,还是值得多多思考的功课。

测试一下!配置好像相当简单嘛!:) 而且好像不是用域名而是ID单匹配的,自己本地的测试服务也能用,所以像我这种两个域名的配置起来完全无障碍,注册、改config一站式完成,赞!

(挖坑自己跳系列四)
以为实现了xml的解析,我就能拿到所有想要的数据,可是经过几次尝试,发现有的API失效了!😱无论我怎么尝试,有的数据就是拿不到,苦恼了好久,灵光一闪:前阵子买的surge可以派上用场了!不知道surge是啥的我给解释一下,是个iOS App,利用iOS9的特性能给设备做个全局代理,前阵子大家纷纷用来流畅FQ,已下架。对我来说,用来过滤下广告正好,偶尔还能看看http log。
看了下自己手机上的第三方客户端,request居然直接是网页,看来是直接解析网页了,这个太高深、放弃;只好去下载官方客户端来看,在网页看同样的请求,返回居然换成JSON了……😥
还能说啥呢,去找找JSON怎么解析呗!这次看了看,似乎比XML简单多了……先拿数据:

let url = NSURL(string:TESTURL)!
guard data = NSData(contentsOfURL: url)  else{
    print("Can't get any data")
    exit(0)
}

然后解析出来:

阅读全文 »