这里讲的不是语法层面,而是运行时层面
类型系统上应该说不支持泛型的 OC 和 Ruby 还比较接近,Swift 区别就有点大了。
比方说数组类型,Swift 中 [1, 2, 3]
的类型是 Int[]
, ["foo", "bar"]
的类型是 String[]
, Ruby 的数组在 Swift 中大概相当于 Any[]
或者 var x = [] // 数组类型变成了 NSArray
.
由于泛型数组的类型在编译时已经确定,所以可以比多态类型的数组更能发掘性能潜力,RC4 的 Swift 实现比 OC 实现速度快的原因就在于此。
泛型约束可以极度增加函数签名的长度...
Swift 默认禁止 nil
Object, 只有在类型上加问号 (Optional type) 才可以成为 nil.
var s: String = nil // 错误
var b: String? = nil
b?.isEmpty // nil 值链传递
b! // 取出了 nil 值
b.isEmpty // 错误, 你必须用 ! 拆箱或者用 ?. 去做链式调用
Swift 是面向类的语言,对象成员表是编译时决定的。
Swift 和 Ruby 同样是 单继承,和 Ruby 可以 include 任意多个 module 相比,Swift 可以添加任意多个 protocol 和 extension.
构造方法 init
的规则有点复杂。Swift 里可以定义两种构造方法:designated (写法是:init(...) { ... }
) 和 convenient (写法是 convenient init(...) { ... }
)
convenient 构造方法的常见作用是提供某些默认值,在 convenient 构造方法中,是必须调用 designated 构造方法的。还有个限制是子类中不能调用超类的 convenient 构造方法。
初始化方法的继承有两个规则:
对象成员的初始化分为 2 阶段,第一阶段是放默认值 (成员声明中的 var v = 默认值
), 第二阶段是执行 init 方法的赋值。初始化代码中有 4 个编译时的 safety check, 如果访问顺序不满足 4 个条件,就会报错。和 Java 完全相反:Swift 是子类成员先赋值,超类成员后赋值 -- 估计这个顺序规则有利于提高性能。(从实现角度看,我猜想 Swift 对象的内存布局可能会类似下面这样,既方便实现 extension method, 又方便 init 方法的优化,没看过内存里什么样子,扯远了...)
struct Object {
pointer klass;
pointer super_klass_object;
... // members defined in child object
}
成员的默认值其实可以用比较复杂的代码设置,例如
class A {
var x = {
...
}()
}
有些成员可以用 @lazy
修饰,到用的时候才初始化 ( lazy 成员才能在初始化块中引用 self
)... 所以太滥用成员的初始化机制还是挺容易产生坑的... Swift 对象系统还挺复杂难掌握的... 还好报错信息都非常人性化,很容易发现问题在哪里。
inout
区别于 class
, 用 struct
定义的类型都是传值类型,不用担心这类对象内存管理的问题 (下面会提到).
Swift 还有一种参数属性是 inout
, 使得函数中可以改变参数的值。不过可以返回 tuple 了我觉得挺多余的,这到底是为了提高性能还是吸引 C# 程序员?
Swift 的运行时和 OC 的运行时有很大的重合,它也是基于自动引用计数而不是 GC 的,所以还是要和 OC 一样,用 weak var
来避免循环引用内存不能释放的问题。如果要把 closure 中的强引用改成弱引用,就要用和 C++1x 相似的方括号语法和 unowned
关键字指定 capture list, 例如
someClosure = {
[unowned foo] (foo: Foo) -> Bar in
...
}
let
声明的变量往往会让人想起 Ruby 中的常量,但用 let
对象不但不可以改变值,还不可以改变它的内部状态
let x: Int[] = []
x.append(3) // can not modify immutable
var x: Int[] = []
x.append(3) // ok
下面代码可以看到 a 和 b 指向的是同一个数组:
a = [1]
b = a
a[0] = 3
b[0] // 3
但是当数组长度发生变化时,就坑爹了...
a = [1]
b = a
a.append(2)
a[0] = 3
b[0] // 1
所以数组是个 copy on extension 的东西,用的时候要多加注意... 用意为何没弄明白,难道是并发环境下数组内存重分配的考虑?还在在学 golang 的 slice?? 预测将来要么这个行为会改掉,要么向用户解释十几年...
反射除了 object is Klass
和 reflect(object)
以外,都用的 objc 的反射机制,所以还得回想 objc 的 selector ...
如果想获得 class 对象,需要自己包装一个调用 [object class]
的函数暴露给 swift
"".respondsToSelector("respondsToSelector:") // true
少数函数如 CGColorGetRandomColor
返回对象是没有被 ARC managed 的,需要手动 retain 和 release, 参见 Unmanaged
的文档 ...
Swift 比引入 Ruby Framework 的优点之一是语言内建了方便交互的桥梁。
在 Swift 项目中加个 .m
文件或者在 OC 项目中加个 .swift
文件,XCode 就会问你要不要加个 项目名-Bridging-Header.h
, 然后你把相应的头文件加进去就可以在 Swift 中使用了。
例如 OC 用 NS_ENUM
定义的枚举类型在 Swift 中就可以直接用:
typedef NS_ENUM(NSInteger, MyEnum) { FOO = 1, BAR };
swift:
var e: MyEnum = MyEnum.BAR
如果没用 NE_ENUM
定义,也能用,只是相当于整数类型了。不过好处是范围依然有检查:
typedef enum {FOO = 1, BAR} MyEnum;
swift:
var e: MyEnum = 2
e = 3 // 错误, 超出范围了
__bridge
例如 Swift Interoperability In Depth 中提到的:
func drawGradientRect(context: CGContext, startColor: CGColor, endColor: CGColor, width: CGFloat, height: CGFloat) {
let colorSpace = CGColorSpaceCreateDeviceRGB() // 不用手动 CGColorSpaceRelease 了
let gradient = CGGradientCreateWithColors(colorSpace, [startColor, endColor], [0.0, 1.0]) // 不用 __bridge
let startPoint = CGPoint(x: width / 2, y: 0) // 代替 Make 系列函数
let endPoint = CGPoint(x: width / 2, y: height)
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0)
}
整数类型 Int 在 64 位系统相当于 Int64, 在 32 位系统相当 Int32.
为了解决溢出问题,Swift 的运算符默认检查溢出,但是前面加了 &
的话就不检查 (例如 &+
, &*
, &%
...).
Swift 还可以声明 +=
, ||=
等赋值操作符,估计将来可以写原子性的检查 - 赋值语句,但是貌似还不支持 objc 中的 @synchronized
, @atomic
等 attributes.
playground 只能用系统已经提供的库,暂时还不能引入项目中内容或者第三方库