Swift真的有那么好吗?是否有必要学习

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/fishmai/article/details/82469213

一  从语法角度,他的优势点

最近,除了N多的基于Swift的服务端开发框架,笔者不由深思,到底该这么评价Swift呢?不可否认,在iOS的开发领域,Swift是比OJC拥有着优势,那么在通用语言这个层次上比较时,它又如何呢?Apple 在推出 Swift 时就将其冠以先进,安全和高效的新一代编程语言之名。前两点在 Swift 的语法和语言特性中已经表现得淋漓尽致:像是尾随闭包,枚举关联值,可选值和强制的类型安全等都是 Swift 显而易见的优点。

1. Comparison

  oc java c# c++

swift

python
值对象 没有 没有
指针(缺陷) 没有 没有 没有 没有
内存管理 引用计数 垃圾回收 垃圾回收 智能指针(缺陷) 引用计数 垃圾回收
多返回值 没有 没有 没有 没有
脚本语言特性 没有 没有 没有 没有
移动端支持 ios android 游戏 游戏 ios 较少(缺陷)
             

近来Swift与Rust都挺好的,一个背靠Apple,一个是Mozilla的亲儿子。不可否认这二者都是工程领域的集大成者,不过笔者认为Swift是会比D或者Rust具有更大的可用性与吸引力,当然,他们的瞄准的目标点也不一样。D与Rust适合于那些长期使用C++并且已经适应了要去掌握N多的语法与概念的,但是想要使用些更加清晰明了与安全的语言。这类型的开发者往往从事着类似于游戏引擎、编译器、加解密库、HTML渲染引擎等等类似的工作。

Swift呢,更多意义上是一门面向于应用编程的语言,它很容易上手,在某些方面它应该与Go、Java、Python以及C#相提并论。不过Swift比这些会更容易开始学习,它的helloworld只需要一行代码就好了,并且它是支持函数的(不像Java那样完全的OO)。你不需要学习任何的类与对象的知识就可以开始撰写简易的Swift的代码。基于此,Swift是更合适用作一种教学语言的,它还有像脚本语言一样的交互环境,也就是REPL以及Xcode本身提供的PlayGround。

综上所述,Swift拥有着被广泛使用以及当做第一学习语言的潜质。并且,Swift并不是像PHP那样的语法特性较少的语言,它拥有足够的深度来满足Rust或者D这样的用户的需求。与Go相比的话,Go背靠Google,也是个非常容易上手的语言,并且号称自带并发。Swift在语法层次上会更加高级,并且Swift并没有使用GC机制,因此可以与C更好地相兼容。也就是说,你可以用Swift编写任何的库来供任何语言使用,只要这些语言可以使用C的库。这些特质保证了Swift拥有着比Java、C#、Python、Ruby以及Go更广阔的适用范围。后面这几个家伙,因为有GC的存在,不适合为其他语言提供基本库。我也很喜欢Go,但是毫无疑问,Swift会是个更加严谨与安全的语言。类型检测系统会帮助处理很多的错误,Go目前是很合适于Web开发但是不适合科学计算与游戏引擎这些你必须要大量自定义操作符来处理向量啊、矩阵运算这样的。

因此,Swift可以被定义为一个安全并且用户友好的语言,并且可以像脚本语言那样方便实验与使用,其实swift想做移动端的python

 

1.1  TypeAlias 类型别名:类似 C++ 的 typedef 和 Golang 的 type

Swift 版本:

typealias status = Int8

C++ 版本:

typedef status int

Golang 版本:

type status int

1.2   Optional 可选类型: 类似 Haskell 中的 Maybe 类型

Optional 可选类型,表示变量有值时返回存储的值,没有值返回 nil 。它可以在变量未声明的情况下确保后面调用的安全性。

用法为 Optional<Type> 或者 Type? 。 例如:

var i:Optional<Int>
var str:String?

Haskell 中也有类似的东西: Maybe 类型,它是这样定义的:

data Maybe a =  Nothing | Just a deriving (Eq, Ord, Read, Show)

用法也类似:

i :: Maybe Int

1.3  枚举支持元组:类似 Rust

Swift 不仅支持基本的枚举类型,还可以是元组:

enum Product
{
    case Car(String, Int)
    case Phone(String, String)
}

Rust 中的枚举也有类似用法:

enum Shape {
	Circle { center: Point, radius: f64 },
	Retangle { top_left: Point, bottom_right: Point }
}

1.4  用于 switch case 的 fallthrough: 类似 Golang 中的 fallthrough

当初学 C 语言的时候就觉得 switch case 设计成默认贯穿很坑爹:实际开发中多数是不需要贯穿的,这就导致代码中一大堆 break 

现在 Swift 终于从 Golang 吸收过来这个特性,一般不需要处理 case ,少数需要贯穿的情况才加上 fallthrough ,二者用法也类似,代码就不贴了。

1.5  switch case 支持 where 语句:类似 Haskell 中 Pattern Guard 的 where 语句

Swift 中的 switch case 语句支持 where 条件判断:

let point = (-1, 2, 0)
switch point
{
case (let x, _, _):
    println("x: \(x)")
    fallthrough
case (x, y, z) where x * y * z == 0:
    println("x: \(x), y: \(y), z: \(z)")
default:
    println()
}

Haskell 中虽然没有 switch case ,但有类似的 Pattern Guard,而且也支持 where 语句:

funcBMI :: (RealFloat a) => a -> a -> String
funcBMI weight height
	| bmi <= bmi_thin = "Thin."
	| bmi >= bmi_fat = "Fat."
	| otherwise = "Normal."
	where bmi = weight/height^2
	      (bmi_thin,bmi_fat) = (18.5,30.0)

1.6  支持函数类型:类似 C++11 中的 std::function 类型

Swift 中可定义函数类型,并作为变量类型、参数类型、返回值类型:

//定义函数类型:
typealias FuncType = (Double, Double) -> Double

//函数类型的变量:
var funcDDD : FuncType

//函数类型的参数:
func printFuncResult(fun : FuncType, x : Double, y : Double) {
    println(fun(x, y))
}

//函数类型的返回值:
func getFunc() -> FuncType {
    return funcDDD
}

甚至还可以将函数类型结合闭包使用:

typealias FuncType = (Double, Double) -> Double

func printFuncResult(x : Double, y : Double, fun : FuncType) {
    println("\(fun(x, y))")
}

//定义闭包:
let funPlus = { (x : Double, y : Double) -> Double in return x + y }

//将闭包作为函数类型的参数传入函数:
printFuncResult(0.1234, 5.6789, funPlus)

C++11 中的 std::function 也可定义一个函数指针,也有上述类似用法:

//定义函数类型指针变量:
std::function<double(double, double)> multiply;

//将变量声明为该类型的 Lambda:
multiply = [] (double x, double y) -> double {
	return x * y;
};

//调用 Lambda:
std::cout << multiply(1.234, 5.6789) << std::endl;

1.7  支持运算符重载和定义新运算符:类似 C++ 中的运算符重载

Swift 中可用 prefix 重载前缀运算符:

prefix func - (p : Point) -> Point {
    return Point(x : -p.x, y : -p.y)
}

也可用 postfix 重载后缀运算符:

postfix func ++ (p : Point) -> Point {
    var px = p.x, py = p.y
    return Point(x : ++px, y : ++py)
}

甚至可以使用 operator 定义新的运算符:

prefix operator %& {}
prefix func %& (p : Point) -> Point {
    return Point(x : p.x % 8, y : p.y % 8)
}

C++ 中的运算符重载那是出了名的强大,甚至可以重载 IO 运算符,这里仅给出基本用法:

class Point {
public:
	Point(int i1, int i2, int i3): x(i1), y(i2), z(i3){}
	Point operator-();
	Point operator++();
	bool operator==(const Point &p);
	Point operator+(const Point &p);
	int operator[](size_t n);
private:
	int x, y, z;
	int values[3] = {x, y, z};
};

Point Point::operator-() {
	return Point(-x, -y, -z);
}

Point Point::operator++() {
	return Point(++x, ++y, ++z);
}

bool Point::operator==(const Point &p) {
	return x == p.x && y == p.y && z == p.z;
}

Point Point::operator+(const Point &p) {
	return Point(x + p.x, y + p.y, z + p.z);
}

int Point::operator[](size_t n) {
	return values[n];
}

int main (int argc, char **argv) {
	Point p1(1, 3, 5);
	Point p2(2, 4, 6);
	std::cout << (p1 == p2 ? "p1 == p2" : "p1 != p2") << std::endl;
	Point p3 = -(p1 + p2);
	//p3.operator++();
	++p3;
	std::cout << "p3: " << p3[0] << ", " << p3[1] << ", " << p3[2] << std::endl;
	return 0;
}

1.8  函数参数可设置是否可变:类似 C++

C++ 中在函数前添加 const 即可声明为不可变,代码就不贴了。

Swift 中函数参数默认就是不可变的,如果要在函数内部修改参数的值,需要在参数前声明 var 关键字:

func printContactVar(var name: String, email : String) {
    name = name.uppercaseString
    printContact(name: name, email: email)
}

上面的 var 参数虽然能修改值,但作用域仅限于函数内部,如果想在函数调用结束后,在外部依然生效,还可将关键字改为 inout 

1.9  函数参数可设置默认值:类似 C++

Swift 中定义函数时,可设置参数默认值:

func printContact(name : String = "Rinc", email : String = "i@RincLiu.com") {
    println("\(name): \(email)")
}

C++ 也有类似特性:

void printContact(std::string name = "Rinc", std::string email = "i@RincLiu.com");
void printContact(std::string name, std::string email) {
	std::cout << name << ": " << email << std::endl;
}

1.91  可通过 count 和 repeatedValue 初始化数组:类似 C++

Swift 版本:

var a = [Character](count: 10, repeatedValue: "X")

C++ 版本:

std::vector<char> v(10, 'x');

1.92  元组相关特性:类似 Golang

Swift 中可以通过 不取元组中的某个元素:

let people = ("Rinc", age : 25)
var (name, _) = people

还有用于集合数据的遍历:

var dic : Dictionary<String, Int> = ["Rinc" : 25, "Emma": 24]
for (k, _) in dic {
    println("\(k)")
}

Golang 由于经常取回的数据集含有索引(连数组都有),所以这种用法更常见:

mp := map[string]float32{"C": 5, "GO": 4.5, "JAVA": 6}
for key, _ := range mp {
	fmt.Println(key)
}

虽然 C++11、Rust 等语言也支持元组,但感觉大多简单用于函数多值返回,像上面这种用法貌似没见过;

1.93  其他特性

  1. 函数语句结束不加分号:Golang、Rust 等很多语言都支持;

  2.  var 动态类型声明:JavaScript、Golang 等语言都支持;

  3. 函数多值返回:因为有了元组的支持,所以实现这个并不难,C++、Golang、Rust 等语言都支持;

  4. 闭包:这东西现在随着函数式编程的热门,几乎所有语言都提供了支持,也不多说了。

总结

总体来看 Swift 吸收的 C++ 特性最多,其次是 Golang 和 Hashekll,还有少量 Rust 特性。

 

2. Performance

Swift 具有一门高效语言所需要具备的绝大部分特点。与 Ruby 或者 Python 这样的解释型语言不需要再做什么对比了,相较于其前辈的 Objective-C,Swift 在编译期间就完成了方法的绑定,因此方法调用上不再是类似于 Smalltalk 的消息发送,而是直接获取方法地址并进行调用。虽然 Objective-C 对运行时查找方法的过程进行了缓存和大量的优化,但是不可否认 Swift 的调用方式会更加迅速和高效。

另外,与 Objective-C 不同,Swift 是一门强类型的语言,这意味 Swift 的运行时和代码编译期间的类型是一致的,这样编译器可以得到足够的信息来在生成中间码和机器码时进行优化。虽然都使用 LLVM 工具链进行编译,但是 Swift 的编译过程相比于 Objective-C 要多一个环节 -- 生成 Swift 中间代码 (Swift Intermediate Language,SIL)。SIL 中包含有很多根据类型假定的转换,这为之后进一步在更低层级优化提供了良好的基础,分析 SIL 也是我们探索 Swift 性能的有效方法。

最后,Swift 具有良好的内存使用的策略和结构。Swift 标准库中绝大部分类型都是 struct,对值类型的 使用范围之广,在近期的编程语言中可谓首屈一指。原本值类型不可变性的特点,往往导致对于值的使用和修改意味着创建新的对象,但是 Swift 巧妙地规避了不必要的值类型复制,而仅只在必要时进行内存分配。这使得 Swift 在享受不可变性带来的便利以及避免不必要的共享状态的同时,还能够保持性能上的优秀。

编译器优化

Swift 编译器十分智能,它能在编译期间帮助我们移除不需要的代码,或者将某些方法进行内联 (inline) 处理。编译器优化的强度可以在编译时通过参数进行控制,Xcode 工程默认情况下有 Debug 和 Release 两种编译配置,在 Debug 模式下,LLVM Code Generation 和 Swift Code Generation 都不开启优化,这能保证编译速度。而在 Release 模式下,LLVM 默认使用 "Fastest, Smallest [-Os]",Swift Compiler 默认使用 "Fast [-O]",作为优化级别。我们另外还有几个额外的优化级别可以选择,优化级别越高,编译器对于源码的改动幅度和开启的优化力度也就越大,同时编译期间消 耗的时间也就越多。虽然绝大部分情况下没有问题,但是仍然需要当心的是,一些优化等级采用的是激进的优化策略,而禁用了一些检查。这可能在源码很复杂的情 况下导致潜在的错误。如果你使用了很高的优化级别,请再三测试 Release 和 Debug 条件下程序运行的逻辑,以防止编译器优化所带来的问题。

值得一提的是,Swift 编译器有一个很有用的优化等级:"Fast, Whole Module Optimization",也即 -O -whole-module-optimization。在这个优化等级下,Swift 编译器将会同时考虑整个 module 中所有源码的情况,并将那些没有被继承和重载的类型和方法标记为 final,这将尽可能地避免动态派发的调用,或者甚至将方法进行内联处理以加速运行。开启这个额外的优化将会大幅增加编译时间,所以应该只在应用要发布的时候打开这个选项。

虽然现在编译器在进行优化的时候已经足够智能了,但是在面对编写得非常复杂的情况时,很多本应实施的优化可能失效。因此保持代码的整洁、干净和简单,可以让编译器优化良好工作,以得到高效的机器码。

3. 注释与换行

注释

请将你的代码中的非执行文本注释成提示或者笔记以方便你将来阅读。Swift 的编译器将会在编译代码时自动忽略掉注释部分。

Swift 中的注释与C 语言的注释非常相似。单行注释以双正斜杠作(//)为起始标记:

// 这是一个注释

你也可以进行多行注释,其起始标记为单个正斜杠后跟随一个星号(/),终止标记为一个星号后跟随单个正斜杠(/):

/* 这是一个, 多行注释 */

与C 语言多行注释不同,Swift 的多行注释可以嵌套在其它的多行注释之中。你可以先生成一个多行注释块,然后在这个注释块之中再嵌套成第二个多行注释。终止注释时先插入第二个注释块的终止标记,然后再插入第一个注释块的终止标记:

/* 这是第一个多行注释的开头 /* 这是第二个被嵌套的多行注释 */ 这是第一个多行注释的结尾 */

通过运用嵌套多行注释,你可以快速方便的注释掉一大段代码,即使这段代码之中已经含有了多行注释块。

4. Objective-C混合编程

参考资料

  • 从Objective-C到Swift

  • swift与objective-c混编

  • Swift and Objective-C in the Same Project

Swift 与 Objective-C混合调用示意图

5. Swift类引用Objective-C文件

因为Swift没有内嵌的头文件机制,因此Swift调用Objective-C需要一个名为“<工程名>-Bridging-Header.h”的桥接头文件。桥接头文件的作用是为Swift调用Objective-C对象搭建一个桥,它的命名必须是“<工程名>- Bridging-Header.h”,我们需要在桥接头文件中引入Objective-C头文件,所有的Swift类会自动引用这个头文件。

桥接文件

桥接文件设置

  • OJC类如下:

//
//  ObjcFunc.h
//

# import <Foundation/Foundation.h>

@interface ObjcFunc : NSObject

-(NSString*)sayHello:(NSString*)greeting withName: (NSString*)name;

@end

//
//  ObjcFunc.m
//

# import "ObjcFunc.h"

# import "CombinedProgram-Swift.h"

@implementation ObjcFunc

- (NSString*)sayHello:(NSString*)greeting withName: (NSString*)name{

    NSString *string = [NSString stringWithFormat:@"Hi,%@ %@.",name,greeting];

    return string;

  }

@end
  • Swift类中调用

import Foundation
@objc class SwiftFunc: NSObject {
  func sayHello() -> Void {
  var obj : ObjcFunc = ObjcFunc()
  println(obj.sayHello("Hello", withName: "Swift"));
  return
  }
}

6. Objective-C类引用Swift文件

(1)在Building Settings -> Packaging -> Defining中选定Module Name;

(2)在OJC的头文件中引入:#import "{ModuleName}-swift.h"

SwiftFunc* obj = [[SwiftFunc alloc] init];
[obj sayHello];

有时候会发现Xcode无法自动生成*-Swift.h文件,可以参考StackOverflow上的这篇文章。该文章总结下来,我们需要进行以下两大步检测:

(1)检测你的Xcode的配置

Product Module Name : myproject

Defines Module : YES

Embedded Content Contains Swift : YES

Install Objective-C Compatibility Header : YES

sObjective-C Bridging Header : $(SRCROOT)/Sources/SwiftBridging.h

(2)检查你的Swift类是否正规

要保证你的Swfit类中已经使用@objc关键字声明了一个继承自NSObject的类。Xcode不会为存在任何编译错误的类进行编译操作。

(3)忽略Xcode的报错,先编译一下

7. 语法要点

! VS ?

在Swift中经常看到!与?两个操作符,譬如在类型转换、可选类型构造中都用到,用Apple官方的话说:

It may be easiest to remember the pattern for these operators in Swift as: ! implies “this might trap,” while ?indicates “this might be nil.”

就是!操作符表示我不管你编译器,我肯定要这么做,那么有可能导致运行时崩溃。而?操作符表示这个可能是nil,你帮我查查有没有进行完备的空检查。

二. 以上是从语言技术角度,而如果从工程角度

1, 生态环境比语言成熟慢:Swift非常适合思考,但是目前还不是工程化ready的语言,主要是上下游生态环境还不成熟,需要时间;

2,路径依赖效应:人的路径依赖无解;未来使用swift并获得巨大市场成功的公司,不是今天这些市场上的巨无霸;就像诺基亚比苹果更早理解触屏的价值,但仍然只能眼睁睁看着苹果后来居上;普朗克曾经说过一句关于科学真理的真理,叙述为“一个新的科学真理取得胜利并不是通过让它的反对者们信服并看到真理的光明,而是通过这些反对者们最终死去,熟悉它的新一代成长起来。”对于技术来说也是如此。
 

Reference

Tutorial & Docs

  • 中文版 Apple 官方 Swift 教程《The Swift Programming Language》

  • Swift学习笔记

Forum & Lessons

Blog & News

Book & Resources

展开阅读全文

没有更多推荐了,返回首页