Share 10 practical Swift tips

  • 2020-06-19 11:48:21
  • OfStack

preface

While programming languages don't die so easily, development groups that adhere to the fading paradigm are doing just that. If you're developing applications for mobile devices and you haven't looked at Swift yet, be warned: when Swift involves Mac, iPhone, ipad, Apple Watch and future devices, it not only supplanting ES10en-ES11en, but also replacing the C language for embedded development on the Apple platform. Swift has a number of interesting grammars, features, and features that you can take advantage of once you've mastered usage.

In this article I'll walk you through the 10 tips I've selected, along with the verified code for you to try out.

1. Class and protocol existential

The Existential type allows us to say what function we want a type to have without having to request something specific. For example, we can write a function that receives a class or subclass:


func process(user: User) { }

Then we write a function that accepts any type of object that conforms to a protocol:


func identify(thing: Identifiable) { }

Swift allows us to have existential represent both classes and protocols

In the following example, there is a protocol and a class that conforms to the protocol


protocol CanCook { }
class CelebrityChef: CanCook { }

And then I have a class, and then I have a subclass


class Appliance { }
class Hairdryer: Appliance { }

Now we have a protocol that defines whether something is CanCook or not, and a class that defines something in our home. It gets complicated when we combine the two into one -- using Appliance.

Defining them is easy because they can be subclassed under Appliance and conform to CanCook


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

existential of Swift can support their use. But unless you know a chef, you shouldn't be able to find one to cook in your home. Similarly, you won't cook with a hair dryer unless you really can't help it.

As a result, neither of these functions works well -- they don't give a complete picture of the type of file we want to receive:


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

Good thing by writing Appliance & CanCook, Swift allows us to combine protocols and subclasses into one existential. We want something that is an everyday tool (Appliance) and complies with the CanCook protocol, like this:


func makeDinner(using: Appliance & CanCook) { }

2. The protocol extension can provide default attribute values

Protocol extensions provide default property values for method execution, which can then be overridden by conforming types, but you can also use them to provide default values for properties.

In the following example, we create an Fadeable protocol and gradually fade out after a set number of seconds:


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

Instead of adding their own fade-out speed and fadeOut() methods to all conforming types, we can provide them with default values in a protocol extension.


extension Fadeable where Self: UIView {
 var fadeSpeed: TimeInterval {
 return 1.0
 }
 
 func fadeOut() {
 UIView.animate(withDuration: fadeSpeed) {
  self.alpha = 0
 }
 }
}

This way you can make new subclasses conform to them without having to worry about repeating the same default


class MyViewClass: UIView, Fadeable { }

3. Check whether all collection items satisfy 1 state

Swift 4.2 introduces the new allSatisfy() method, which runs one state closure (condition closure) and returns allSatisfy() if all elements return true after passing to the closure

For example, the array of someone's test results is as follows:


func identify(thing: Identifiable) { }
0

It is decided whether a student passes according to whether he achieves 85 on all the exams.


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

4. Use deconstruction (destructuring) to operate primitives (tuples)

Deconstruction can decompose primitives into independent values so that they can be manipulated more easily. For example, you might want to call a function like this:


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

It will return 1 meta ancestor with 2 strings, if you want them to continue at 1, you can:


func identify(thing: Identifiable) { }
3

However, refactoring allows us to separate them:


func identify(thing: Identifiable) { }
4

You can even do this after the function is called -- they look like this:


func identify(thing: Identifiable) { }
5

This technique allows Swift to easily solve a classic introductory code problem: how to swap two variables without using a third variable.

Thanks to refactoring, Swift has this simplest solution:


func identify(thing: Identifiable) { }
6

5. Enable addition and subtraction to wrap around by overflow (overflow) operator

All Swift integer have maximum value, such as the UInt8 a maximum of 255, the Int64 a maximum of 9223372036854775807.

To be safe, Swift crashes automatically if the integer limit is exceeded. For example, the following code will crash at compile time but run time


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

Because it adds 1 to Int8.es133EN, it produces 128 more than Int8 storage range. As bad as crashing sounds, at least it keeps you safe.

However, Swift provides another way to do this: we can add with overflow, which lets Swift wrap around the minimum instead of crashing.


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

It's actually quite common, for example, the MySQL database automatically assigns the integer ID to the rows of the database form. But when all the integers are used up, it will loop back and look up unused ID from 1, some of which will be deleted over time.

6. Public only, individuals can write

Although Swift's access control has been much criticized in the past, it can be improved by using two different access control properties.

For example, the following structure represents a bank:


func identify(thing: Identifiable) { }
9

We don't use any access control for address, meaning that anyone can read and override it. If we use private for this property, no one can change it, but no one can read it either.

Swift makes one compromise: public private(set) It allows a property to be read, but not written. So everyone can read our bank's address, but only the bank can change it.


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

7. Member by member initialization (memberwise initializers) is coordinated with custom initialization

By default, the Swift structure is initialized with members one by one, making it easy and quick to create instances


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

But if you create your own initializers, you will automatically lose member initializers by one. This is due to safety concerns: your initialization seems to be doing one extra task that you feel is important, so if Swift also initializes with members one by one, your extra work is skipped.

If you want your initialization to be used with member by member by member by member, the steps are simple. Declare your initialization in an extension like this:


struct Score {
 var player: String
 var score: Int
}
 
extension Score {
 init(player: String) {
  self.player = player
  score = 0
 }
}
 
//  Now they're all available 
let highScore1 = Score(player: "twostraws", score: 0)
let highScore2 = Score(player: "twostraws")

static vs class attribute

Class attributes in Swift can be created with two keywords: static and class. Both allow all instances of a class to share a property, but static means final, which cannot be overridden in a subclass.

For example, we can create an Building class and define an class property for storing building planning and an static property for storing security instructions.


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

Since zoningRestrictions is an class attribute, it can be modified in subclasses, such as residential building, commercial building, office building, and so on. The opposite of safetyRequirements is an static attribute, meaning that all houses and subclasses must comply with safety regulations.

The code is as follows:


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. == and === are different

The == operator is used to detect whether two Equatable types are equal, for example


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

Through automatic synthesis analysis of Equatable, support for == is as simple as adding Equatable1 to a type definition. But for classes, there is another operator: ===.

Because an instance in a class is nothing more than a reference to a specific memory address, === is used to check if two instances in a class point to the same memory address.

So the following would be considered true


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

The === operator does not use Equatable at all, which means that if you create two separate objects with the same property, === will return false


let saber3 = Lightsaber()
saber1 === saber3

10. Convert between integers via numericCast()

Swift1 is highly selective in using integers, and if you're not careful, you'll often find your code scattered with Int(), UInt32(), and other conversions. This code may be error-free, but it's not easy to read: that's why we need to enforce an integer.

Swift has a dedicated integer conversion function that numericCast() USES to say, "I don't care what type I need here, please check." In this way, it can communicate your intention more clearly than the hard-coded type: in order to run better, you need to convert from one type to another, but don't care how

Its common location 1 is the arc4random_uniform() function, which takes one UInt32 argument and returns one UInt32, often casting between Int and UInt32.

With numericCast, you can write a nice arbitrary implementation


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

Extra tip: If not! What with that

Not everyone likes the NOT operator,! "Mainly because it doesn't sound natural. In Swift, however, the boundaries between functions, methods, closures, and operators become blurred. So you can put it if you want! Convert to its function:


let not = (!)

Now you can use not(someBool) instead! someBool


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

conclusion


Related articles: