Docker in Production 笔记

In production

docker-workshop-thought-works

上周六参加了thoughtworks的docker workshop活动, 主要探讨了docker技术在生产环境应用的问题.

前面介绍了一下容器相关技术, 如Linux Container(lxc), 也介绍了namespace隔离等等.

还有docker的架构:

docker-architecture

然后又有部分常见的docker使用技术, 如docker compose, 使用compose.yml文件指定容器的创建, 以及整体管理等.

进阶

文件系统的挂载, 包括数据卷挂载(-v), 以及制作+使用数据容器的思路方向.

容器网络模型, host内的bridge, 跨host的overlay, 以及sandbox/endpoint定义.

docker-network

也扩展了一下安全性问题. (docker真的权限…太高了)

多主机部署于服务发现

Consul服务发现.

Registrator服务注册.

Docker Swarm集群.

日志/监控

docker logs的使用局限.

一点关于实际应用的思考

在docker的助力下,将每个服务,做成一个容器,然后跑在公司所有的主机上,这样的微服务架构想想就知道才是当下最合理的方式。

借助cluster解决方案,可以将公司的多服务器主机的资源合起来利用,并能更好的应对服务故障。

而容器微服务,在开发本地调试,一旦通过测试可以发布,发布的过程也是简单而不会出错。

构建好如上讲述的服务注册+发现,更能动态获取服务地址,自动完成服务之间的连接,才称得上是科学做法。

而服务状态监控,健康检查,日志收集处理,这些服务构建好,才能真的在出错时及时追溯问题,更能通过预警邮件及时知道问题。

一个产品环境,必须是科学专业的环境,而不是单单能运行就行。

Rust-Lang Notes

Rust语言学习笔记

Installation 安装

习惯的

$ brew search rust
==> Searching local taps...
rust       rustc-completion       rustup-init      uncrustify

然后顺手就可以:

$ brew install rust

这样安装完之后是可以使用rustc等的.

然而缺少rustup工具, 也因为brew同一管理路径的原因, 缺失~/.cargo目录, (不知道为啥没有至少一个软连接?).

所以出于学习阶段(在我还没弄清谁更好时), 先使用官方的方法(况且cargo是Rust的package manager):

如果brew安装过的, 别忘了先brew uninstall rust卸载掉.

$ curl https://sh.rustup.rs -sSf | sh
info: downloading installer

Welcome to Rust!

This will download and install the official compiler for the Rust programming
language, and its package manager, Cargo.

It will add the cargo, rustc, rustup and other commands to Cargo's bin
directory, located at:

  /Users/blodely/.cargo/bin

This path will then be added to your PATH environment variable by modifying the
profile files located at:

  /Users/blodely/.profile
  /Users/blodely/.zprofile

You can uninstall at any time with rustup self uninstall and these changes will
be reverted.

Current installation options:

   default host triple: x86_64-apple-darwin
     default toolchain: stable
  modify PATH variable: yes

1) Proceed with installation (default)
2) Customize installation
3) Cancel installation
1

info: syncing channel updates for 'stable-x86_64-apple-darwin'
info: latest update on 2017-08-31, rust version 1.20.0 (f3d6973f4 2017-08-27)
info: downloading component 'rustc'
 35.1 MiB /  35.1 MiB (100 %) 121.6 KiB/s ETA:   0 s
info: downloading component 'rust-std'
 49.0 MiB /  49.0 MiB (100 %) 128.0 KiB/s ETA:   0 s
info: downloading component 'cargo'
  2.6 MiB /   2.6 MiB (100 %) 124.8 KiB/s ETA:   0 s
info: downloading component 'rust-docs'
  3.6 MiB /   3.6 MiB (100 %) 115.2 KiB/s ETA:   0 s
info: installing component 'rustc'
info: installing component 'rust-std'
info: installing component 'cargo'
info: installing component 'rust-docs'
info: default toolchain set to 'stable'

  stable installed - rustc 1.20.0 (f3d6973f4 2017-08-27)


Rust is installed now. Great!

To get started you need Cargo's bin directory ($HOME/.cargo/bin) in your PATH
environment variable. Next time you log in this will be done automatically.

To configure your current shell run source $HOME/.cargo/env

这里当然猴急的运行source $HOME/.cargo/env,

再就测试一下:

$ rustc --version
rustc 1.20.0 (f3d6973f4 2017-08-27)

一切正常.

Hello, world

$ vim main.rs

然后录入:

fn main() {
    println!("Hello, world!~");
}

编译:

$ rustc main.rs

运行二进制文件:

$ ./main
Hello, world!~

Cargo

Cargo命令给出的解释是: Rust’s package manager.

好吧, 使用Cargo来创建一个工程看看.

$ cargo new hello_cargo --bin
    Created binary (application) `hello_cargo` project

这样就创建了一个binary即应用程序(而不是一个库)的工程了.

cd进去发现, 还给默认初始化成一个git仓库了.

$ git status
On branch master

Initial commit

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    .gitignore
    Cargo.toml
    src/

文件结构已归好, 还增加git ignore文件.

查阅文档, --vcs参数可控制此项.

然后这个Cargo.toml文件类型让我为之一震…

toml = Tom’s Obvious, Minimal Language GithubRepo

$ cat Cargo.toml
[package] # 段落标题; 配置一个包
name = "hello_cargo"
version = "0.1.0"
authors = ["Luo Yu <indie.luo@gmail.com>"]

[dependencies] # 项目依赖的crates列表(crate: Rust代码包)

构建与运行:

$ cargo build # cargo run
   Compiling hello_cargo v0.1.0 (file:///Users/blodely/Desktop/hello_cargo)
    Finished dev [unoptimized + debuginfo] target(s) in 3.27 secs
$ ./target/debug/hello_cargo
Hello, world!

Author

Luo Yu

indie.luo@gmail.com

Tuesday, September 12, 2017

Emacs 新手笔记

Learning Emacs

还在Kotei的时候, 跟着一个Tuts+教程, 花了4天熟悉Vim.
从此入了Vim阵营, 感叹它在啥环境下都有.
那时也留下了学习emacs的念想.
这么多年过去, 是时候付诸实践啦~

会持续在此更新相关笔记.

注解

C代表Ctrl

M代表Meta键(Alt键)

Operations

终止emacs会话C-x C-c

退出正在键入的命令C-g

Screen-moving Operations

下一屏C-v, 上一屏M-v

光标所在字符的 居中/居上/居下C-l

Cursor-moving Operations

前一行C-p (Previous line)
退一格C-b (Backward)
进一格C-f (Forward)
下一行C-n (Next line)

进一词M-f (Forward)
退一词M-b (Backward)

行头C-a
行尾C-e
句头M-a
句尾M-e

篇头M-<
篇尾M->
<>键都处于上键位置, 所以输入时是需要按住Shift键.

位移命令可接受数字参数, 使用C-u录入数字, 然后会被重复作用到后续的位移命令上.
按住M输入数字也等同于此操作.
例如C-u 8 C-f会前进8个字符.

Author

Luo Yu
Friday, September 8, 2017

Bluemix 试用

Bluemix 试用

账号

官网注册一下bluemix的账号, 如果之前注册过IBM的账号的话, 可以直接拿来登录并激活该功能.

(有部分Script似乎在墙外, 最好翻一下操作).

创建空间

首次登入, 会被告知试用天数, 和简单的收费介绍, 大概就是超出免费使用额的时候进行收费.

然后呢, 会有一个指引, 来创建”组织名称”>”空间名称”. 这里也会选择服务器物理位置. 自动给我匹配了一个“悉尼”, 看似最近的也只有这个了.

创建服务

选一个服务吧. 各类应用容器…(这样算, 一个服务一份钱💰).

因为要部署的是Hubot, 一个NodeJS做的程序, 所以这里就直接选个CloudFoundry的Node.js应用类型就行啦.

同样, 创建好名称, 比如我这里就叫“nodejshubot”.

创建完后, 系统会自动为你启动该服务.

准备工具

bluemix环境已有, 接下来就是获取hubot并部署到该服务中.

那这个环境是一个CloudFoundry环境, 就需要它的交互工具.

在macOS中, 还是老一套, 用Homebrew来安装吧:

brew install cloudfoundry/tap/cf-cli

使用cf命令测试了一下,

$ cf
cf version 6.30.0+decf883fc.2017-09-01, Cloud Foundry command line tool
Usage: cf [global options] command [arguments...] [command options]

挺正常的.

然后还需要git和node环境, 如果你还没有的话.

下载安装过Xcode的, 都会由Command Line Tools附带安装了git.

node环境, 也是可以直接brew install的.

获取Hubot

$ git clone git@github.com:hubotio/hubot.git
Cloning into 'hubot'...
remote: Counting objects: 8746, done.
remote: Total 8746 (delta 0), reused 0 (delta 0), pack-reused 8746
Receiving objects: 100% (8746/8746), 1.91 MiB | 117.00 KiB/s, done.
Resolving deltas: 100% (4926/4926), done.

官方解释到:

如果要部署到 Bluemix,设置 manifest.yml 文件会很有用。manifest.yml 包含有关应用程序的基本信息,例如名称、要为每个实例分配的内存量以及路径。

那我们就在这个项目里创建一个manifest.yml文件, name呢就是我们的应用程序名称.

---
applications:
- name: nodejshubot
  command: ./bin/hubot --adapter slack
  instances: 1
  memory: 256M

部署应用程序

到了用Cloud Foundry CLI的地方啦.

cf api <API-endpoint>

API-endpoint呢 则是前面选好的区域, 比如我选的悉尼.

API 端点 区域
https://api.ng.bluemix.net 美国南部
https://api.eu-gb.bluemix.net 英国
https://api.au-syd.bluemix.net 悉尼

实际使用:

$ cf api https://api.au-syd.bluemix.net
Setting api endpoint to https://api.au-syd.bluemix.net...
OK

api endpoint:   https://api.au-syd.bluemix.net
api version:    2.75.0
Not logged in. Use 'cf login' to log in.

那么就是OK的了, 都提示好让我直接login:

$ cf login
API endpoint: https://api.au-syd.bluemix.net

Email> blodely@gmail.com
Password>

Authenticating...
OK

Targeted org blodelyAtSyd

Targeted space dev

API endpoint:   https://api.au-syd.bluemix.net (API version: 2.75.0)
User:           blodely@gmail.com
Org:            blodelyAtSyd
Space:          dev

这就可以从程序目录push应用程序到bluemix了(感觉这个架构很重):

$ cf push
Using manifest file /Users/blodely/Desktop/bluemixhubot/hubot/manifest.yml

Updating app nodejshubot in org blodelyAtSyd / space dev as blodely@gmail.com...
OK

Uploading nodejshubot...
Uploading app files from: /Users/blodely/Desktop/bluemixhubot/hubot
Uploading 85K, 64 files
Done uploading
OK

Stopping app nodejshubot in org blodelyAtSyd / space dev as blodely@gmail.com...
OK

Starting app nodejshubot in org blodelyAtSyd / space dev as blodely@gmail.com...
Downloading liberty-for-java_v3_11-20170710-0312...
# 省略省略大段的远端环境自动下载配置的信息...
Staging complete
Uploading droplet, build artifacts cache...
Uploading build artifacts cache...
Uploading droplet...
Uploaded build artifacts cache (1.2M)
Uploaded droplet (18.8M)
Uploading complete
Destroying container
Successfully destroyed container

0 of 1 instances running, 1 starting
1 of 1 instances running

App started


OK

App nodejshubot was started using this command `npm start`

Showing health and status for app nodejshubot in org blodelyAtSyd / space dev as blodely@gmail.com...
OK

requested state: started
instances: 1/1
usage: 256M x 1 instances
urls: nodejshubot.au-syd.mybluemix.net
last uploaded: Sat Sep 2 04:10:43 UTC 2017
stack: cflinuxfs2
buildpack: SDK for Node.js(TM) (ibm-node.js-6.11.1, buildpack-v3.13-20170725-1347)

     state     since                    cpu    memory          disk          details
#0   running   2017-09-02 12:12:13 PM   0.2%   48.7M of 256M   75.4M of 1G

这就push成功,应用也启动了.

状态也列举出来在最后面, manifest中写到的一样, 指派了256M内存的使用(💰💰💰).

Author

骆昱, September 2, 2017

indie.luo@gmail.com

Kitura framework

Index

Kitura
使用Kitura框架
部署到服务器
Author

Kitura

不用多介绍了,Kitura差不多是一岁了. 来自IBM的Swift框架(on server-side).

自Swift开源以来,大家都在进行各个平台的尝试.

相关较成熟的解决方案有Perfect框架, Kitura则相对来说较为轻量级.

Official site, Github.

现在的优势是该Swift框架在IBM的Bluemix上已有应用服务了,当然Swift容器也有的.

使用Kitura框架

照例使用Swift Package Manager创建:

swift package init --type executable

修改Package.swift,添加Kitura框架的依赖:

import PackageDescription

let package = Package(
    name: "myFirstProject",
    dependencies: [
        .Package(url: "https://github.com/IBM-Swift/Kitura.git", majorVersion: 1, minor: 7)
    ])

Build一下:

swift build

首次Build就会看到一个个依赖包被克隆下来啦.

修改Main.swift文件,添加服务入口:

import Kitura

// Create a new router
let router = Router()

// Handle HTTP GET requests to /
router.get("/") {
    request, response, next in
    response.send("Hello, World!")
    next()
}

// Add an HTTP server and connect it to the router
Kitura.addHTTPServer(onPort: 8080, with: router)

// Start the Kitura runloop (this call never returns)
Kitura.run()

然后就可以跑起程序试试了:

swift build
.build/debug/myFirstProject

部署到服务器 deploy

这里有个比较坑的地方, 追根溯源, 还是来自Apple的. 目前Swift给出的包都是为了Ubuntu环境准备的, 有14.04LTS, 有16.04LTS的. 其他环境得自己编译.

这里呢, 我偷懒点, 使用最爱的docker来部署.

ibmcom/swift-ubuntu是一个Swift Ready的Ubuntu14环境,可以直接拿来用.

作者 Author

骆昱/Luo Yu, indie.luo@gmai.com

Saturday, September 2, 2017

Code Review (ObjC for iOS project)

Review时, 找寻的目标..

假定项目/公司既有代码规范, coder遵从or不遵从规范都是显而易见的.

故不评审代码样式/风格/规范.

找寻的目标, 应该是潜在bug和有性能问题的代码.

一些ObjC审查问题

代码层面的基础问题:

  1. 内存泄漏; 野指针问题.
  2. 线程安全.
  3. 循环依赖.
  4. 集合类型“越界”可能.
  5. 条件判断等的边界考虑.

代码组织设计上的问题:

  1. 类的单一职责原则;
  2. 方法设计, 与其他的耦合关系;
  3. 模块的设计问题;
  4. 算法类方法的瓶颈审查.

Godot engine学习 (2)

Day 3

GUI

Simplified UI controls
  • Label 显示一个文本的node
  • TextureFrame 显示一个简单的材质
  • TextureButton 显示一个简单的、材质化的按钮, 具有常规的state
  • TextureProgress 显示一个材质化的进度条
Complex UI controls

其他的控件:

  • 需要复杂UIs的情况: PC RPCs, MMOs, strategy, sims游戏等
  • 创建自定义的开发工具以加速内容的创建
  • 创建Godot编辑器的插件
  • 这类控件的重定位更常用containers来完成

Size and anchors

为不同尺寸的设备配置UI控件:

godot-anchors-guide

Splash Screen

基本上,这也是个场景(Scene).

使用自定义的布局, 包含一些基础的UI元素, 例如一个“Start开始”按钮.

一张背景图(TextureFrame).

和一个使用custom font自定义字体的Label, 显示copyright信息.

fonts字体

现今操作系统的字体通常都是矢量图形, 适合不同尺寸的展示, 格式通常为TTF(TrueType)或OTF(OpenType).

渲染此类字体成位图资源是一个复杂的过程. 通常由CPU完成. 而游戏引擎使用GPU渲染, 3D相关APIs并不能高效支持此类渲染, 所以需要在之前将字体转换成一个更友好的格式.

基本上呢, 和使用Unity类似, 有指定字体大小情况下的bitmap生成图, 再交由引擎使用, 是一个好方法.

劣势也是一样的: 字体尺寸问题、中文字符集问题等.

Animations

Godot里的动画. Godot的动画系统是极其强大而灵活的.

增加AnimationPlayer节点(node).

选中时, 会出现Animation Editor面板.

off

(颈椎背痛,休息了四天…)

Day 4

Resources资源

Godot里, 引擎绝大多数的行为与功能都是有nodes实现而来, 与Nodes一样非常重要的另一种数据类型是resource.

Nodes关注与behaviors(行为), 例如绘制一个sprite, 绘制3D模型, 物理引擎, GUI控件等等.

Resources则纯粹是数据容器(data containers). 意思是它们不会有任何action也不会处理任何信息.

Resources只包含数据.

如: Texture, Script, Mesh, Animation, Sample, AudioStream, Font, Translation等等.

当Godot从磁盘存储或载入一个scene(.scn/.xml), 一张图片(.png/.jpg), 一段脚本(.gd)或几乎任何东西, 被操作的那个文件就是一个resource.

当资源(resource)从磁盘载入, 它将只会被载入一次. 这意味着,资源会被载入到内存(memory). 尝试再次载入只会返回该资源.

通常而言, Godot里的每个对象(Node, Resource or anything else)都能输出属性.

属性可以是很多类型, 如string, integer, Vector2等, 有些类型可以是个resource.

这意味着nodes和resources都可以包含resources型的属性. 如图:

godot-resource-1

External 外部 vs built-in 内建

资源属性可以以两种方式关联资源: external(on disk)或是built-in.

代码的两种载入方式:

# 1
func _ready():
        var res = load("res://robi.png") # resource is loaded when line is executed
        get_node("sprite").set_texture(res)

# 2
func _ready():
        var res = preload("res://robi.png") # resource is loaded at compile time
        get_node("sprite").set_texture(res)

Godot engine学习 (1)

自打去年在搜索引擎上看到这个开源的游戏引擎来,足足一年时间过去了,却没有真正开始学习/尝试使用这个引擎.

今起,可以利用业余时间开始一步步学习这个引擎,目前看来,作为移动平台小游戏的基础,它应该是满足条件的.

MIT的许可证,也比较适合我们来使用.

将会在博客每天持续更新学习记录.

内容会是以官方文档为基础的学习, 暂不会参考其他网上教程(其实好像也还没有). 时间尽量定在1~2周.

Day 1

第一天,就来了解一些关于Godot引擎的基础好了.

基本上,介绍页解释的是Godot里元素的基础是node, 暂且翻译为“节点”. 一个节点(node)有名称、有可编辑的属性、能接受回调、能被扩展、能被挂到其他节点下作为子节点.

也强调了子节点的组织方式, 称非常重要, 以这种方式组织时,这些nodes形成了tree(树). (感觉…非常…寻常的方式…- -!)

另外,就是Scene(场景). 起特征是:

一个Scene有且只有一个根节点(root node)、Scene可以被保存到磁盘也能被载入、可以被实例化、运行一款游戏意味着运行一个Scene、一个工程里可有多个Scene.

(基本上和其他引擎定义一致)

Godot editor其实就是个Scene editor.

hello, world

  • 选择路径, 创建工程(New Project), 并打开
  • 在Scene面板中, 使用+新增node(Create New Node), Node|CanvasItem|Label
  • Inspector中编辑label的内容, 比如“hello, world”
  • 保存, 并设置该Scene为main scene, 运行即可看到效果

Instancing

基本原理

Godot里,Scene的特征上面已描述过:

godot-scene-1

对于一个正在编辑的Scene来说,其他Scene的示例也可以加到其下:

godot-scene-2

上面图里展示的是Scene B以一个instance加到Scene A中.

官方的demo示例中,在Scene面板中点按“链接形状”按钮选择一个Scene创建instance.

Instancing

Design language

When making games with Godot, 
the recommended approach is to leave aside other design patterns 
such as MVC or Entity-Relationship diagrams 
and start thinking about games in a more natural way. 
Start by imagining the visible elements in a game, 
the ones that can be named not just by a programmer but by anyone.

举例一个简单的射击游戏的思路:
godot-instancing-1

记下想到的元素, 并用箭头表示其ownership.

Day 2

Scripting 脚本

使用的脚本语言称作GDScript. 其设计时有两个目标:

  • First and most importantly, making it simple, familiar and as easy to learn as possible.
  • Making the code readable and error safe. The syntax is mostly borrowed from Python.

任意情况下, 若有性能需求, 可以用C++编写核心块然后交给脚本调用.

Reference

右击node, add script, 意味着extend该node, 故会自动填上Inherit该node.

默认脚本里, 会有一个_ready()函数. 它会在该node(和它所有的children)进入活动scene时被调用. Constructor函数是_init().

# 获取元素
Node.get_node()
# 默认树结构 是 从该脚本控制的node的直接子node开始
# 例如Panel->Label->Button的结构的话, 就是get_node("Label/Button")

# 连接signal信号与响应
Object.connect()
# get_node("Button").connect("pressed",self,"_on_button_pressed")

Processing

有些情况下, 需要脚本处理每一帧. 有两类processing: idle processingfixed processing.

Idle processing由Node.set_process()函数激活. 一旦其激活, Node._process()回调将会被每一帧调用. 示例:

func _ready():
    set_process(true)

func _process(delta):
    # do something...
# delta参数描述了时间间隔, 单位秒, float类型, 间隔自上一次调用_process().

Groups

Nodes可以被添加到groups(想加多少加多少).

  1. 通过界面添加: Node面板下的Groups按钮.
  2. 通过代码添加: add_to_group("enemies")

在此情况下, SceneTree.call_group()可通知一个group下的所有node.

# if the player is discovered sneaking into the secret base
# all enemies can be notified about the alarm sounding
# by using SceneTree.call_group()
func _on_discovered():
    get_tree().call_group(0, "guards", "player_was_discovered")

当然, 也可通过group获取其所有nodes的列表: SceneTree.get_nodes_in_group()

var guards = get_tree().get_nodes_in_group("guards")

Notifications

Godot系统包含一个通知系统.

添加Object._notification()函数即可使用:

func _notification(what):
    if (what == NOTIFICATION_READY):
        print("this is the same as overriding _ready()...")
    elif (what == NOTIFICATION_PROCESS):
        var delta = get_process_time()
        print("this is the same as overriding _process()...")

Overrideable functions

func _enter_tree():
    # When the node enters the _Scene Tree_, it becomes active
    # and  this function is called. Children nodes have not entered
    # the active scene yet. In general, it's better to use _ready()
    # for most cases.
    pass

func _ready():
    # This function is called after _enter_tree, but it ensures
    # that all children nodes have also entered the _Scene Tree_,
    # and became active.
    pass

func _exit_tree():
    # When the node exits the _Scene Tree_, this function is called.
    # Children nodes have all exited the _Scene Tree_ at this point
    # and all became inactive.
    pass

func _process(delta):
    # When set_process() is enabled, this function is called every frame.
    pass

func _fixed_process(delta):
    # When set_fixed_process() is enabled, this is called every physics
    # frame.
    pass

func _paused():
    # Called when game is paused. After this call, the node will not receive
    # any more process callbacks.
    pass

func _unpaused():
    # Called when game is unpaused.
    pass

Creating nodes

代码创建node. 调用.new()方法. 例如:

# 代码创建node. 调用`.new()`方法
var s
func _ready():
    s = Sprite.new() # create a new sprite
    add_child(s)     # add it as a child of this node

# 删除node
func _someaction():
    s.free() # immediately removes the node from the scene and frees it
# 释放一个node, 它的子nodes也会跟着被释放.

删除一个正处于“blocked”状态的node将会导致crashing, 因为它正在发出信号(emitting a signal)/调用方法(calling a function).

最为安全的删除node的办法是Node.queue_free(). 它将在idle状态下删除安全的删除node.

Instancing scenes

代码里instancing一个scene分两步:

  1. 从磁盘载入: var scene = load("res://myscene.scn"), 或是var scene = preload("res://myscene.scn")
  2. 此时scene才是以PackedScene的资源包形式存在着的, 要创建一个实在的node, 需使用PackedScene.instance().
var node = scene.instance()
add_child(node)

二步初始化的优势是, packed的scene可以一直被保持着, 用来快速初始化多个instances. 例如创建多个enemies, bullets等等.

Day 3

Author

Luo Yu

indie.luo@gmail.com

Xcode Objective-C Swift混合项目的小问题

Xcode Objective-C Swift混合项目的小问题

向Objective-C工程中添加Swift

  1. 在Xcode中创建一个*.swift文件

    在Xcode弹出提示时,选择创建一个Objective-C Bridging Header文件

    如果未选择创建,可以手动创建该头文件,并在Build Settings里增加配置Objective-C Bridging Header -> $(SRCROOT)/projectname-Bridging-Header.h

  2. 实现Swift类

    类使用@objc注解

  3. Build Settings的参数检查

    • define module = YES
    • Product Module Name = projectname (不可以有特殊字符)
    • Install Objective-C Compatibility Header = YES
    • Objective-C Bridging Header
  4. 在要用到Swift类的ObjC的实现文件.m内,导入Swift接口的头文件

#import "projectname-Swift.h"

Author

Luo Yu

Friday, July 7, 2017