猫·仁波切

我得把我那么多吐槽笔记都迁移过来别整到不靠谱的那堆上

Swift Beta3 Changes ( Swift 在 Beta3 中的变化)

准确说是 beta2 Swift version 1.0 (swift-600.0.34.4.8) 到 beta3 Swift version 1.0 (swift-600.0.38.7) 的变化。

对了,补充下。 beta1 Swift version 1.0 (swift-600.0.34.4.5) 到 beta2 几乎没有什么变化。

语法

nil 成为关键字。

[KeyType : ValueType] 可以表示字典类型 Dictionary<KeyType, ValueType>

[Type] 用于表示原 Array 类型 Type[],等价 Array<T>,原用法会导致警告。

增加 @noinline 属性

.. 运算符改为 ..<,不容易和 ... 混淆。

函数、类型

sort() 改名为 sorted()。新增 sort() 函数,参数为 inout

Index 类型中的 .succ() 变为 .successor().pred() 变为 .predecessor()

C/ObjC 交互变化

增加 UnsafeMutableArray<T> 类型。

增加 CFunctionPointer<T> 类型。

删除 CConstVoidPointerCMutableVoidPointer。替换为 UnsafePointer<()>ConstUnsafePointer<Int32>

删除 CConstPointer<T>CMutablePointer<T>。替换为 UnsafePointer<T>ConstUnsafePointer<T>

这么一来指针操作简单了好多。原有会出现 COpaquePointer 的不合理情况,也都对应到适合的类型。

CString 可以从 UnsafePointer<UInt8>UnsafePointer<CChar> 两种类型构造获得,之前只支持 UInt8

module.map 中头文件声明转换为 Swift 声明不再使用 C 兼容类型,直接使用 Swift 相应类型。原有 CInt,现在成为 Int32

结构体会自动添加构造函数 init(field1:field2:...) 这样。

nil

去掉了 NilType,增加了 NilLiteralConvertiblenil 成为关键字。可以认为是 nil 常量。

1
2
3
protocol NilLiteralConvertible {
  class func convertFromNilLiteral() -> Self
}

除了 Optional 、上面所提到的指针类型外,RawOptionSet 也实现了该协议。

Array

去掉了 .copy()unshare() 方法。

增加了以下方法:

1
2
func makeUnique(inout buffer: ArrayBuffer<T>, e: T, index: Int)
func sorted(isOrderedBefore: (T, T) -> Bool) -> Array<T>

看起来 Array 对底层容器的引用有了更好的控制 ArrayBufferType 增加了判断方法 func isMutableAndUniquelyReferenced() -> Bool

Array 目前可以认为是真正的值类型。

指针

增加了 _Pointer protocol

1
2
3
4
protocol _Pointer {
  var value: RawPointer { get }
  init(_ value: RawPointer)
}

表示一个类型可以对应到原生指针。

同时成为内部桥接类型,编译器内部在转换时使用它(取出 RawPointer, 构造具体指针类型)。

模块

增加了 StdlibUnittest 模块。 声明代码。单元测试终于有了。

Use Swift Dynamic Framework (如何科学地引用第三方 Swift 库)

排名 16 了。啧啧。你看才刚出一个月。

目前已经有了很多非常棒的 Swift 第三方库, JSON 处理啊、 HTTP 访问啊、 UIView 插件啊等等。

如何科学地引用这些第三方库呢?

现状

CocoaPods 由于完全使用静态链接解决方法,过度依赖 Objective-C ,目前应该是官方 repo 有提到是 -Xlinker error , 这个问题之前我也遇到过,无解。除非手工执行 ar 不用 ldlibtool

小伙伴有用子目录的方法引用代码,貌似不错,还有就是直接用 git submodule,看起来维护性也可以。

简单解决方案

一个良好的第三方库应该实现为 Cocoa Touch Framework (实际内容为 Header + 动态链接库)。而不是直接把 Swift 代码 Copy 过来放入自己的项目。这里以一个简单项目为例,介绍如何科学使用。

目标描述

用 Swift 创建一个 Demo ,使用 SwiftyJSON 和 LTMorphingLabel 库。

项目的名字叫 DemoApp 。

创建 Workspace

创建一个 Workspace ,名字随意,位置能找到就好。这个 Workspace 主要用来管理我们的项目及其依赖的第三方库。

创建 DemoApp

在 Workspace 创建一个 App ,因为是测试所以我选了 Single View Application 。

引入 SwiftyJSON

SwiftyJSON 是一个 Cocoa Touch Framework ,可以直接使用, git clone 后,添加项目到 Workspace 即可。

尝试操作发现。。最容易最不会出错的方法就是直接从 Finder 里把 .xcodeproj 文件拖动到 Workspace 。

引入 LTMorphingLabel

LTMorphingLabel 是一个 App Deme 式项目。其中 Label View 的实现在一个子目录中。可以采用创建 Cocoa Touch Framework 的方法来引入这几个文件。

当然也可以直接把目录拖到我们的 DemoApp 里,不过太原始粗暴了。

为 App 添加依赖

在 DemoApp 的 Genral 选项卡中,添加 Linked Frameworks and Libraries 。选择 Workspace 中 SwiftyJSON 和 LTMorphingLabel 两个 .framework

如果是直接选择来自其他项目的 .framework 而不是同一 Workspace ,那么这里也许还要同时加入 Embedded Binaries

使用

添加好依赖后,就可以在 DemoApp 项目代码中 import SwiftyJSON 或者 import LTMorphingLabel 来使用对应的库。同时还可以用 Command + 鼠标点击的方法查看声明代码。

除错

比较坑爹的是,实际上按照以上方法, LTMorphingLabel 并不能正常使用,查看报错信息发现是自动生成的 LTMorphingLabel-Swift.h 有处语法无法被识别,编辑器找到 .h 文件,注释掉这行诡异代码即可。

看起来目前的 Bridge Header 和 -emit-objc-header 实现还是有问题的。小伙伴一定要淡定。

对于非 Workspace

如果不喜欢使用 Workspace ,也可以将第三方库的编译结果,一个 .framework 目录拖到项目文件里,然后添加 Embedded Binaries

评论

创建 Cocoa Touch Framework 选项中,可以使用 Swift 代码,此时编译结果(默认)会包含 module.modulemap 文件, 之前有介绍过它的作用,通过它, Swift 可以使用第三方模块。参考 Module System of Swift (简析 Swift 的模块系统)

实际上这个解决方案绕了一大圈,通过 Swift 文件导出 ProjName-Swift.h、然后 module.modulemap 模块描述文件引入、然后再由 Swift 导入。

其实 .framework 同时也包含了 ProjName.swiftmodule/[ARCH].swiftmodule 不过看起来没有使用到,而且默认在 IDE 下也不支持 Swift 从 .swiftmodule 文件导入,比较坑。希望以后版本能加入支持。

.framework 包含了所有 Swift 标准库的动态链接库,小伙伴可能会以为这会导致编译后的 App 变大。其实大可放心,任何 Swift 语言的 App 都会包含这些动态链接库,而且只会包含一个副本。此方法对 App 最终的大小几乎无影响。

注: 个人测试了下,发现这个 .swiftmodule 是可以通过其他方法使用的,绕过 module.modulemap,应该是更佳的解决方案,但是需要控制命令行参数。

至于静态链接库,过时了。抛弃吧。

参考

Swift Undocumented Grammar (Swift 黑语法)

本文介绍 Swift 的 Undocumented 语法特性。

电子书上介绍的 default function parameter 这里都不好意思拿出来写。

咳咳。持续更新。

用关键字当变量名

Keywards as variable name.

1
2
3
// escaped variable name
let `let` = 1000
dump(`let`, name: "variable named let")

new 关键字

The new keyword.

快速初始化数组。

1
let an_array_with_100_zero = new(Int)[100]

protocol type

use protocol<Protocol1, Protocol2, ...> as a type.

How I find it?

瞎试出来的。

Cocoa Extensions in Swift ( Cocoa 在 Swift 中所添加的扩展)

最近看到了 Swift Style Guide 个人觉得内容太少, Swift 本身作为一门庞大的语言,语素众多。本文就 Swift 本身对 Cocoa 的扩展,看看对日常 Cocoa 风格有什么影响。

Swift 本身的特性,导致它在一些用法上和 Objective-C 上有所不同,比如 ObjC 的 struct 单纯和 C 的一样,但是在 Swift 中的 struct 则要强大得多。

个人认为比如 CGPointMake 这样的函数,理论上不应该出现在 Swift 代码中。而是应该用 CGPoint(x:y:)

本文可以作为参考手册使用。

标准库扩展

ObjectiveC

值得注意的是 Selector 相关方法,实现了 StringLiteralConvertible。也可以从 nil 获得。

Foundation

这里忽略之前介绍过的 _BridgedToObjectiveC 相关内容。

协议附加

Sequence 协议

1
NSMutableArray NSSet NSArray NSMutableDictionary NSMutableSet NSDictionary

所有以上这些类型都可以通过 for-in 操作。

*LiteralConvertible

1
NSNumber NSString NSArray NSDictionary

隐式类型转换

CF 几乎都对应到了 NS 类型。这里略去

  • NilType –> NSZone
  • Dictionary<KeyType: Hashable, ValueType> –> NSDictionary
  • NSDictionary –> Dictionary<NSObject, AnyObject>
  • String <–> NSString
  • NSArray –> AnyObject[]
  • A[] –> NSArray
  • Float Double Int UInt Bool –> NSNumber
  • NSRange –> Range<Int> // 比较有意思的一个

方法扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// let s = NSSet(objects: 12, 32, 23, 12)
extension NSSet {
  convenience init(objects elements: AnyObject...)
}
extension NSOrderedSet {
  convenience init(objects elements: AnyObject...)
}
// 这里注意,NSRange 和 Swift Range 对 range 结束的表述方法不同
// NSRange 保存 range 元素个数
// Swift Range 保存的是结束元素
// let r = NSRange(0..20)
extension NSRange {
  init(_ x: Range<Int>)
}
// let prop = NSDictionary(objectsAndKeys: "Feather", "name", "Programming", "hobby")
extension NSDictionary {
  convenience init(objectsAndKeys objects: AnyObject...)
}
extension NSObject : CVarArg {
  @objc func encode() -> Word[]
}

字符串的扩展方法非常多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
  static func availableStringEncodings() -> NSStringEncoding[]
  static func defaultCStringEncoding() -> NSStringEncoding
  static func localizedNameOfStringEncoding(encoding: NSStringEncoding) -> String
  static func localizedStringWithFormat(format: String, _ arguments: CVarArg...) -> String
  static func pathWithComponents(components: String[]) -> String
  static func stringWithContentsOfFile(path: String, encoding enc: NSStringEncoding, error: NSErrorPointer = default) -> String?
  static func stringWithContentsOfFile(path: String, usedEncoding: CMutablePointer<NSStringEncoding> = default, error: NSErrorPointer = default) -> String?
  static func stringWithContentsOfURL(url: NSURL, encoding enc: NSStringEncoding, error: NSErrorPointer = default) -> String?
  static func stringWithContentsOfURL(url: NSURL, usedEncoding enc: CMutablePointer<NSStringEncoding> = default, error: NSErrorPointer = default) -> String?
  static func stringWithCString(cString: CString, encoding enc: NSStringEncoding) -> String?
  static func stringWithUTF8String(bytes: CString) -> String?
  func canBeConvertedToEncoding(encoding: NSStringEncoding) -> Bool
  var capitalizedString: String { get }
  func capitalizedStringWithLocale(locale: NSLocale) -> String
  func caseInsensitiveCompare(aString: String) -> NSComparisonResult
  func commonPrefixWithString(aString: String, options: NSStringCompareOptions) -> String
  func compare(aString: String, options mask: NSStringCompareOptions = default, range: Range<String.Index>? = default, locale: NSLocale? = default) -> NSComparisonResult
  func completePathIntoString(_ outputName: CMutablePointer<String> = default, caseSensitive: Bool, matchesIntoArray: CMutablePointer<String[]> = default, filterTypes: String[]? = default) -> Int
  func componentsSeparatedByCharactersInSet(separator: NSCharacterSet) -> String[]
  func componentsSeparatedByString(separator: String) -> String[]
  func cStringUsingEncoding(encoding: NSStringEncoding) -> CChar[]?
  func dataUsingEncoding(encoding: NSStringEncoding, allowLossyConversion: Bool = default) -> NSData
  var decomposedStringWithCanonicalMapping: String { get }
  var decomposedStringWithCompatibilityMapping: String { get }
  func enumerateLines(body: (line: String, inout stop: Bool) -> ())
  func enumerateLinguisticTagsInRange(range: Range<String.Index>, scheme tagScheme: String, options opts: NSLinguisticTaggerOptions, orthography: NSOrthography?, _ body: (String, Range<String.Index>, Range<String.Index>, inout Bool) -> ())
  func enumerateSubstringsInRange(range: Range<String.Index>, options opts: NSStringEnumerationOptions, _ body: (substring: String, substringRange: Range<String.Index>, enclosingRange: Range<String.Index>, inout Bool) -> ())
  var fastestEncoding: NSStringEncoding { get }
  func fileSystemRepresentation() -> CChar[]
  func getBytes(inout buffer: UInt8[], maxLength: Int, usedLength: CMutablePointer<Int>, encoding: NSStringEncoding, options: NSStringEncodingConversionOptions, range: Range<String.Index>, remainingRange: CMutablePointer<Range<String.Index>>) -> Bool
  func getCString(inout buffer: CChar[], maxLength: Int, encoding: NSStringEncoding) -> Bool
  func getFileSystemRepresentation(inout buffer: CChar[], maxLength: Int) -> Bool
  func getLineStart(start: CMutablePointer<String.Index>, end: CMutablePointer<String.Index>, contentsEnd: CMutablePointer<String.Index>, forRange: Range<String.Index>)
  func getParagraphStart(start: CMutablePointer<String.Index>, end: CMutablePointer<String.Index>, contentsEnd: CMutablePointer<String.Index>, forRange: Range<String.Index>)
  var hash: Int { get }
  static func stringWithBytes(bytes: UInt8[], length: Int, encoding: NSStringEncoding) -> String?
  static func stringWithBytesNoCopy(bytes: CMutableVoidPointer, length: Int, encoding: NSStringEncoding, freeWhenDone flag: Bool) -> String?
  init(utf16CodeUnits: CConstPointer<unichar>, count: Int)
  init(utf16CodeUnitsNoCopy: CConstPointer<unichar>, count: Int, freeWhenDone flag: Bool)
  init(format: String, _ _arguments: CVarArg...)
  init(format: String, arguments: CVarArg[])
  init(format: String, locale: NSLocale?, _ args: CVarArg...)
  init(format: String, locale: NSLocale?, arguments: CVarArg[])
  var lastPathComponent: String { get }
  var utf16count: Int { get }
  func lengthOfBytesUsingEncoding(encoding: NSStringEncoding) -> Int
  func lineRangeForRange(aRange: Range<String.Index>) -> Range<String.Index>
  func linguisticTagsInRange(range: Range<String.Index>, scheme tagScheme: String, options opts: NSLinguisticTaggerOptions = default, orthography: NSOrthography? = default, tokenRanges: CMutablePointer<Range<String.Index>[]> = default) -> String[]
  func localizedCaseInsensitiveCompare(aString: String) -> NSComparisonResult
  func localizedCompare(aString: String) -> NSComparisonResult
  func localizedStandardCompare(string: String) -> NSComparisonResult
  func lowercaseStringWithLocale(locale: NSLocale) -> String
  func maximumLengthOfBytesUsingEncoding(encoding: NSStringEncoding) -> Int
  func paragraphRangeForRange(aRange: Range<String.Index>) -> Range<String.Index>
  var pathComponents: String[] { get }
  var pathExtension: String { get }
  var precomposedStringWithCanonicalMapping: String { get }
  var precomposedStringWithCompatibilityMapping: String { get }
  func propertyList() -> AnyObject
  func propertyListFromStringsFileFormat() -> Dictionary<String, String>
  func rangeOfCharacterFromSet(aSet: NSCharacterSet, options mask: NSStringCompareOptions = default, range aRange: Range<String.Index>? = default) -> Range<String.Index>
  func rangeOfComposedCharacterSequenceAtIndex(anIndex: String.Index) -> Range<String.Index>
  func rangeOfComposedCharacterSequencesForRange(range: Range<String.Index>) -> Range<String.Index>
  func rangeOfString(aString: String, options mask: NSStringCompareOptions = default, range searchRange: Range<String.Index>? = default, locale: NSLocale? = default) -> Range<String.Index>
  var smallestEncoding: NSStringEncoding { get }
  func stringByAbbreviatingWithTildeInPath() -> String
  func stringByAddingPercentEncodingWithAllowedCharacters(allowedCharacters: NSCharacterSet) -> String
  func stringByAddingPercentEscapesUsingEncoding(encoding: NSStringEncoding) -> String
  func stringByAppendingFormat(format: String, _ arguments: CVarArg...) -> String
  func stringByAppendingPathComponent(aString: String) -> String
  func stringByAppendingPathExtension(ext: String) -> String
  func stringByAppendingString(aString: String) -> String
  var stringByDeletingLastPathComponent: String { get }
  var stringByDeletingPathExtension: String { get }
  var stringByExpandingTildeInPath: String { get }
  func stringByFoldingWithOptions(options: NSStringCompareOptions, locale: NSLocale) -> String
  func stringByPaddingToLength(newLength: Int, withString padString: String, startingAtIndex padIndex: Int) -> String
  var stringByRemovingPercentEncoding: String { get }
  func stringByReplacingCharactersInRange(range: Range<String.Index>, withString replacement: String) -> String
  func stringByReplacingOccurrencesOfString(target: String, withString replacement: String, options: NSStringCompareOptions = default, range searchRange: Range<String.Index>? = default) -> String
  func stringByReplacingPercentEscapesUsingEncoding(encoding: NSStringEncoding) -> String
  var stringByResolvingSymlinksInPath: String { get }
  var stringByStandardizingPath: String { get }
  func stringByTrimmingCharactersInSet(set: NSCharacterSet) -> String
  func stringsByAppendingPaths(paths: String[]) -> String[]
  func substringFromIndex(index: Int) -> String
  func substringToIndex(index: Int) -> String
  func substringWithRange(aRange: Range<String.Index>) -> String
  func uppercaseStringWithLocale(locale: NSLocale) -> String
  func writeToFile(path: String, atomically useAuxiliaryFile: Bool, encoding enc: NSStringEncoding, error: NSErrorPointer = default) -> Bool
  func writeToURL(url: NSURL, atomically useAuxiliaryFile: Bool, encoding enc: NSStringEncoding, error: NSErrorPointer = default) -> Bool

CoreGraphics

几个常用基本类型都有了 Swift-style 的构造函数。其中 CGRect 有很多的相关运算都被封装为方法,很不错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
extension CGPoint : Equatable {
  static var zeroPoint: CGPoint
  init()
  init(x: Int, y: Int)
}
extension CGSize {
  static var zeroSize: CGSize
  init()
  init(width: Int, height: Int)
}
extension CGVector {
  static var zeroVector: CGVector
  init(_ dx: CGFloat, _ dy: CGFloat)
  init(_ dx: Int, _ dy: Int)
}
extension CGRect : Equatable {
  // 全为 0
  static var zeroRect: CGRect
  // 原点为无穷大,表示空
  static var nullRect: CGRect
  // 原点无穷小,宽高无穷大
  static var infiniteRect: CGRect
  init()
  init(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat)
  init(x: Int, y: Int, width: Int, height: Int)
  var width: CGFloat
  var height: CGFloat
  var minX: CGFloat
  var minY: CGFloat
  // 中点
  var midX: CGFloat
  var midY: CGFloat
  var maxX: CGFloat
  var maxY: CGFloat
  var isNull: Bool
  var isEmpty: Bool
  var isInfinite: Bool
  var standardizedRect: CGRect
  func standardize()
  var integerRect: CGRect
  func integerize()
  func rectByInsetting(#dx: CGFloat, dy: CGFloat) -> CGRect
  func inset(#dx: CGFloat, dy: CGFloat)
  func rectByOffsetting(#dx: CGFloat, dy: CGFloat) -> CGRect
  func offset(#dx: CGFloat, dy: CGFloat)
  func rectByUnion(withRect: CGRect) -> CGRect
  func union(withRect: CGRect)
  func rectByIntersecting(withRect: CGRect) -> CGRect
  func intersect(withRect: CGRect)
  func rectsByDividing(atDistance: CGFloat, fromEdge: CGRectEdge) -> (slice: CGRect, remainder: CGRect)
  func contains(rect: CGRect) -> Bool
  func contains(point: CGPoint) -> Bool
  func intersects(rect: CGRect) -> Bool
}

AppKit

1
2
3
extension NSGradient {
  convenience init(colorsAndLocations objects: (AnyObject, CGFloat)...)
}

UIKit

1
2
3
4
5
6
7
8
extension UIDeviceOrientation {
  var isPortrait: Bool
  // also isLandscape isValidInterfaceOrientation isFlat 
}
extension UIInterfaceOrientation {
  var isPortrait: Bool
  var isLandscape: Bool
}

这个模块是交叉编译的。。不太容易获得信息。不过好在扩展内容不多。

SpriteKit

1
2
3
extension SKNode {
  @objc subscript (name: String) -> SKNode[] { get }
}

特殊 Mirror 实现

1
2
3
4
5
NSSet NSDate NSArray NSRange NSURL NSDictionary NSString
CGPoint CGRect CGSize
NSView
UIView
SKTextureAtlas SKTexture SKSpriteNode SKShapeNode

单独添加了自己的 Mirror 类型,单独实现。

Mirror 类型其实是为 QuickLookObject 准备的,也就是在 Xcode Playground 中快速查看。

Swift Type Hierarchy ( Swift 类型层次结构 )

声明: 转载请注明,方便的情况下请知会本人. weibo

本文主要介绍 Swift 所有标准库类型的层次结构,及所有标准类型。本文可作为参考手册使用。

本人不保证内容及时性和正确性,请善于怀疑并反馈。谢谢。

本文探索 Swift 所有基础类型和高级类型,以及所有协议和他们之间的继承关系。

为了简化问题,某些类型略去了中间的过渡类型,人肉保证不歧义。

Swift 基础类型

数值类型

Bit

只有一位,实现为 enum.zero.one。简单明了。

协议: RandomAccessIndex IntegerArithmetic

整型

有符号:

1
Int Int8 Int16 Int32 Int64

协议:SignedInteger RandomAccessIndex BitwiseOperations SignedNumber CVarArg

无符号:

1
UInt UInt8 UInt16 UInt32 UInt64

协议:UnsignedInteger RandomAccessIndex BitwiseOperations

别名:

1
2
3
4
5
IntMax = Int64
UIntMax = UInt64
IntegerLiteralType = Int
Word = Int // 字长
UWord = UInt

浮点型

1
Float Double Float80

别名:

1
2
FloatLiteralType = Double
Float64 = Double

协议:FloatingPointNumber

逻辑型

只有一个 Bool

实例: truefalse

协议:LogicValue

只有一个 NilType

唯一实例 nil

字符(串)类型

  • String
  • Character Unicode 字符
  • UnicodeScalar 相当于 C 中的 wchar_t
  • CString 用于表示 C 中的 const char *,请参考相关文章
  • StaticString 静态字符串,内部使用,例如 fatalError

别名:

1
2
StringLiteralType = String
ExtendedGraphemeClusterType = String

官方文档

Character represents some Unicode grapheme cluster as defined by a canonical, localized, or otherwise tailored segmentation algorithm.

String 实现协议:Collection ExtensibleCollection OutputStream TargetStream

Array 类型

  • Array<T>
  • ContiguousArray<T>

实现协议 ArrayType

内部容器:

  • ArrayBuffer<T>
  • ContiguousArrayBuffer<T>

这两个类型看起来是 Array 的内部容器,一般不应该直接使用。

字典类型

Dictionary<KeyType : Hashable, ValueType>

只实现了 Collection

元祖类型

除正常元祖外,还有个特殊的别名

Void = ()

其实很多语言都这么定义的,比如 Haskell 。

Optional 类型

  • Optional<T>T?
  • ImplicitlyUnwrappedOptional<T>T!

实现协议: LogicValue,行为是判断是否为 .None

另外 Swift 的隐式类型转换 有提到,为什么 nil 可以给 Optional 类型赋值的问题。

C/ObjC 兼容类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CBool = Bool
CFloat = Float
CDouble = Double
CChar = Int8
CSignedChar = Int8
CUnsignedChar = UInt8
CChar16 = UInt16
CWideChar = UnicodeScalar
CChar32 = UnicodeScalar
CInt = Int32
CUnsignedInt = UInt32
CShort = Int16
CUnsignedShort = UInt16
CLong = Int
CUnsignedLong = UInt
CLongLong = Int64
CUnsignedLongLong = UInt64

具体使用参考 C 交互的几篇文章,基本没区别。

Any 类型

1
2
3
4
AnyObject
// 别名
Any = protocol<>
AnyClass = AnyObject.Type

还有个用在函数定义的类型签名上, Any.Type

顺便这里看到一个奇异的语法 protocol<>,这个也是 Swift 一种用来表示类型限制的方法,可以用在类型的位置,尖括号里可以是协议的列表。

指针类型

1
2
3
4
5
6
7
8
UnsafePointer<T>
CMutableVoidPointer
CConstVoidPointer
COpaquePointer
CConstPointer<T>
AutoreleasingUnsafePointer<T>
CVaListPointer
CMutablePointer<T>

参考 C 交互文章。

其他辅助类型

多了去了。比如 for-in 实现时候的 Generator 、比如反射时候用的 *Mirror、比如切片操作用的 Range<T>。比如内部储存类。

还有储存辅助类 OnHeap<T> 等等。以后有机会再探索。

Swift 标准库协议

打印相关 Printable DebugPrintable

1
2
3
4
5
6
protocol Printable {
  var description: String { get }
}
protocol DebugPrintable {
  var debugDescription: String { get }
}

用于打印和字符串的 Interpolation 。

*LiteralConvertible

从字面常量获取。

1
2
3
4
5
6
7
ArrayLiteralConvertible
IntegerLiteralConvertible
DictionaryLiteralConvertible
CharacterLiteralConvertible
FloatLiteralConvertible
ExtendedGraphemeClusterLiteralConvertible
StringLiteralConvertible

其中字符串和字符的字面常量表示有所重合,也就是说 "a" 可以是字符串也可以是字符。简析 Swift 中的 Pattern Match 一文中就是遇到了类似的情况。

LogicValue

相当于重载 ifwhile 的行为。

1
2
3
protocol LogicValue {
  func getLogicValue() -> Bool
}

Sequence

相当于重载 for-in 。和 Generator 联用。

1
2
3
4
5
6
7
8
9
protocol Sequence {
  typealias GeneratorType : Generator
  func generate() -> GeneratorType
}

protocol Generator {
  typealias Element
  mutating func next() -> Element?
}
1
2
3
4
5
// for .. in { }
var __g = someSequence.generate()
while let x = __g.next() {
    ...
}}

整型、 Index 相关协议

这些协议都是用来表示容器类型的索引、及相关的索引运算。

这里略去了部分私有内容。略去了 Printable 等。

RawOptionSet 相关协议

一般用来表示二进制的选项,类似于 C enum ,很多 Cocoa 的 flag 被映射到它。相当于一个 Wrapper 的作用。

可以看到要求被 Wrap 的对象支持 BitwiseOperations

Array 相关协议

图中用虚线标注了和 Generator 的关系。

Array<T> 类型实现了 ArrayType 协议。

Dictionary 类型实现了 Collection 协议。

反射相关协议

包括 MirrorMirrorDispositionReflectable

请参考 Swift 的反射

浮点数协议

只有一个 FloatingPointNumber。单独存在。是为了定义完整而存在。看官自己搞定。

IO 输出,伪输出相关

1
2
3
protocol Streamable {
  func writeTo<Target : OutputStream>(inout target: Target)
}

Streamable 表示可以被写入到输出流中,比如字符串、字符等。

1
2
3
protocol OutputStream {
  func write(string: String)
}

OutputStream 表示一个输出流,比如标准输出(stdout),也可以表示一个伪输出流,例如字符串 String

标准输出的获取方法

var stdout = _Stdout()

看起来是私有结构,某一天不能用的话,别怪我。调用时候用 inout 引用语法。

CVarArg 处理

1
2
3
protocol CVarArg {
  func encode() -> Word[]
}

用于处理 C 函数的可变参数,参考 简析 Swift 和 C 的交互,Part 二

Bridge 协议

这里有个疑问就是编译过程中这些 Bridge 协议有没有参与。目前还没办法确定。

  • _BridgedToObjectiveC
  • _ConditionallyBridgedToObjectiveC

具体内容可以参考 Swift 与 Objective-C 之间的交互一文。

其他

Sink 看起来是一个容器,可能是用来编码时使用。

ArrayBufferType 用于表示 ArrayType 的内部储存,看起来似乎也可以直接用。

UnicodeCodec 用于处理编码。有 UTF8UTF16UTF32 可用。

ArrayBound 用来处理数组边界,详细原理和作用过程未知。

总结

无。

参考:

My View of Swift (闲扯对 Swift 语言的看法)

其实这是很早就像要说的了,大概当时信誓旦旦说要看完那本 epub 写个读后感谈谈对 Swift 看法什么的。后来不了了之。 现在觉得这个时机或许差不多,对 Swift 的了解也算凑合了。

纯个人观点。

Swift 是系统编程语言

一开始大家还不太了解的时候,可能会有很多误解。现在好歹一个月了。误解终于少了。

是的, Swift 是系统编程语言,原因是因为它 ABI 兼容 C (不包括 name mangling 部分)。基于强大的 llvm 生成具体平台代码。不是翻译为 Objective-C 的。

编译器参数还显示, Swift 文件的中间编译结果(介于 Swift 代码和 llvm ir )是 SIL ,猜测是 Swift Intermediate Language 。好像和 llvm ir 有所联系。而且至少有两个 stage 。

不是脚本语言,也不是胶水语言。但是它的标准库 (import Swift 库) 几乎不含任何 IO 网络 等内容,随便做个功能强依赖 Cocoa 框架。也可以 import Darwin 用 C 语言的标准库来写。

猜测写个 Python C 模块这种任务是可以轻易胜任的。

而 Golang 、 Rust 本身 ABI 是和 C 不兼容的。虽然 Rust 通过 extern "C" 可以修改单个函数为兼容。

Swift 是现代语言

自动类型推导、泛型、 LLVM 。当然语言研究党都知道这些都是几十年前的“新东西”。

Swift 是半完成品

这么说主要是指 Swift 对 Cocoa 的库实在是太半吊子了。只是 Foundation 有 Bridge 支持,其他库中,明显的列表都无法支持 subscript 、 for-in 这样简单的操作。原因很简单,这些库都是自动转换 ObjC 头文件而来(参考模块那篇文章)。没有额外的封装代码。

所以其实真要用起来,可能会很纠结。或者可以预计很快就有第三方的 Bridge 库给这些类型加上舒服的 Swift 支持。

另外命令行没有静态链接库支持。只能用其他命令拼装。也侧面说明, Apple 希望开发者更多用动态链接库, Framework 。

另外目前的编译器 coredump 、 stackoverflow 太多太多。错哪都不知道。

Swift 隐藏细节太多

就对应到 Foundation 类型这个特性太说,太多黑魔法,隐式类型转换、 BridgeToObjectiveC 协议、指针类型转换。

这些隐藏的特性多少都会成为 Swift 的坑。

要知道定义在 ObjC 库的 NSString 参数某些情况下在 Swift 中被转换为 StringNSArray 都被转换为 AnyObject[]。即使有隐式类型转换,某些极端情况下,还是会有编译时错误。

Swfit 的性能

我没做过测试,但就语言特性来说, Swift 是比 ObjC 快的,因为静态类型使得他在编译时就已经知道调用函数的具体位置。而不是 Objective-C 的消息发送、 Selector 机制。

目前来看, Swift 性能略差原因主要是编译器还没足够优化、还有就是 Cocoa 拖了后腿, Cocoa 本身有大量的 AnyObject 返回值。所以实际写 Swift 代码时,多用 as 一定是好习惯。明确类型。

Swift 的未来

我不知道。至少好像感觉很多培训机构都看到了前途开始疯狂的做视频。

倒是觉得什么时候 Cocoa for Swift 出了才算它完全完成任务。

总觉得 Cocoa 拖后腿,不然放到其他平台也不错。

对了,之前不是在 App 开发领域,这才知道原来这个地盘水很深,太多唯利的培训机构,太多嗷嗷待哺等视频教程的新人。觉得挺有意思。就拿 ? ! 这个 Optional 为例,太多介绍的文章。可惜能说明白的太少太少。糊里糊涂做开发就是当前现状吧。

Swift Interop With C/ObjC Part 3 (Swift 与 ObjC 和 C 的交互,第三部分)

声明: 转载请注明,方便的情况下请知会本人. weibo

之前说那是最后一篇。可惜越来越发现有很多东西还没介绍到。事不过三。再坑一篇。

前言

本文解决如下问题

  • ObjC/C 中定义的某个类型、结构体,通过 Bridge Header 或者 Module 对应到 Swift 到底是什么类型
  • 指针间的转换问题

补充之前没解决的一些问题,比如提到 CMutablePointersizeof 是两个字长,那么在函数调用中是如何对应到 C 的指针的?

预备内容:

C/ObjC to Swift 对应规则

以下内容均适合 Objective-C 。第一部分适合 C 。

以下内容再 Xcode6-beta3 中不适用 请参考 Swift 在 Xcode6-beta3 中的变化

for C

可导出的类型定义

函数、枚举、结构体、常量定义、宏定义。

结构体定义支持:

1
2
3
typedef struct Name {...} Name;
typedef struct Name_t {...} Name;
struct Name { ... };

其中无法处理的结构体、函数类型、 varargs 定义不导出。预计以后版本会修复。带 bit field 的结构体也无法识别。

类型对应关系

仔细分析发现,诡异情况还很多。基础类型请参考上几篇。

在函数定义参数中:

类型 对应为
void * CMutableVoidPointer
Type *Type[] CMutablePointer<Type>
const char * CString
const Type * CConstPointer<Type>
const void * CConstVoidPointer

在函数返回、结构体字段中:

类型 对应为
const char * CString
Type *const Type * UnsafePointer<Type>
void *const void * COpaquePointer
无法识别的结构指针 COpaquePointer

另外还有如下情况:

全局变量、全局常量(const)、宏定义常量(#define) 均使用 var,常量不带 set

结构体中的数组,对应为元祖,例如 int data[2] 对应为 (CInt, CInt),所以也许。。会很长。数组有多少元素就是几元祖。

for ObjC

ObjC 明显情况要好的多,官方文档也很详细。

除了 NSError ** 转为 NSErrorPointer 外,需要注意的就是:

函数参数、返回中的 NSString * 被替换为 String!NSArray * 被替换为 AnyObject[]!

而全局变量、常量的 NSString * 不变。

关于 CMutablePointer 的行为

上回说到 CMutablePointerCConstPointerCMutableVoidPointerCConstVoidPointer 四个指针类型的字长是 2,也就是说,不可以直接对应为 C 中的指针。但是前面说类型对应关系的时候, C 函数声明转为 Swift 时候又用到了这些类型,所以看起来自相矛盾。仔细分析了 lldb 反汇编代码后发现,有如下隐藏行为:

in Swift

在纯 Swift 环境下,函数定义等等、这些类型字长都为 2,不会有任何意外情况出现。

in C/ObjC

当一个函数的声明是由 Bridge Header 或者 LLVM Module 隐式转换而来,且用到了这四个指针类型,那么代码编译过程中类型转换规则、隐式转换调用等规则依然有效。只不过在代码最生成一步,会插入以下私有函数调用之一:

1
2
3
4
@transparent func _convertCMutablePointerToUnsafePointer<T>(p: CMutablePointer<T>) -> UnsafePointer<T>
@transparent func _convertCConstPointerToUnsafePointer<T>(p: CConstPointer<T>) -> UnsafePointer<T>
@transparent func _convertCMutableVoidPointerToCOpaquePointer(p: CMutableVoidPointer) -> COpaquePointer
@transparent func _convertCConstVoidPointerToCOpaquePointer(p: CConstVoidPointer) -> COpaquePointer

这个过程是背后隐藏的。然后将转换的结果传参给对应的 C/ObjC 函数。实现了指针类型字长正确、一致。

结论

作为程序员,需要保证调用 C 函数的时候类型一致。如果有特殊需求重新声明了对应的 C 函数,那么以上规则不起作用,所以重声明 C 中的函数时表示指针不可以使用这四个指针类型。

再说指针

虚线表示直接隐式类型转换。其中 UnsafePointer<T> 可以通过用其他任何指针调用构造函数获得。

CMutablePointer<T>CMutableVoidPointer 也可以通过 Array<T> 的引用隐式类型转换获得 ( &arr )。

椭圆表示类型 sizeof 为字长,可以用于声明 C 函数。

四大指针可以用 withUnsafePointer 操作。转换为 UnsafePointer<T>。上一节提到的私有转换函数请不要使用。

字符串

之前的文章已经介绍过怎么从 CString 获取 String (静态方法 String.fromCString)。

String 获取 CString 也说过, 是用 withCString

也可以从 CString(UnsafePointer.alloc(100)) 来分配空数组。

参考

Tilde Arrow in Swift (Swift 标准库中的波浪箭头 ~> )

本文瞎写,没实际内容。请不用看了。

摘要

本文挖掘 Swift 标准库中的诡异操作符 ~> 波浪箭头的作用。

正文

查看标准库定义的时候,发现了一个奇怪的运算符 ~>,看起来高大上,所以探索下它到底起什么作用。

标准库对 ~> 用到的地方很多,我取最简单的一个来做说明。

1
2
3
4
5
6
7
protocol SignedNumber : _SignedNumber {
  func -(x: Self) -> Self
  func ~>(_: Self, _: (_Abs, ())) -> Self
}
func ~><T : _SignedNumber>(x: T, _: (_Abs, ())) -> T
func abs(_: CInt) -> CInt
func abs<T : SignedNumber>(x: T) -> T

这是对有符号整型的一个协议,我去掉了额外的属性。事实上 _Abs 类型是一个空结构, sizeof 为 0 。

写个测试程序,计算下 abs(-100) 看看情况,发现 top_level_code() 调用了 SignedNumber 版本的 abs()

1
callq  0x100001410               ; Swift.abs <A : Swift.SignedNumber>(A) -> A

反汇编这个库函数,发现一个有意思的调用:

1
callq  0x10000302a               ; symbol stub for: Swift._abs <A>(A) -> (Swift._Abs, A)

这个 _abs() 函数是私有函数, Swift 中把很多私有的函数、成员变量、结构、协议都以下划线开头,意思就是不希望我们去调用或者访问的函数,在缺乏成员访问控制的语言中,其实这么做也不错。大家可以借鉴。

_abs() 函数很简单,将任意类型 T 直接封装成 (_Abs, T) 元组,返回。

然后代码的逻辑就是用这个元祖解开重新组装,调用 ~>。逻辑如下:

1
2
3
// logic of abs() funciton
let (operation, val) = _abs(-100)
val ~> (operation, ()) // 返回 100

到这里就清楚了。实际上 ~> 将一个简单的操作复杂化。多调用了层,实际开销主要在元祖的解开和重组装(实际开销理论上在优化模式下应该可以忽略,因为包含 _Abs, size 为 0)。

到这里很多朋友应该已经知道怎么回事了。 SignedNumber 中的 ~> 操作是为我们提供了一个方法可以 hook 到标准库的 abs() 函数。来自 Haskell 的同学应该会见过这种单纯地用类型签名来实现函数分发调用的方式。

优点?

暂时正在考虑。想明白会发出来。

延伸

其实很多标准库函数都用到了类似的方法实现。都用到了 ~> 运算符。包括:

1
2
3
4
5
6
countElements()
// _countElements() 工具函数  _CountElements 结构
underestimateCount()
// _underestimateCount() 、 _UnderestimateCount
advance()
// _advance() 、 _Advance

等。

附录

这里列出部分定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protocol Sequence : _Sequence_ {
  typealias GeneratorType : Generator
  func generate() -> GeneratorType
  func ~>(_: Self, _: (_UnderestimateCount, ())) -> Int
  func ~><R>(_: Self, _: (_PreprocessingPass, ((Self) -> R))) -> R?
  func ~>(_: Self, _: (_CopyToNativeArrayBuffer, ())) -> ContiguousArrayBuffer<Self.GeneratorType.Element>
}
protocol Collection : _Collection, Sequence {
  subscript (i: Self.IndexType) -> Self.GeneratorType.Element { get }
  func ~>(_: Self, _: (_CountElements, ())) -> Self.IndexType.DistanceType
}

protocol ForwardIndex : _ForwardIndex {
  func ~>(start: Self, _: (_Distance, Self)) -> Self.DistanceType
  func ~>(start: Self, _: (_Advance, Self.DistanceType)) -> Self
  func ~>(start: Self, _: (_Advance, (Self.DistanceType, Self))) -> Self
}

相关更多声明代码信息请参考 我的 Github : andelf/Defines-Swift

总结

通过 ~>protocol 可以自定义编译器的行为。相当于 hook 标准库函数。由于内部实现未知,还不能继续断言它还有什么作用。

但是和直接用 extension 实现协议的方法相比,这个有什么好处呢?待考。

更新

可以避免 protocol 中的静态函数混淆空间,如果用全局函数,那么相当于全局函数去调用静态函数。

还有就是在使用操作符的时候,如果定义多个,那么需要编译器去寻找可用的一个版本。

仔细查看目前的 protocol 实现,发现还是有点 BUG ,类型限制还是不清楚,表述高阶类型的时候。

为了描述 ~> 的用法,我写了个 Monad.swift

Write Swift Module Cont. Static Library (使用 Swift 创建 Swift 模块 - 静态链接库)

声明: 转载注明我或者 SwiftChina, 请在方便的情况下情尽量告知. weibo

本文的发现基于个人研究。请尊重原创。

摘要

本文提出了一种可以编译 Swift 静态链接模块的方法,通过对 swift 编译命令行参数的控制,生成可以自由分发的静态链接库和 swift module 描述文件。同时还提出了导出 objC 头文件供 Objective-C 调用的可能。

关键词: Swift 模块 静态链接库

上次一篇文章 Module System of Swift (简析 Swift 的模块系统) 中提到:

静态链接库 .a 目前还没有找到方法, -Xlinker -static 会报错。

最近摸索了下用 Swift 创建静态链接库的方法。有所收获,这里记录下。

废话

我们中的很多人都知道,编译器编译的最后一个步骤一般都是链接,一般都是调用 ld。经过仔细分析,之前为什么不能生成 .a 静态链接库的原因,发现有如下问题:

  • -Xlinker -static 参数传递的时候, swift 命令本身不能识别,讲 -dylib-static 一起传递(这倒不是问题,参数优先级,静态盖掉了动态)
  • 链接到 -lSystem 时候,这个库没有静态链接。

所以总会报错。

思考

实际上之前的方法是走了弯路,根本没有必要去调用 ld,作为一个合格的 .a 静态链接库,只要有对应的 .o 就可以了,没必要去链接 -lSystem,也许是 swift 本身没有编译为静态链接库的参数支持。

检查 Swift 标准库中的静态链接库,果然只包含对应 .swift 代码编译后的 .o 文件。(检查方法是用 ar -t libName.a

说到底, Swift 静态链接库的目标很简单,就是包含对应 Swift 模块的所有代码,这样就避免了对应动态链接库的引入。和什么 -lSystem 没啥相干。

解决方法 HOWTO

以 lingoer 的 SwiftyJSON 为例。

我们的目标很简单,就是生成 ModName.swiftmoduleModName.swiftdoc(可选)、libswiftModName.a 三个文件。

编译

生成 .swiftmodule .swiftdoc

1
xcrun swift -sdk $(xcrun --show-sdk-path --sdk macosx) SwiftyJSON.swift -emit-library -emit-module -module-name SwiftyJSON -v -o libswiftSwiftyJSON.dylib -module-link-name swiftSwiftyJSON

生成 .o

1
xcrun swift -sdk $(xcrun --show-sdk-path --sdk macosx) -c SwiftyJSON.swift -parse-as-library -module-name SwiftyJSON -v -o SwiftyJSON.o

生成 .a

1
ar rvs libswiftSwiftyJSON.a SwiftyJSON.o

大功告成。

同时应该也可以用 lipo 来合成不同平台下的 .a 链接库。

使用

和静态链接库类似,需要 -I 包含 .swiftmodule 所在目录, -L 包含 .a 所在目录。

如果动态链接库和静态链接库两者同时存在,可以依靠不同目录来区分。

你丫闲的!

可能不少人要群嘲,你这意义是啥。你丫闲的。

其实在分发 library 的时候,很多时候我们需要二进制分发,希望别人可以方便地使用。这种情况下,静态链接更佳(虽然新的 iOS 8 支持动态链接,但是看起来是基于 Framework 的,略复杂些。)

甚至我们可以用 lipo 创建全平台可用的静态链接库。多赞。

补充

多个 Swift 文件可以分别编译为 .o 然后用 ar 合并。

对于 CocoaPods ,也许可以按照这个逻辑将 Swift 模块暴露出去。需要多加一个参数 -emit-objc-header (以及 -emit-objc-header-path)即可。

参考文献

Use CocoaPods With Swift (在 Swift 中使用 CocoaPods)

声明: 转载注明我或者 SwiftChina 。请在方便的情况下情尽量告知. weibo

本文的发现基于个人研究。请尊重原创。已授权 CocoaChina 转载个人文章。

本文介绍如何在 Swift 项目中使用 CocoaPods 。如果你已经精通 Bridging Header 的方法,请直接跳到 “扩展 CocoaPods” 一节。

什么是 CocoaPods

CocoaPods is the dependency manager for Objective-C projects. It has thousands of libraries and can help you scale your projects elegantly. 1

从介绍看,它是主要给 Objective-C 项目用的,但是我们可以很容易地混合 Objective-C 和 Swift 到同个项目,从而利用大量的 CocoaPods 库和 Swift 漂亮舒服的语法。

作为 iOS 开发新手,一定是要紧跟前人脚步,学习使用 CocoaPods 。

基础用法

这里简单略过,请参考其他无数的文章。

安装

系统默认安装,可以参考其他教程2 。在命令行下执行。

sudo gem install cocoapods

我的环境是 HomeBrew

1
2
3
4
5
6
7
8
9
# 添加 taobao Mirror 不然被墙掉没办法下载
gem sources -a http://ruby.taobao.org/ 
# 安装
gem install cocoapods
# 更新命令
rbenv rehash
# 执行
pod
# 此时一般会下载官方的所有 PodSpec 库,也可以用 pod setup 初始化环境

本文不打算在安装部分耗费太多时间。希望看到这里保证你的命令行下有可用的 pod 命令。

使用

假设我们已经有个项目,叫 ProjName ,需要使用一些注明的 CocoaPods 库,比如 AFNetworking3.

首先,命令行 cd 到我们的项目目录,一般 ls 命令会看到如下几个文件夹:

1
2
3
ProjName
ProjName.xcodeproj
ProjNameTests

赞,就是这里,创建一个 Podfile 文本文件,写入如下内容

1
2
platform :ios, "8.0"
pod "AFNetworking", "~> 2.0"

一般这么简单的文件都是直接 nano 写。 :)

直接创建 Podfile , CocoaPods 会创建一个项目同名的 WorkSpace ,然后添加一个叫 Pods 的项目,这个项目编译结果是一个叫 libPods.a的链接库, 它会添加到我们之前的 ProjName 项目中作为编译依赖。

当然,通过命令行执行 pod init 也可以自动创建 Podfile,而且可以自动分析当前项目的 target ,相对来说更好,也更优雅。具体请参考官方手册。这样的好处是更细致,还可以区分多个子项目子 target 。原理大同小异。

然后接下来,命令行执行 open ProjName.xcworkspace,注意这个可不是 .xcodeproj,这个是 CocoaPods 为我们创建的一个 WorkSpace ,包含我们之前的项目,和 Pods 依赖。

开始编码过程。直接在代码里调用,比如写在某个按钮的 @IBAction 里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    let manager = AFHTTPRequestOperationManager()
    let url = "http://api.openweathermap.org/data/2.5/weather"
    println(url)

    let params = ["lat": 39.26, "lon": 41.03, "cnt":0]
    println(params)

    manager.GET(url,
        parameters: params,
        success: { (operation: AFHTTPRequestOperation!,
                    responseObject: AnyObject!) in
            println("JSON: " + responseObject.description!)
        },
        failure: { (operation: AFHTTPRequestOperation!,
                    error: NSError!) in
            println("Error: " + error.localizedDescription)
        })

这里直接抄了 JakeLin 的 SwiftWeather 代码4,就一小段,希望他不会打我。

Swift 坑爹了

看起来貌似我们已经可以在 Swift 中使用 AFNetworking 了。结果刚写几句代码一堆类和变量找不到定义,而且坑爹的是很多时候我们只能靠猜测,判断这些 Objective-C 的定义转换成 Swift 定义是什么样子,用起来就是完全靠蒙!

这不科学!

这都三礼拜了,所以大家都摸索出了调用的方法,那就是按照和 Objective-C 代码混编的例子,添加 Bridging Header !

继续

之前简单介绍过和 Objective-C 交互的内容5,大家可以去围观。

一般说来,你在 Swift 项目新建 Objective-C 类的时候,直接弹出是否创建 Bridge Header 的窗口,点 YES 就是了,这时候一般多出来个 ProjectName-Bridging-Header.h 。然后删掉这个类, Bridging Header 头文件还在。

在这个 Bridging Header 文件里写入要导入的 CocoaPods 库,就可以在 Swift 中使用了。

#import <AFNetworking/AFNetworking.h>

如果没有自动创建头文件的话,这个配置在项目的 Build Settings 中的 Swift Compiler – Code Generation 子项里。

创建一个头文件,指定为 Bridging Header 也可以。

然后编译,成功执行!

这就完事了?

实际上,前两天刚写一篇 Swift 的模块系统 , 把任意 Objective-C 库当做 Swift Module 是可行的。当时就觉得这个东西应该是可能完全进入 CocoaPods 的,但是在官方 repo 找了下发现,以前有人提过增加 module.map 支持,结果 CocoaPods 的人认为这个是 llvm 内部特性, issue 被关闭了。#2216 最近又被提起,我在后面提了下 Swift 支持,希望官方靠谱。

所以下面的内容,就是,我们是否可以在 CocoaPods 上加入 module.map 支持,然后直接在 Swift 中 import ModuleName

扩展 CocoaPods

考虑了多种方式,最后选择了 Hook 的方式。如果 Ruby 技术足够好,或许可以直接写个插件。或者直接改官方代码给官方提交。但是实在能力有限。相关的 module.map 语法参考 llvm 官方手册 Modules – Clang 3.5 documentation。用了最简单的功能。也许遇到复杂的 PodSpec 就不起作用了,但是原理如此,相信小伙伴们已经知道怎么做了。

目前我的 Podfile 大概是这个样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
platform :ios, "8.0"
pod "AFNetworking", "~> 2.0"
pod "Baidu-Maps-iOS-SDK", "~> 2.0"

post_install do |installer|
  File.open("#{installer.sandbox_root}/Headers/module.map", 'w') do |fp|
    installer.pods.each do |pod|
      normalized_pod_name = pod.name.gsub('-', '')
      fp.write <<EOF
module #{normalized_pod_name} [system] {
  umbrella "#{pod.name}"
  export *
}
EOF
      puts "Generating Swift Module #{normalized_pod_name.green} for #{pod} OK!"
    end
  end
end

post_installPodfile 的一种 hook 机制,可以用来加入自定义操作。我在这里的写的逻辑就是,针对所有的 Pod 生成一个 module.map 文件。 位于 Pods/Headers/,这个目录被 CocoaPods 自动设置为项目的 Header Search Path 所以不需要额外处理。默认我们的 Swift 文件就找得到。

其中 normalized_pod_name 用于处理百度地图 API SDK 这一类名字带减号的库,因为他们不能作为 Module Name ,实际上或许有更好的方法来处理。

实际效果

实测发现完全没有问题,直接 import AFNetworking 或者 import BaiduMapsiOSSDK 都可以。

而且很不错的一点是,按住 Command 键,然后鼠标点击模块名、类名等,会跳转到 Swift 定义。

遇到提示 .pcm 文件 outdate 的情况下需要你删除 $HOME/Library/Developer/Xcode/DerivedData/ModuleCache 目录,这个目录保存的是预编译模块,类似于预编译头文件。

目前 Swift 还是有很多 BUG 的,调用 NSObject 也许会让编译器直接 segment fault ,不带任何出错信息。很伤情。此时请第一时间检查语法是否有诡异,其次将所有用到字符串或者 Optional 的地方都额外用变量处理,避免用字面常量。(个人经验)

如果多次调用 pod install 并在其中修改过 Podfile,那么有可能你的项目依赖会乱掉,多了不存在的 .a 文件到依赖或者多次包含。手工在项目树和项目选项里删除就可以了。此类编译错误都是链接错误。

总结

本文提出了一种 Bridging Header 之外的使用 CocoaPods 库的方法。利用有限的 Ruby 知识写了个 Hook 。目前测试 OK 。

参考