开始阅读《Swifter - 100 个 Swift 必备 tips》,一边学习一边记录,一边打代码 ,一边提问。因为Swift是新出的,所以作者还是先讲述OC知识然后转换为Swift,但是我属于先学Swift那种人,所以得先写Swift后写OC
1.Selector
直接使用了Selector
let NameSEL = Selector("setAge:blog:")
因为Selector实现了StringLiteralConvertible,可以不用初始化,代码如下
NSTimer.scheduledTimerWithTimeInterval(3, target: self, selector: "setAge:blog:", userInfo: nil, repeats: false)
func setAge(age:Int,blog:NSString){
//...
}
这里还有一个问题就是IOS runtime 我会在后面写篇文章整理一下。
如果方法由private修饰的,正确代码如下(前面需要加@objc
)
NSTimer.scheduledTimerWithTimeInterval(3, target: self, selector: "setAge:blog:", userInfo: nil, repeats: false)
@objc private func setAge(age:Int,blog:NSString){
//...
}
若方法第一个参数是外部参数,正确代码如下(方法名
with
参数名)
func set(exman name:NSString){...}
let s = Selector("setWithExman:")
2.柯里化
作为小白的我看到这三个字我无语了,神马意思,来吧,找网络。
柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。这个技术由 Christopher Strachey 以逻辑学家 Haskell Curry 命名的,尽管它是 Moses Schnfinkel 和 Gottlob Frege 发明的。
Ole Begemann 使用了一个简单的代码简单介绍一下柯里化Demo.
class BankAccount {
var balance: Double = 0.0
func deposit(amount: Double) {
balance += amount
}
}
我们调用一般是初始化(差点说了new其实差不多的说法但是没有new)
let account = BankAccount()
account.deposit(100)//balance is 100
利用柯里化代码如下
let depositor = BankAccount.deposit
depositor(account)(100) //balance is 200
是不是觉得很别扭,其实很简单,一开始我觉得这是什么,难道不会抱错,其实就是你怎么看返回值而已
let depositor:BankAccount->(Double)->()
//相当于后面那个是返回值,因为可以返回一个函数,这个函数参数是Double类型,无返回值。
应用到代码最好的地方就是更改Selector的问题,因为Selector只接受String类型的参数,其他的都不能接收。用柯里化优化代码如下
protocol TargetAction {
func performAction()
}
struct TargetActionWrapper : TargetAction {
weak var target: T?
let action: (T) -> () -> ()
func performAction() -> () {
if let t = target {
action(t)()
}
}
}
enum ControlEvent {
case TouchUpInside
case ValueChanged
// ...
}
class Control {
var actions = [ControlEvent: TargetAction]()
func setTarget(target: T, action: (T) -> () -> (), controlEvent: ControlEvent) {
actions[controlEvent] = TargetActionWrapper(target: target, action: action)
}
func removeTargetForControlEvent(controlEvent: ControlEvent) {
actions[controlEvent] = nil
}
func performActionForControlEvent(controlEvent: ControlEvent) {
actions[controlEvent]?.performAction()
}
}
调用
class MyViewController {
let button = Control()
func viewDidLoad() {
button.setTarget(self, action: MyViewController.onButtonTap, controlEvent: .TouchUpInside)
}
func onButtonTap() {
println("Button was tapped")
}
}
试试确实很棒,但是我还需要时间消耗,而且我也好奇我怎么能打出代码不看这些资料。
3.protocol 的方法声明为 mutating
protocol Vehicle
{
var numberOfWheels: Int {get}
var color: UIColor {get set}
mutating func changeColor()
}
struct MyCar: Vehicle {
let numberOfWheels = 4
var color = UIColor.blueColor()
mutating func changeColor() {
color = UIColor.redColor()
} }
4.Sequence
序列化是内部自动实现的,在程序中我们能很好使用for-in的原因是这个类一定是实现了SequenceType
,书里面如果我们自己写一个能进行序列化的类,得先实现GeneratorType
协议
书里面介绍了实现了SequenceType
还有其他的方法.
func map(source: S,
transform: (S.Generator.Element) -> T) -> [T]
func filter(source: S,
includeElement: (S.Generator.Element) -> Bool) -> [S.Generator.Element]
func reduce(sequence: S,
initial: U, combine: (U, S.Generator.Element) -> U) -> U
我都以数组为例
var arr = [1,2,3,4,5]
map
:
arr.map { $0/2 }//[0,1,1,2,2]
filter
:
arr.filter({ $0%2 == 0 })//[2,4]
reduce
://第一个参数代表运算几次
arr.reduce(2, combine: { $0 * $1
})//240
5.多元组 (Tuple)
说实话你看完这个代码,你会笑死真的,为什么早不这么写代码呢,太小清新了。
func swapAB(inout a:T,inout b:T){
(a,b) = (b,a)
}
利用元组瞬间完成交换.是不是惊呆了,我当时也是,不用中间变量了.
但是看第二个例子我就觉得没第一个惊喜,因为作者在OC已经习惯了在需要错误处理做一个 NSError 的指针,然后将地址传到方法里等待填充。直接上代码
func doSomethingMightCauseError() -> (Bool, NSError?) { //... 做某些操作,成功结果放在 success 中
if success {
return (true, nil) } else {
return (false, NSError(domain:"SomeErrorDomain", code:1, userInfo: nil))
} }
let (success, maybeError) = doSomethingMightCauseError()
if let error = maybeError {
// 发生了错误 }
好吧,彩蛋是一个接着一个呀。作者竟然发现了一个很神奇的东西,我搞不懂这个是Apple公司故意还是为未来打算的
测试代码如下:(你发现什么了)
var num = 42
println(num)
println(num.0.0.0.0.0.0.0.0.0.0)
神奇的地方就是,每个对象都是一个元组,现阶段没发现这样做会出现什么问题,但是很好玩。这就是编程的乐趣吧,发现各种菜单,解决各种bug,说不定未来bug就发生在这里的。
6.@autoclosure 和??
在swift中有一个概念是闭包,就是其他代码的代码块,一样的道理。
func setName(myName : ()->Bool){
if myName() {
println("true")
}
}
setName {2>1}
使用@autoclosure 代码如下
func setName2(getName:@autoclosure()->Bool){
if getName(){
println("true")
}
}
setName2(2>1)
??:运算符
规则:当前左侧为nil时,返回右侧的值;当左侧不为nil时,返回其值。定义如下:
func ??(optional: T?, @autoclosure defaultValue: () -> T?) -> T?
func ??(optional: T?, @autoclosure defaultValue: () -> T) -> T
7.Optional Chaining
还是拿书上的代码做演示
class Toy {
let name: String
init(name: String) {
self.name = name
}
}
class Pet {
var toy: Toy?
}
class Child {
var pet: Pet?
}
获取xiaoming的宠物玩具的名字的时候如下
var name = xiaoming.Pet?.Toy?.name//可能为nil相当一条锁链
改为如下
if let name = xiaoming.Pet?.Toy?.name{...}
如果做一个闭包方便调用,代码如下:
extension Toy{
func play(){
//......
}
}
var xiaohuang = Child()
let Closure = {(child:Child)->()? in child.pet?.toy?.play()}
if let Children = Closure(xiaohuang){
//小黄的宠物玩具玩游戏
}
8.操作符
这节让我又学了Swift一个知识点,感觉应该是我之前看文档的时候,漏掉了,没事,现在捡起来。
运算符函数
定义了一个名为Vector2D
的二维坐标向量(x,y)
的结构,然后定义了让两个Vector2D
的对象相加的运算符函数。
struct Vector2D {
var x = 0.0, y = 0.0
}
最神奇的一段代码,好吧,因为我之前没看到,所以觉得神奇:
func + (left: Vector2D, right: Vector2D) -> Vector2D {
return Vector2D(x: left.x + right.x, y: left.y + right.y)
}
当你要定义一个不属于库中的操作符的时候, 需要设置这个操作符的一些属性,代码如下:
func +* (left: Vector2D, right: Vector2D) -> Double {
return left.x * right.x + left.y * right.y
}
infix operator +* {
associativity none
precedence 160
}
infix
表示要定义的是一个中位操作符,即前后都是输入;其他的修饰子还包括prefix
和postfix
,不再赘述;
associativity
定义了结合律,即如果多个同类的操作符顺序出现的计算顺序。比如常见的加法 和减法都是 left,就是说多个加法同时出现时按照从左往右的顺序计算 (因为加法满足交换律,所以这个顺序无所谓,但是减法的话计算顺序就很重要了)。点乘的结果是一个 Double,不再会和其他点乘结合使用,所以这里写成 none;
precedence
运算的优先级,越高的话越优先进行运算。Swift 中乘法和除法的优先级是 150, 加法和减法是 140,这里我们定义点积优先级 160,就是说应该早于普通的乘除进 行运算。
9.func 的参数修饰
主要需要注意当参数修饰符为inout和一般的修饰符在改变值的区别
var x = 1
func setX(inout myX:Int){
++myx
}
setX(x)//x值改变了为2
10.方法参数名称省略
参数名前通过加
#
或_
显示参数名称或者隐藏参数名称,一般情况下,第一个参数都会被省略。有一个特殊的案例,就是全局的方法,所有参数名可以全部省略。
11.Swift 命令行工具
基本上从我学Swift时候开始,一直在使用Xcode,听说过控制台,但是没听说过有命令行工具.
脱离Xcode编程生成二进制文件->使用Swiftc进行编译
注:Swift 的命令行工具还有不少强大的功能,对此感兴趣的读者不妨使用 xcrun swift --help 以 xcrun swiftc --help 来查看具体还有哪些参数可以使用。
12.字面量转换
Swift提供了字面量的接口。用于将字面量转换为特殊的特殊的类型。
• ArrayLiteralConvertible
• BooleanLiteralConvertible
• DictionaryLiteralConvertible
• FloatLiteralConvertible
• NilLiteralConvertible
• IntegerLiteralConvertible
• StringLiteralConvertible
举个例子:
class Person: StringLiteralConvertible {
let name: String
init(name value: String) {
self.name = value
}
required convenience init(stringLiteral value: String) {
self.init(name: value)
}
required convenience init(extendedGraphemeClusterLiteral value: String) {
self.init(name: value)
}
required convenience init(unicodeScalarLiteral value: String) {
self.init(name: value)
}
let p:Person = "xiaoming"//赋予Person拥有String的特性,String转换为Person类
println(p.name)
注:在 extension 中,我们是不能定义 required 的初始化方法的
13.下标
对于系统提供的下标无法满足你的时候,你需要自己做扩展,虽然下面是书中的代码,作者还不推荐这种方式,好吧, 原来是因为参数列表的原因
extension Array {
subscript(input: [Int]) -> Slice {
get {
var result = Slice() for i in input {
assert(i < self.count, "Index out of range")
result.append(self[i]) }
return result
}set {
for (index,i) in enumerate(input) {
assert(i < self.count, "Index out of range")
self[i] = newValue[index]
} }
}
14.方法嵌套
Swift 提供了 public,internal 和 private 三种访问权限,可以采用方法嵌套就能很好的管理好权限的问题。
15.实例方法的动态调用
先看一下实例方法的代码
class MyClass {
func method(number: Int) -> Int {
return number + 1 }
class func method(number: Int) -> Int { return number
} }
没错,你没看错,里面的那个class func method是实例方法
16.命名空间
我的大脑印象中,好像C#里面有命名空间的说法,但是作用是什么,不清楚。作者是这么说的
Swift 的命名空间是基于 module 而不是在代码中显式地指明,每个 module 代表了 Swift 中的一个命名空间。也就是说,同一个 target 里的类型名称还是不能相同的。在 我们进行 app 开发时,默认添加到 app 的主 target 的内容都是处于同一个命名空间中的, 我们可以通过创建 Cocoa (Touch) Framework 的 target 的方法来新建一个 module,这样 我们就可以在两个不同的 target 中添加同样名字的类型了
//使用的时候语法
module(target).ClassName.FunctionName(或者属性)
好吧这就是一种宣传的感觉,没有太多的实际意义,对了,作者也介绍了还可以使用结构体。
说明一点,代码以及引用版权在于书的作者,我在总结,我现在还属于小白阶段,得向人学习,但是我觉得的互动性不好,为什么只有喜欢文章,基本不会去评论,还是说只是把我整理的文章放进去了,就成为古董再也不看了。希望互动起来,虽然我是小白,还是希望和大家交流,说不定会有意想不到的收获。书还会接着读下去,也不可以一下子全读完,我还得去思考,去实践,所以我把这个系列做完整。谢谢大家的眼睛。