44

10条Swift小提示

 5 years ago
source link: http://www.cocoachina.com/ios/20180621/23891.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Swift有大量有趣的语法、特性、特点,只要掌握了用法就可以利用好它们。在这篇文章中我会带你浏览我选择出的10条小提示,并附有已验证的代码供大家试用。

1.类与协议的existential

Existential类型允许我们说出想要一个类型具有哪种功能,而不用请求某些特定的东西。比如我们可以写一个接收类或子类的函数:

func process(user: User) { }

之后我们写一个函数,让它能接收符合某个协议的任意类型对象:

func identify(thing: Identifiable) { }

Swift允许我们让existential同时代表类与协议

下例中,有一个协议和一个符合该协议的类

protocol CanCook { }
class CelebrityChef: CanCook { }

之后再有一个类,并附有一个子类

class Appliance { }
class Hairdryer: Appliance { }

现在我们有了一个定义东西是否CanCook的协议,和一个定义我们家里东西的类。当我们把这两个合二为一时候就变得复杂了——用餐饮工具(Appliance)做饭。

定义它们很简单,因为它们可以归入Appliance的子类,并符合CanCook

class Oven: Appliance, CanCook { }
class Microwave: Appliance, CanCook { }

Swift的existential可以支持使用它们。但除非你是认识某个大厨,不然你应该找不到一个大厨来你家做饭。类似的,除非你实在没办法,你也不会用一个吹风机做饭。

结果就是,这两个函数都不够好用——它们并没有完整描绘出我们想要接收的文件类型:

func makeDinner(using: Appliance) { }
func makeDinner(using: CanCook) { }

好在通过写Appliance & CanCook,Swift让我们能够把协议与子类合并到一个existential中。我们希望某些东西是日常工具(Appliance),并符合CanCook协议,就像这样:

func makeDinner(using: Appliance & CanCook) { }

2.协议扩展可以提供默认属性值

协议扩展为方法的执行提供了默认属性值,这些默认值之后可以被符合类型覆盖,但你也可以用它们为属性提供默认值。

下例中我们创建一个Fadeable协议,并在设定好的秒数后逐渐淡出:

protocol Fadeable {
    var fadeSpeed: TimeInterval { get }
    func fadeOut()
}

比起给所有符合类型添加各自的淡出速度和fadeOut()方法,我们可以在一个协议扩展中为它们提供默认值。

extension Fadeable where Self: UIView {
    var fadeSpeed: TimeInterval {
        return 1.0
    }

    func fadeOut() {
        UIView.animate(withDuration: fadeSpeed) {
            self.alpha = 0
        }
    }
}

这样你可以让新的子类符合它们,而不用担心重复写相同的默认值

class MyViewClass: UIView, Fadeable { }

3.检查所有的集合项目是否满足一个状态

Swift 4.2新推出了allSatisfy()方法,让它运行一个状态闭包(condition closure),如果传递给这个闭包后,所有元素都返回true,那么allSatisfy()就返回true

例如某人考试结果数组如下:

let scores = [85, 88, 95, 92]

根据一个学生是否所有考试都达到85分,决定他是否通过。

let passed = scores.allSatisfy { $0 >= 85 }

4.使用解构(destructuring)操作元祖(tuples)

解构能够把元祖分解成独立数值,这样就可以更容易的操作它们。比如你也许想调用这样一个函数:

func getCredentials() -> (name: String, password: String) {
    return ("Taylor Swift", "biebersux")
}

它会返回一个包含两个字符串的元祖,如果你想让他们继续在一起,你可以:

let user = getCredentials()
print(user.name)
print(user.password)

然而,重构让我们能够把它们分开:

let (username, password) = getCredentials()
print(username)
print(password)

你甚至可以在函数被调用完后做这些——它们是一样的:

let user = getCredentials()
let (username, password) = user

这个技术让Swift能够简单轻易地解决一个经典入门代码问题:怎样在不使用第三个变量的情况下,交换两个变量。

多亏重构,Swift才能有这种最简单的解决方式:

var a = 10
var b = 20
(a, b) = (b, a)

5.通过溢出(overflow)算符让加减法能够环绕处理

所有的Swift整型都有最大值,比如UInt8的最大值是255,Int64的最大值是9,223,372,036,854,775,807。

为了保证安全,如果超过整型的限值,Swift会自动崩溃。比如下面的代码在编译时没问题而运行时会崩溃

let highScore = Int8.max
let newHighScore = highScore + 1

因为它在Int8.max上加1,产生了超过Int8存储范围的128。尽管崩溃听起来不好,但是至少它保证了安全。

不过,Swift提供了另一种处理方法:我们可以用overflow做加法,它让Swift绕回最小值,而不是崩溃。

let highNumber = UInt8.max
let nextNumber = highNumber &+ 1

它实际上挺常用,例如MySQL数据库会自动分配整数ID到数据库表单的行中。但是当整数都用完后,它会绕回并从1开始查到未使用ID,其中有些会随时间被删除。

6.公众只读,个人可写

尽管Swift的访问控制过去倍受诟病,但通过使用2个不同的访问控制属性可以改善很多。

例如下面的结构代表一家银行:

struct Bank {
    var address: String
}

我们对address没有使用任何访问控制,意味着任何人都可以读取并改写它。如果我们对这个属性用private,别人是改不了它,但也无法读它了。

Swift做出了一个兼顾:public private(set)。它可以让一个属性可被读取,但不能被写入。这样所有人都可以读取我们银行的地址,但只有银行才能改它。

struct Bank {
    public private(set) var address: String
}

7.成员逐一初始化(memberwise initializers)与自定初始化协同

Swift结构默认用成员逐一初始化,它可以方便快捷地创建实例

struct Score {
    var player: String
    var score: Int
}
 
let highScore = Score(player: "twostraws", score: 556)

但是如果你创建自己的初始化,你会自动失去成员逐一初始化。这是考虑到安全问题:你的初始化似乎是做了一些你觉得很重要的额外工作,所以如果Swift还用成员逐一初始化,那你的额外工作会被跳过。

如果你想要你的初始化与成员逐一初始化同时使用,步骤很简单。把你的初始化声明到一个扩展中,像这样:

struct Score {
    var player: String
    var score: Int
}
 
extension Score {
    init(player: String) {
        self.player = player
        score = 0
    }
}
 
// 现在它们都可用了
let highScore1 = Score(player: "twostraws", score: 0)
let highScore2 = Score(player: "twostraws")

8.static vs class属性

Swift中的类属性可以用2种关键词创建:static 和 class。它们都能让一个类中所有实例共享某个属性,但static意味着final,即无法在子类中被覆盖。

例如我们可以创建一个Building类,并定义一个用于存储建筑规划的class属性,和一个用于存储安全须知的static属性。

class Building {
    class var zoningRestrictions: String {
        return "None"
    }
 
    static var safetyRequirements: [String] {
        return ["Fire escapes", "Sprinklers"]
    }
}

因为zoningRestrictions是class属性,可以在子类中修改,比如居民区建住房,商业区建写字楼等等。相对的safetyRequirements是一个static属性,意味着所有房屋和子类必须符合安全法规。

代码如下:

class Skyscraper: Building {
    // this is allowed
    override class var zoningRestrictions: String {
        return "Dense commercial only"
    }
 
    // but this is not
    override static var safetyRequirements: [String] {
        return ["Sprinklers"]
    }
}

9. == 和 === 是不一样的

==运算符用于检测两个Equatable类型是否相等,例如

1 == 1
"kayak" == String("kayak".reversed())
[2, 4, 6] == [1, 2, 3].map { $0 * 2 }

通过对Equatable的自动综合分析,对==的支持就像对类型定义添加Equatable一样简单。但如果是对类,有另一个运算符:===。

因为类中的实例只不过是对内存特定地址的引用,===用于检查一个类中的2个实例是否指向同一段内存地址。

所以下面的情况会被认为是true

class Lightsaber {
    var color = "Blue"
}
 
let saber1 = Lightsaber()
let saber2 = saber1
saber1 === saber2

===运算符完全不使用Equatable,这就是说如果你创建2个拥有相同属性的独立对象,===会返回false

let saber3 = Lightsaber()
saber1 === saber3

10.通过numericCast()在整型间转换

在使用整数方面,Swift一直有高度选择性,如果你不留意,经常会发现你的代码中分散着Int(), UInt32(),和其他类型转换。也许这段代码不会出错,但它并不易于阅读:这就是为什么我们需要强制制定一种整型。

Swift有个专用的整型转换函数numericCast(),用了它就可以做到“我不关心这里需要什么类型,请查明白”。这样比起硬编码的类型,它可以更清楚的传达你的意图:为了运行的更好,你需要把一种整型转换到另外一种,但并不关心到底是怎么转换的

它的常用地点之一是arc4random_uniform()函数,这个函数会接收一个UInt32参数并返回一个UInt32,这里经常要在Int与UInt32之间加类型转换。

使用numericCast的话,你就可以写出很好的任意范围的实现

func random(in range: Range
<int>
 ) -> Int {
    return numericCast(arc4random_uniform(numericCast(range.count)))
        + range.lowerBound
}
</int>

额外小技巧:如果不用 ! 那用什么

不是所有人都喜欢NOT运算符,!,主要是因为它读起来不自然。然而Swift中功能,方法,闭包,运算符之间的界限变得模糊了。所以如果你想的话,可以把!转化为它的函数:

let not = (!)

现在你可以用not(someBool)代替!someBool

let loggedIn = false
 
if not(loggedIn) {
    print("Please log in.")
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK