猫·仁波切

会研发的PM才是好OP.

在 CircleCI 上使用 Rust(CircleCI Meets Rust)

最近由于频频遇到 travis-ci 的问题,主要是 Linux 资源排队、macOS 资源更需要排队,导致自动测试时间被拉长, 影响开发效率。

了解到 CircleCI 是不错的替代品,所以打算迁移 Rust 项目过去。当然说起来, CircleCI 的野心更大,是要来替代 jenkins 的。

目前官方支持语言其实都比较落后,包括 go 也只是 1.6 版本,但似乎不是问题,而且据介绍, CircleCI 2.0 支持自定义 build image,支持语言的版本当然不在话下。

每天面对各种 IaaS, PaaS,免不了写配置是,这也是 yaml 程序员的日常。

1
2
3
4
5
6
7
8
dependencies:
  pre:
    - curl https://sh.rustup.rs -sSf | sh

test:
  override:
    - cargo build
    - cargo test

如上。然而不 work。报错:

1
2
3
4
5
6
7
8
9
10
11
cargo build
    Updating registry `https://github.com/rust-lang/crates.io-index`
warning: spurious network error (2 tries remaining): [12/-12] Malformed URL 'ssh://git@github.com:/rust-lang/crates.io-index'
warning: spurious network error (1 tries remaining): [12/-12] Malformed URL 'ssh://git@github.com:/rust-lang/crates.io-index'
error: failed to fetch `https://github.com/rust-lang/crates.io-index`

To learn more, run the command again with --verbose.

cargo build returned exit code 101

Action failed: cargo build

神了。原来, CircleCI 自作聪明在 .gitconfig 里修改了映射配置,强制用它自己的 ssh key 去访问 github,rewrite 了 https://github.com 的所有仓库。 这恰恰和 cargo 的 registry 机制冲突。所以报错。

CircleCI has rewrite https:://github.com to ssh://git@github.com: in .gitconfig. And this made cargo fail with above error message.

找到了原因,就可以搞了:

1
2
3
4
5
6
7
8
9
10
11
12
machine:
  pre:
    - sed -i 's/github/git-non-exist-hub/g' ~/.gitconfig

dependencies:
  pre:
    - curl https://sh.rustup.rs -sSf | sh

test:
  override:
    - cargo build
    - cargo test

嗯, Ugly but works.

折腾 Raspberry Pi + HomeKit 手记

9月14日凌晨苹果终于推送了 iOS 10 的更新。从之前发布会来看,并没有多少亮点,除了几天的新鲜感之外, 尤其是对于目前还在用上两代机型的我来说,2333。

两年前苹果发布 Swift 语言的同时,新增了 HomeKit,当时用工具 dump 过最老版本的 Swift 声明。传送门:HomeKit.swift。目前所有官方相关的资料位于 HomeKit - Apple

好消息是期待很久的 HomeKit 应用终于上线,屏幕上多了“家庭(Home)”应用,控制中心(从屏幕下方滑动)、 Siri 均对此有支持。 iOS 10 终于强化了推出已有两年智能家居平台,提供了官方 App,有不少硬件厂商支持。

简单说,HomeKit 就是苹果官方的智能家居平台解决方案,包括移动设备 SDK,智能家居硬件通信协议(HAP: HomeKit Accessory Protocol)、以及 MFi(Made for iPhone/iPod/iPad) 认证等等。通过 WiFi 或蓝牙连接智能家居设备(或 bridge 设备),也可以利用 Apple TV(4代) 或闲家中的置 iPad 实现设备的远程控制(HAP over iCloud)。

Home App 的维度划分:

  • Home: 家,和地理位置绑定,支持共享给好友控制。
  • Room: 房间,用于对设备进行分组。
  • Scene: 场景,一组对设备的配置,例如“起床”,那么可能的配置是打开卧室灯、窗帘、放段舒缓music等等。

众所周知苹果是卖数据线等硬件的公司(嗯,假设你数据线也坏过不少),HAP 协议部分是需要加入 MFi Program 才能获取文档,而且 MFi Program 无法以个人开发者身份加入。

好在有好心人逆向了 HAP 的服务端协议(对于智能硬件来说,硬件是服务端,手机App是客户端)。

对于折腾党来说,机会来了,自己动手改造家居!本文不涉及 App 开发,只涉及如何自制支持 HomeKit 的设备。

准备工作

设备列表:

  • iPhone 6P (iOS 10)
  • Raspberry Pi 3 (Debian jessie)

考察了两个比较靠谱的 HAP 实现:

最终选择使用 golang 的 brutella/hc,准备环境。

需要保证树莓派和手机位于统一子网,因为 HAP 底层是基于 Apple mDNS(RFC 6762)。

brutella/hc 要求 golang >= 1.4,而 Debian jessie 版本较低, 需要配置 jessie-backports 源:

1
deb ftp://ftp.cn.debian.org/debian jessie-backports main contrib non-free

同时导入源的 GPG Key。方法参考 这里

安装好 golang 1.6.2,建立开发目录。

1
2
3
# 似乎直接 install golang 会出点小问题,所以折衷用了如下方法:
> sudo apt-get install -t jessie-backports golang-1.6 golang-1.6-go golang-1.6-src golang-1.6-doc
> sudo apt-get install -t jessie-backports golang

示例

跑通官方示例代码:

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
package main

import (
  "github.com/brutella/hc"
  "github.com/brutella/hc/accessory"
  "log"
)

func main() {
  info := accessory.Info{
      Name:         "Lamp",
      SerialNumber: "051AC-23AAM1",
      Manufacturer: "Apple",
      Model:        "AB",
  }
  acc := accessory.NewSwitch(info)

  acc.Switch.On.OnValueRemoteUpdate(func(on bool) {
      if on == true {
          log.Println("Client changed switch to on")
      } else {
          log.Println("Client changed switch to off")
      }
  })

  config := hc.Config{Pin: "00102003"}
  t, err := hc.NewIPTransport(config, acc.Accessory)
  if err != nil {
      log.Fatal(err)
  }

  hc.OnTermination(func() {
      t.Stop()
  })

  t.Start()
}

编译执行.

1
2
3
4
5
6
7
8
9
10
$ AppleHome> # current dir

$ AppleHome> go get
...

$ AppleHome> go build
...

$ AppleHome> ./AppleHome
...

随后打开手机的 Home App,添加设备,选择 Lamp,输入 PIN 00102003,完成配对,即可使用。

自定义设备

树莓派外接小音箱一只,用来放电台,尝试用 HomeKit 控制树莓派的禁音。命令:

1
2
amixer set PCM on
amixer set PCM off

代码:

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
package main

import (
        "os/exec"
        "github.com/brutella/hc"
        "github.com/brutella/hc/accessory"
        "log"
)

func main() {
        info := accessory.Info{
                Name:            "Radio",
                SerialNumber: "051AC-23AAM2",
                Manufacturer: "Apple",
                Model:          "RPI3",
        }
        acc := accessory.NewSwitch(info)

        acc.Switch.On.OnValueRemoteUpdate(func(on bool) {
                log.Println("Toggled PCM!")

                if on == true {
                        exec.Command("amixer", "set", "PCM", "on").Run()
                        log.Println("Client changed switch to on")
                } else {
                        exec.Command("amixer", "set", "PCM", "off").Run()
                        log.Println("Client changed switch to off")
                }
        })

        config := hc.Config{Pin: "00102004"}
        t, err := hc.NewIPTransport(config, acc.Accessory)
        if err != nil {
                log.Fatal(err)
        }

        hc.OnTermination(func() {
                t.Stop()
        })

        t.Start()
}

其他

HAP 将智能家居分为以下维度:

  • Accessory: 单个设备,例如开关,温度计,调节器
  • Service: 一组值,合起来提供服务,例如中央空调(调节温度,风速等等)

TODO

Swift 3.0 尝试——从入门到再学一门(a Glimpse of Swift 3.0)

安装工具

https://github.com/kylef/swiftenv

Swift 3.0 新变化

以下内容来自 Swift 语言提案1

Swift 3.0 发布计划

Swift Package System

https://github.com/donald-pinckney/swift-packages

AST 结构

代码位于 include/swift/ASTlib/AST

ModuleDecl 模块(单个库或是可执行文件)。编译的最小单元,由多个文件组成。

FileUnit(抽象类) 文件作用域,是代码组织的最小单元。

  • DerivedFileUnit: A container for a module-level definition derived as part of an implicit protocol conformance.
  • SourceFile: A file containing Swift source code. .swift 或 .sil 也可以是虚拟 REPL
    • Imports: Vec<(ImportedModule, ImportOptions)>
    • Identifier
    • Decls: Vec
    • LocalTypeDecl: Vec
    • ObjCMethods: Map<ObjCSelector, AbstractFunctionDecl>
    • infix, postfix, prefix operators: OperatorMap
  • BuiltinUnit

swift 命令入口

入口函数 tools/driver/driver.cppmain 函数。

集成多个子工具。同时若 PATH 下有名为 swift-foobar 的可执行文件,则可通过 swift foobar 调用。

编译器前端 swift -frontend

编译。同时支持打印出各种编译时中间结果。

API Notes 功能 swift -apinotes

参考信息位于 https://github.com/apple/swift/tree/master/apinotes .

简单说,API Notes 机制就是通过 .apinotes 文件(YAML格式)描述 Objective-C Framework 和对应 Swift API 的关系。最终生成 .apinotesc 文件,与 .swiftmodule 文件一起作为 Swift 的模块。

主要功能包括且不限于:

  • SwiftBridge:设置对应的 Bridge 类型,例如 NSArray 对应与 Swift.Array
  • Nullability/NullabilityOfRet: 类的属性、方法的参数、返回值对应类型是否可以为 null,即对应与 Swift 的 T 还是 T?
  • Availability:方法是否在 Swift 中暴露,并给出 availability message
  • SwiftName:方法 Selector 在 Swift 中的重命名,例如 filteredArrayUsingPredicate: 替换为 filtered(using:)

Dump 为YAML文件:

$> swift -apinotes -binary-to-yaml /path/to/lib/swift/macosx/x86_64/Dispatch.apinotesc -o=-

Module Wrap 工具 swift -modulewrap

1
2
3
4
// Wraps .swiftmodule files inside an object file container so they
// can be passed to the linker directly. Mostly useful for platforms
// where the debug info typically stays in the executable.
// (ie. ELF-based platforms).

用法:

swift -modulewrap ObjectiveC.swiftmodule -o objc.o

实际发现是在 .o 里定义了 ___Swift_AST 符号。

REPL

Swift 提供了两个 REPL(Read-Evaluate-Print Loop),一个是 Swift 本身内置,另一个集成到了 lldb 命令行下。前者只有基本功能,即将废弃,后者功能更强大。

子命令分别是:

  • swift -deprecated-integrated-repl
  • swift -lldb-repl

swift -repl 子命令选择可用的 REPL 进入,一般是 lldb-repl,除非找不到 lldb 时。这也是 Swift 命令不带任何参数的默认行为。


  1. (apple/swift-evolution)[https://github.com/apple/swift-evolution]

Rust Pattern Match(Rust中的模式匹配)

模式匹配

汉语字典中对“模式”的解释是:事物的标准样式。在计算机科学中,它指特定类型的数据(往往是序列或是树形结构)满足某一特定结构或格式。“匹配”本身是指一个判断寻找过程。最早的模式匹配用于文本编辑器中的正则字符串搜索,之后才作为编程语言特性。

模式匹配基础

模式匹配在计算机科学领域有两层意思。其一,可以特指字符串匹配算法,例如为人熟知的 KMP 字符串匹配算法、命令行工具 grep 等。 其二,特指在一些语言中作为一种以结构的方式处理数据的工具,此时的匹配过程往往是树形匹配,与此相伴的往往还有一个特性叫 guard(守卫)。

Rust 中模式匹配随处可见,例如在let变量绑定语句、match匹配语句中等。利用好模式匹配这一特性可以使代码更简洁易懂。Rust支持模式匹配中的变量绑定、结构体/元组解构、守卫条件判断、数值范围匹配等特性。

原始匹配

match 语句中可以直接匹配字面常量,下划线_匹配任意情形。

1
2
3
4
5
6
7
8
let x = 1;

match x {
    1 => println!("one"),
    2 => println!("two"),
    3 => println!("three"),
    _ => println!("anything"),
}

以上代码会打印出one

结构匹配

match 用于匹配一个表达式的值,寻找满足条件的子分支(arm)并执行。每个子分支包含三部分:一系列模式、可选的守卫条件以及主体代码块。

多个模式

每个子分支可以是多个模式,通过 | 符号分割:

1
2
3
4
5
6
7
let x = 1;

match x {
    1 | 2 => println!("one or two"),
    3 => println!("three"),
    _ => println!("anything"),
}

以上代码打印出one or two

守卫条件

通过if引入子分支的守卫条件:

1
2
3
4
5
6
7
8
9
10
11
12
enum OptionalInt {
    Value(i32),
    Missing,
}

let x = OptionalInt::Value(5);

match x {
    OptionalInt::Value(i) if i > 5 => println!("Got an int bigger than five!"),
    OptionalInt::Value(..) => println!("Got an int!"),
    OptionalInt::Missing => println!("No such luck."),
}

模式匹配进阶

其实进阶,不如直接从libsyntax源码看看到底模式匹配是如何实现。syntax::ast::Pat

从AST源码中寻找语法要素屋外户两个要点,其一,语法要素是如何表达为对应AST的;其二,对应AST在哪些父AST中出现。

Rust中使用syntax::ast::Pat枚举来表示一个模式匹配。

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
pub struct Pat {
    pub id: NodeId,
    pub node: PatKind,
    pub span: Span,
}

pub enum PatKind {
    /// Represents a wildcard pattern (`_`)
    /// 表示通配,下划线
    Wild,

    /// A `PatKind::Ident` may either be a new bound variable,
    /// or a unit struct/variant pattern, or a const pattern (in the last two cases
    /// the third field must be `None`).
    ///
    /// In the unit or const pattern case, the parser can't determine
    /// which it is. The resolver determines this, and
    /// records this pattern's `NodeId` in an auxiliary
    /// set (of "PatIdents that refer to unit patterns or constants").
    Ident(BindingMode, SpannedIdent, Option<P<Pat>>),

    /// A struct or struct variant pattern, e.g. `Variant {x, y, ..}`.
    /// The `bool` is `true` in the presence of a `..`.
    Struct(Path, Vec<Spanned<FieldPat>>, bool),

    /// A tuple struct/variant pattern `Variant(x, y, z)`.
    /// "None" means a `Variant(..)` pattern where we don't bind the fields to names.
    TupleStruct(Path, Option<Vec<P<Pat>>>),

    /// A path pattern.
    /// Such pattern can be resolved to a unit struct/variant or a constant.
    Path(Path),

    /// An associated const named using the qualified path `<T>::CONST` or
    /// `<T as Trait>::CONST`. Associated consts from inherent impls can be
    /// referred to as simply `T::CONST`, in which case they will end up as
    /// PatKind::Path, and the resolver will have to sort that out.
    QPath(QSelf, Path),

    /// A tuple pattern `(a, b)`
    Tup(Vec<P<Pat>>),
    /// A `box` pattern
    Box(P<Pat>),
    /// A reference pattern, e.g. `&mut (a, b)`
    Ref(P<Pat>, Mutability),
    /// A literal
    Lit(P<Expr>),
    /// A range pattern, e.g. `1...2`
    Range(P<Expr>, P<Expr>),
    /// `[a, b, ..i, y, z]` is represented as:
    ///     `PatKind::Vec(box [a, b], Some(i), box [y, z])`
    Vec(Vec<P<Pat>>, Option<P<Pat>>, Vec<P<Pat>>),
    /// A macro pattern; pre-expansion
    Mac(Mac),
}

以上AST定义,即说明,到底什么被认为是一个“模式”。

以下介绍Pat在哪些AST中出现。

全局 Item

全局 Item 中,使用模式匹配的均为函数参数。

ItemKind::Fn

Fn 全局函数 -> FnDecl 函数声明 -> [Arg] 函数头参数声明。

ItemKind::Trait

Trait -> [TraitItem] -> TraitItemKind::Method -> MethodSig -> FnDecl 方法声明,同上。

ItemKind::Impl

Impl -> [ImplItem] -> ImplItemKind::Method -> MethodSig -> FnDecl

ast::Stmt 语句

StmtKind::Decl

Decl -> DeclKind::Local

let 语句 let <pat>:<ty> = <expr>;

StmtKind::Expr 表达式

见下。

ast::Expr

match外,if letwhile letfor控制语句支持同时进行模式匹配。具体实现是一种desugared过程,即,去语法糖化。

同时类似于函数定义,闭包参数也支持模式匹配。

if let

IfLet(P<Pat>, P<Expr>, P<Block>, Option<P<Expr>>)

if let pat = expr { block } else { expr }

This is desugared to a match expression.

while let

WhileLet(P<Pat>, P<Expr>, P<Block>, Option<Ident>)

'label: while let pat = expr { block }

for

ForLoop(P<Pat>, P<Expr>, P<Block>, Option<Ident>)

'label: for pat in expr { block }

match

Match(P<Expr>, Vec<Arm>)

match 语句,在 Arm 中出现,其中 Arm 定义为

1
2
3
4
5
6
pub struct Arm {
    pub attrs: Vec<Attribute>,
    pub pats: Vec<P<Pat>>,
    pub guard: Option<P<Expr>>,
    pub body: P<Expr>,
}

闭包

Closure(CaptureBy, P<FnDecl>, P<Block>)

闭包,例如 move |a, b, c| {a + b + c}

相关 feature gate

advanced_slice_patterns - See the match expressions section for discussion; the exact semantics of slice patterns are subject to change, so some types are still unstable.

slice_patterns - OK, actually, slice patterns are just scary and completely unstable.

box_patterns - Allows box patterns, the exact semantics of which is subject to change.

参考

https://doc.rust-lang.org/book/patterns.html

广州实时工具App逆向

简记。用了 IDA Pro,安卓手机的 Remote 客户端。以及 apktool 等。

Github: guangzhou-realtime-bus

  • 生成 e=3 的 1024 位 RSA 密钥对
  • 公钥串用查表加密(byte 映射),然后 base64 封装发送给服务器
  • 服务器返回一串用公钥加密过的数据
  • 用本地私钥解密后,该数据包含未知96字节的一段数据和 DES Key
  • 从此通信用 DES 加密

base64封装过程:先打包字符串长度,然后是原始字符串(JSON),然后是0x10(md5字符串长度), 然后是 md5 校验值。整个二进制字符串用 base64 转码,POST 给服务器。

具体的登录注册过程还需要进一步抓包分析,不过暂时兴趣不在这里了。

Swift 2.0 的错误处理(Swift 2.0 Error Handling)

1
2
3
4
5
6
7
8
9
10
protocol ErrorType {
  var _domain: String { get }
  var _code: Int { get }
}

@asmname("swift_bridgeErrorTypeToNSError") func _bridgeErrorTypeToNSError(e: ErrorType) -> AnyObject

@asmname("swift_stdlib_getErrorCode") func _stdlib_getErrorCode<T : ErrorType>(x: UnsafePointer<T>) -> Int

@asmname("swift_stdlib_getErrorDomainNSString") func _stdlib_getErrorDomainNSString<T : ErrorType>(x: UnsafePointer<T>) -> AnyObject

Foundation

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
protocol _ObjectiveCBridgeableErrorType : ErrorType {
  init?(_bridgedNSError: NSError)
}

struct NSCocoaError : RawRepresentable, _BridgedNSError, _ObjectiveCBridgeableErrorType, ErrorType, __BridgedNSError, Hashable, Equatable {
  let rawValue: Int
  init(rawValue: Int)
  static var _NSErrorDomain: String {
    get {}
  }
  typealias RawValue = Int
}


infix func ==(a: _GenericObjCError, b: _GenericObjCError) -> Bool
infix func ==(a: _GenericObjCError, b: _GenericObjCError) -> Bool
func ==<T : __BridgedNSError where T.RawValue : SignedIntegerType>(lhs: T, rhs: T) -> Bool

@available(OSX 10.11, iOS 9.0, *)
func resolveError(error: NSError?) throws

enum _GenericObjCError : ErrorType {
  case NilError
  var hashValue: Int {
    get {}
  }
  var _domain: String {
    get {}
  }
  var _code: Int {
    get {}
  }
}

@asmname("swift_stdlib_bridgeNSErrorToErrorType")
func _stdlib_bridgeNSErrorToErrorType<T : _ObjectiveCBridgeableErrorType>(error: NSError, out: UnsafeMutablePointer<T>) -> Bool

@asmname("swift_convertNSErrorToErrorType") func _convertNSErrorToErrorType(error: NSError?) -> ErrorType

@objc enum NSURLError : Int, _BridgedNSError, _ObjectiveCBridgeableErrorType, ErrorType, __BridgedNSError { ... }


protocol __BridgedNSError : RawRepresentable {
  static var _NSErrorDomain: String { get }
}
@asmname("swift_convertErrorTypeToNSError") func _convertErrorTypeToNSError(error: ErrorType) -> NSError
func ~=(match: NSCocoaError, error: ErrorType) -> Bool
protocol _BridgedNSError : __BridgedNSError, _ObjectiveCBridgeableErrorType, Hashable {
  static var _NSErrorDomain: String { get }
}

ErrorType 在 Swift 中表示。

1
2
3
4
5
6
7
8
extension NSError : ErrorType {
  @objc dynamic var _domain: String {
    @objc dynamic get {}
  }
  @objc dynamic var _code: Int {
    @objc dynamic get {}
  }
}

北京实时公交分析

361 条线路,705条单向线路。 aibang 负责数据服务。

每辆车,每15秒更新一次 GPS,

为第三方扩展创建 Swift 模块

本文提出了一种将第三方扩展引入到 Swift 标准库的方法。

以 Alamofire 为例,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
cd Path-To-Alamofire-Src-Dir
mkdir -p 32 64

# 创建动态链接库,及对应 Swift 模块,32/64版本
xcrun swiftc -sdk $(xcrun --show-sdk-path --sdk iphoneos) Alamofire.swift -target arm64-apple-ios7.1 -target-cpu cyclone -emit-library -emit-module -module-name Alamofire -v -o libswiftAlamofire.dylib -module-link-name swiftAlamofire -Xlinker -install_name -Xlinker @rpath/libswiftAlamofire.dylib

mv Alamofire.swiftdoc Alamofire.swiftmodule libswiftAlamofire.dylib ./64

xcrun swiftc -sdk $(xcrun --show-sdk-path --sdk iphoneos) Alamofire.swift -target armv7-apple-ios7.1 -target-cpu cyclone -emit-library -emit-module -module-name Alamofire -v -o libswiftAlamofire.dylib -module-link-name swiftAlamofire -Xlinker -install_name -Xlinker @rpath/libswiftAlamofire.dylib

mv Alamofire.swiftdoc Alamofire.swiftmodule libswiftAlamofire.dylib ./64

# 创建 universal lib
lipo -create ./{32,64}/libswiftAlamofire.dylib  -output ./libswiftAlamofire.dylib

# 创建模拟器用 lib
xcrun swiftc -sdk $(xcrun --show-sdk-path --sdk iphonesimulator) Alamofire.swift -target i386-apple-ios7.1 -target-cpu yonah -emit-library -emit-module -module-name Alamofire -v -o libswiftAlamofire.dylib -module-link-name swiftAlamofire -Xlinker -install_name -Xlinker @rpath/libswiftAlamofire.dylib

其他相关 target

1
2
3
4
-target armv7-apple-ios7.1 -target-cpu cortex-a8
-target arm64-apple-ios7.1 -target-cpu cyclone
-target i386-apple-ios7.1 -target-cpu yonah
-target x86_64-apple-ios7.1 -target-cpu core2

其实你了解 Swift 模块结构的化,应该回想到,将第三方模块创建为 swiftmodule 应该是最靠谱的选择。不过实际操作发现, 编译命令无法很方便地调整,主要是因为 xcodebuild 系统,和编译命令不知道怎么导出。也是略纠结。

实际上,如果使用 Carthage 的话,即把第三方扩展作为 Framework 引入,会导致无法支持 iOS 7,但是 Swift 本身是支持 iOS 7 的, 在编译命令和生成的文件中检查发现,对于 iOS 7,Swift 使用了纯静态模块编译的方法。所以其实我们引入第三方扩展的时候也可以这样做。

以下是静态编译所需命令:

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

ar rvs libswiftSwiftyJSON.a SwiftyJSON.o

如何使用?

将编译结果扔到:

1
2
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift_static

下对应目录。

然后在 Xcode 里,直接 import。

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,应该是更佳的解决方案,但是需要控制命令行参数。

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

参考