Swift 2019 回顾
- Swift 5 & 5.1 发布
- Swift ABI 稳定
- SwiftUI 发布
- Xcode 集成 Swift Package Manager
- SwiftNIO 2 发布
- SwiftLog 发布
- SwiftMetrics 发布
- StatsdClient 发布
- swift-numerics 发布
- Vapor 4 Beta 发布
Swift 2020 展望
- Swift 5.2
- Vapor 4 正式版
专注于Swift技术分享 – 微信公众号:SwiftMic
本篇为译文,原文可见:链接
如果超过 6 个月没看过自己写的代码的话,你可能会认为这些代码是其他人写的。
-Eagleson’s Law
当我们使用 Apple
的 class
时,如果不知道如何使用它们的话,我们有很多方式可以去查找资料。比如你可以通过 Apple Developer Documentation 在线文档或者通过 Xcode
来查找。
你可以通过 ⌥ – Option
+ click 方式查看任何 class
:
Quick help
也显示在 Quick Help
检查面板(inspector panel)上。
当你开始打字时,Xcode 将会有相应的提示信息(包含 class
中的 function
/property
/enum
)
接下来将介绍如何给自己的代码加上这些提示。
给代码写文档就像写注释一样,但是有一点点语法上的差别。你需要 ///
来标注单行的文档。
/// This is your User documentation.
struct User {
let firstName: String
let lastName: String
}
对于多行文档,你需要 /** ... */
符号。
/**
This is your User documentation.
A very long one.
*/
struct User {
let firstName: String
let lastName: String
}
本篇为译文,原文可见:链接
本周的 issue ,我们主要讨论 updating the view state、animations in SwiftUI 和 debugging Combine 。
如果你正在查找关于 body 是计算型时,去尝试修改 view 状态的资料的话,这篇文章将会告诉你可以做哪些操作以及不应该做哪些操作。
Fernando Moya de Rivas 使用 SwiftUI
创建了几个有趣的动画。
学习不同方式来调试由 Swift Combine framework 编写的 functional reactive code 。通过 print()
和 handleEvents()
来查看 console
;通过 breakpointOnError()
和 breakpoint()
来生成 Xcode
断点;以及通过绘制图表的方式。
Josh Adams 通过 SwiftUI
修改了他的一个 app,并分享了一些学习心得。
本篇文章中,Jim Dovey 解释了如何使用 SwiftUI
与 CoreData
进行绑定操作。
The folks at Just Eat have experimentation and feature flagging at their heart and they’ve developed a component, named JustTweak, to make things easier on iOS.
John Sundell 找到了一些不同的方式来添加插件支持,这样可以使得系统变得更加灵活。
介绍了如何编写 Swift 代码的文档注释。
介绍了 Alexander Grebenyuk 如何从手动测试他的框架到通过单元测试来自动化测试每一次的变更。
继续阅读“Swift Weekly 中文 – Issue #176”
Swift 5.1
新增了 Property Wrappers
特性。该特性可以通过使用 @
符号以注解的形式来实现某些功能,并达到简化代码的效果。
以 iOS
中的 UserDefaults
为例,一般用法如下。
UserDefaults.standard.set(value, forKey: key)
UserDefaults.standard.object(forKey: key)
Property Wrappers
需满足两个基本要求:
@propertyWrapper
关键字来修饰。wrappedValue
的属性。接下来以 Property Wrappers
方式改造 UserDefaults
,代码如下:
@propertyWrapper
struct MyUserDefault<T> {
let key: String
let defaultValue: T
var wrappedValue: T {
get {
return (UserDefaults.standard.object(forKey: key) as? T) ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
}
@MyUserDefault(key: "username", defaultValue: "")
static var username: String
加了 MyUserDefault
自定义注解后,对 username
的赋值操作相当于是执行了 UserDefaults.standard.set
方法,对 username
的读取操作相当于执行了 UserDefaults.standard.object
方法。
Property Wrappers
也有一些使用上的限制。比如:
protocol
中进行声明。wrapper
包装的实例属性(An instance property with a wrapper
)无法在 extension
中进行声明。enum
中进行声明。class
中通过 wrapper
包装的属性无法被另外一个属性通过 override
覆盖掉。wrapper
包装的实例属性(An instance property with a wrapper
)不能用 lazy
、@NSCopying
、@NSManaged
、weak
或 unowned
来修饰。继续阅读“Swift 之 Property Wrappers 特性”
最近 Swift Workgroup
发布了一则公告:
@IanPartridge and @Chris_Bailey let the group know that following a review by IBM of its open source priorities, it has been decided that they will not be continuing to work on Swift in 2020. As a result, they are both standing down from the workgroup.
@IanPartridge will work to hand over responsibilities for the Swift Docker images and suggested a potential new owner from the community.
The workgroup thanked @IanPartridge, @Chris_Bailey and the rest of the IBM team for their valuable work over the years in getting Swift on server off the ground, and providing the community with reliable solutions during those early days.
大致意思是说,根据 IBM
公司的开源优先级评估后,决定 IanPartridge
和 Chris_Bailey
将于 2020 年不再继续从事 Swift 相关的工作了,同时他们也将退出 workgroup
。
其中,IanPartridge
将不再负责 Swift Docker images
相关的职责,社区将会有一名新的继承者来接管。
可以看出 IBM
在未来将会一定程度上减少 Swift
服务端的投入,算是 Swift 社区的一个损失。
目前主要由 Apple 官方维护的 Swift 库如下:
Event-driven network application framework for high performance protocol servers & clients, non-blocking.
A Logging API for Swift
A Metrics API for Swift
Metrics backend for swift-metrics that uses the statsd protocol.
目前 Swift 社区主要有如下 Web 框架:
另外,Swift 社区还有很多优秀的开源库,这里就不一一列出来了。
从目前来看,Vapor
框架的受关注度最高,且社区相对比较活跃。而且 Vapor
框架的作者 Logan Wright (@loganwright, Vapor) 和 Tanner Nelson (@tanner0101, Vapor) 均为 SSWG(Swift Server Work Group
) 成员,与 Swift 官方组织联系也最为紧密,Vapor
几乎也都是第一时间采用 Swift
官方组件(比如:SwiftNIO、SwiftLog、SwiftMetrics 等)。
本篇为译文,原文可见:链接
本周的 issue ,我们主要讨论 Thread Sanitizer、KeyValuePairs 和 SPMUtility。
学习在 Swift 中如何使用 Thread Sanitizer to catch Data Races。修复怪异的 crash,并且可以看到 Data Reace 示例。
介绍了一些集合类型之间的区别,比如:Array
、 Set
和 Dictionary
。
本篇文章中,Derik Ramirez 将介绍如何使用 Swift Package Manager
中的 SPMUtility
模块,通过 ArgumentParser
来解析你的 swift command-line tool 的参数。
Xcode 11
介绍了一种新方式来测试可选类型。Sarun 向你演示了新的 XCTUnwrap
方法。
本周,John Sundell 看了看一些核心语言特性,这些特性可以使我们设计真正轻量级的 Swift API,并且可以帮助我们更好的开发一个新功能或系统。
如果你正在查找关于 body 是计算型时,去尝试修改 view
状态的资料的话,这篇文章将会告诉你可以做哪些操作以及不应该做哪些操作。
在本周文章中,Majid 将向你展示 UIKit
和 SwiftUI
开发之间的主要不同点,并指出使用 SwiftUI
的时候必须要改变的习惯。
Alexey Naumov 解释了如何处理 SwiftUI
项目中的导航来实现 deep links
。
Ctrl Group builds digital products for patients, healthcare practitioners and researchers to gather evidence and provide better care. We’re looking for an iOS engineer to join our team in London, or work remotely as part of our distributed team.
介绍了今年十月在 Bologna, Italy
举办的 #Pragma Conference
中的所有演讲 。
两个免费视频探索了 Apple
新的 Combine
框架,包括它的核心组件,以及如何集成进你的代码。
继续阅读“Swift Weekly 中文 – Issue #175”
Example output:
# TYPE http_requests_total counter
http_requests_total 0
http_requests_total{status="200", path="GET /hello/:name", method="GET"} 7
http_requests_total{status="200", path="GET /metrics", method="GET"} 3
# TYPE http_request_duration_seconds summary
http_request_duration_seconds{quantile="0.01"} 0.000115894
http_request_duration_seconds{quantile="0.05"} 0.000115894
...
Updates to RoutingKit beta 3 (#2126)
ClientResponse
, HTTPStatus
, and HTTPHeaders
are now Codable
(#2124)
Environment variables loaded from .env
files can now be accessed immediately after Application
has initialized (#2125)
.env
(in same folder as Package.swift
)
FOO=BAR
main.swift
let app = Application(...)
defer { app.shutdown() }
let foo = Environment.get("FOO")
print(foo) // BAR
本文主要介绍原生 iOS
应用如何集成 Flutter
模块。
Flutter
支持 macOS
、Windows
和 Linux
。
安装步骤可见官网:https://flutter.dev/docs/get-started/install
(注:本教程使用的是 macOS
系统)
通过 Xcode
创建一个 iOS
原生项目,命名为 Example-Flutter
。
Example-Flutter
当前目录下。Flutter
模块。flutter create --template module my_flutter
将会生成一个 my_flutter
新目录,结构如下:
my_flutter/
├─.ios/
│ ├─Runner.xcworkspace
│ └─Flutter/podhelper.rb
├─lib/
│ └─main.dart
├─test/
└─pubspec.yaml
CocoaPods
集成 Flutter
模块。在 Example-Flutter
项目目录下创建 Podfile
文件,内容编辑如下:
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '9.0'
use_frameworks!
flutter_application_path = './my_flutter'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
target 'Example-Flutter' do
install_all_flutter_pods(flutter_application_path)
end
然后,通过 pod install
命令集成 Flutter
模块到 iOS
工程项目中。
输出类似如下日志:
$ pod install
Analyzing dependencies
Downloading dependencies
Installing Flutter (1.0.0)
Installing FlutterPluginRegistrant (0.0.1)
Installing my_flutter (0.0.1)
Generating Pods project
Integrating client project
[!] Please close any current Xcode sessions and use `Example-Flutter.xcworkspace` for this project from now on.
Pod installation complete! There are 3 dependencies from the Podfile and 3 total pods installed.
此时,整个项目的目录结构类似是这样的。
some/path/Example-Flutter
├── Example-Flutter/
├── Example-Flutter.xcodeproj
├── Example-Flutter.xcworkspace
├── my_flutter/
│ └── .ios/
│ └── Flutter/
│ └── podhelper.rb
├── Podfile
├── Podfile.lock
└── Pods/
修改 AppDelegate.swift
文件。
import FlutterPluginRegistrant
lazy var flutterEngine = FlutterEngine(name: "my flutter engine")
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
flutterEngine.run()
GeneratedPluginRegistrant.register(with: self.flutterEngine);
return true
}
然后在 iOS
原生页面上添加相应代码。
导入 Flutter
package。
import Flutter
增加一个触发按钮,代码如下:
let button = UIButton(type:UIButton.ButtonType.custom)
button.addTarget(self, action: #selector(showFlutter), for: .touchUpInside)
button.setTitle("Show Flutter!", for: UIControl.State.normal)
button.frame = CGRect(x: 80.0, y: 210.0, width: 160.0, height: 40.0)
button.backgroundColor = UIColor.blue
self.view.addSubview(button)
响应 Button
点击事件,代码如下:
@objc func showFlutter() {
let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine
let flutterViewController =
FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
present(flutterViewController, animated: true, completion: nil)
}
GitHub: iOS-Examples/Example-Flutter
UIView
内部包含一个 hitTest
方法,用于 UI 事件的传递。
当一个触摸事件发生时,事件传递顺序如下:
UIApplication -> UIWindow -> 寻找合适的View
假设颜色对应的 View 关系如下:
黄色:View 1
绿色:View 1_1
灰色:View 1_1_1
红色:View 1_2
View 1
定义如下:
class View1: UIView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
print("View 1 hitTest")
return super.hitTest(point, with: event)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("View 1 touchesBegan")
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
print("View 1 touchesMoved")
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
print("View 1 touchesCancelled")
}
}
同理,View11
、View111
、View12
定义类似于 View1
。
各 View
的层级关系如下:
View1 -> View11 -> View111
View1 -> View12
具体布局代码如下:
// view 1
let view1 = View1(frame: CGRect(x: 40, y: 100, width: 300, height: 400))
view1.backgroundColor = UIColor.yellow
self.view.addSubview(view1)
// view 1_1
let view11 = View11(frame: CGRect(x: 20, y: 30, width: 100, height: 120))
view11.backgroundColor = UIColor.green
view1.addSubview(view11)
// view 1_1_1
let view111 = View111(frame: CGRect(x: 10, y: 10, width: 60, height: 40))
view111.backgroundColor = UIColor.gray
view11.addSubview(view111)
// view 1_2
let view12 = View12(frame: CGRect(x: 20, y: 200, width: 100, height: 120))
view12.backgroundColor = UIColor.red
view1.addSubview(view12)
当一个触摸事件发生在灰色(View 1_1_1
)区域时,hitTest
被调用的顺序如下。
View 1 hitTest
View 1_2 hitTest
View 1_1 hitTest
View 1_1_1 hitTest
表明触摸事件发生时,先从底层 View
开始逐级向上层进行遍历,直到找到灰色视图 View111
。
寻找到合适的 View
后,将会响应对应 View
的触摸事件。
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("View 1_1_1 touchesBegan")
}
此处合适的 View
就是 View111
,将输出日志:
View 1_1_1 touchesBegan
响应者链的传递顺序与 UI 事件传递顺序正好相反,如果当前 View
(即:View 1_1_1
)无法响应触摸事件(比如:isUserInteractionEnabled = false
),将向父一级传递事件。此时,View 1_1
将收到触摸事件,如果 View 1_1
可交互的话,将响应对应的 touchesBegan
事件。
View 1_1 touchesBegan
否则继续往父一级遍历,直到找到合适的响应对象。
GitHub: iOS-Examples/Example-View响应链
本文简要介绍下 Swift 的基本语法。
import Foundation
var str1 = "Hello, playground"
let str2 = "test"
var myArray = [
"t1",
"t2",
"t3"
]
let ttt: Set = [0, 1, 2, 3, 6, 9, 90, 2]
var myDict = [
"k1": "v1",
"k2": "v2",
"k3": "v3"
]
func test(_ value: Int) -> Bool {
if value > 10 {
return true
}
// 编译错误,Swift强制if语句加{}
// if value > 10
// return true
return false
}
func test1(_ value: Int) -> (errorCode: Int, errorDes: String) {
if value > 10 {
return (200, "Success")
}
return (404, "Not Found")
}
let r = test(100)
let r1 = test1(90)
print("result1 = (\(r1.0), \(r1.1))")
print("result1 = (\(r1.errorCode), \(r1.errorDes))")
// 闭区间运算符(a...b)定义一个包含从a到b(包括a和b)的所有值的区间,b必须大于a。
for index in 1...5 {
print("index = \(index)")
}
// 半开区间运算符
for index in 1..<5 {
print("index = \(index)")
}
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
func backwards(s1: String, s2: String) -> Bool {
return s1 > s2
}
func closureTest(_ param1: Int, param2: (_ s1: String, _ s2: String) -> Bool) {
print("param1 = \(param1), param2 = \(param2("d", "b"))")
}
closureTest(1, param2: backwards)
closureTest(2, param2: {
(s1: String, s2: String) -> Bool in
return s1 < s2
})
closureTest(3, param2: {
(s1, s2) in // 类型可自动推倒出来
return s1 < s2
})
closureTest(4, param2: {(s1, s2) in return s1 < s2})
closureTest(5, param2: {$0 > $1})
// 运算符函数
closureTest(6, param2: >)
func trailingClosuresTest(_ param: () -> ()) {
print("trailingClosuresTest 1")
param()
print("trailingClosuresTest 2")
}
// 以下是不使用尾随闭包进行函数调用
trailingClosuresTest({
() -> () in
print("trailingClosuresTest 3")
})
trailingClosuresTest({
print("trailingClosuresTest 4")
})
// 以下是使用尾随闭包进行函数调用
trailingClosuresTest() {
print("trailingClosuresTest 5")
}
// 如果函数只需要闭包表达式一个参数,当使用尾随闭包时,甚至可以把()省略掉。
trailingClosuresTest {
print("trailingClosuresTest 6")
}
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
var runningTotal = 1
func incrementor() -> Int {
runningTotal += amount
return runningTotal
}
return incrementor
}
let capture1 = makeIncrementor(forIncrement: 9)
print(capture1())
print(capture1())
print 输出如下:
10
19
enum Barcode {
case UPCA(Int, Int, Int)
case QRCode(String)
}
func enumTest(value: Barcode) {
switch(value) {
case Barcode.UPCA(let numberSystem, let identifier, let check):
print("UPCA numberSystem = \(numberSystem), identifier = \(identifier), check = \(check)")
case Barcode.QRCode(let productCode):
print("QRCode productCode = \(productCode)")
}
}
enumTest(Barcode.QRCode("qs"))
enumTest(Barcode.UPCA(1, 2, 3))
enum Planet: Int {
case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}
enum StrEnum: String {
case T1 = "a"
case T2 = "b"
}
struct Point {
var x = 0.0
var y = 0.0
}
struct Size {
var width = 0.0
var height = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2.0)
let centerY = origin.y + (size.height / 2.0)
return Point(x: centerX, y: centerY)
}
set {
origin.x = newValue.x - (size.width / 2.0)
origin.y = newValue.y - (size.height / 2.0)
}
}
}
var square = Rect(origin: Point(x: 0.0, y: 0.0), size: Size(width: 10.0, height: 10.0))
print("square.center = \(square.center)")
square.center = Point(x: 15.0, y: 15.0)
print("square origin = \(square.origin)")
print 输出如下:
square.center = Point(x: 5.0, y: 5.0)
square origin = Point(x: 10.0, y: 10.0)
class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("1 totalSteps = \(totalSteps)")
}
didSet {
if(totalSteps > oldValue) {
print("2 totalSteps = \(totalSteps)")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 100
print 输出如下:
1 totalSteps = 0
2 totalSteps = 100
struct PointTest {
var x = 0.0
var y = 0.0
mutating func moveBy(deltaX: Double, deltaY: Double) {
x += deltaX
y += deltaY
}
}
使用 mutating 关键字是为了能在该方法中修改 struct 的变量。
class Player {
init() {
print("Player init")
}
deinit {
print("Player deinit")
}
func test() {
}
}
var player: Player? = Player() // 调用构造方法
if(nil != player) {
player!.test()
}
// 等价于上面这种情况
player?.test()
player = nil // 调用析构方法
class A {
}
class B: A {
}
protocol IA {
}
protocol IB {
}
protocol IC {
}
class C: A, IA, IB, IC {
}
extension Double {
var km: Double { return self * 1_000.0 }
var m : Double { return self }
var cm: Double { return self / 100.0 }
var mm: Double { return self / 1_000.0 }
var ft: Double { return self / 3.28084 }
}
let t = 45.7.km
print(t)
print 输出如下:
45700.0