1, Currying
Swift can use Currying, which means that a method that takes more than one parameter can be converted into a method that takes the first parameter and returns a new method that takes the remaining parameters and returns a result
func add(_ v1:Int,_ v2:Int) -> Int {
return v1 + v2
}
print(add (1, 2)) / / curry (Currying) func add (_ v: Int) - > (Int) - > Int {return {$0 + v}
}
print(add(1)(2))
Copy the code
2, mutating
Swift’s protocol can be implemented not only with class types, but also with structs and enums.
The Swift mutating keyword modifier is used to modify the struct or enum variable in the method, so if you do not write the mutating keyword in the protocol method, the other person uses the struct or enum to implement the protocol. You can’t change your variables in a method
When a class is used to implement a protocol with a mutating method, there is no need to add the mutating modifier in front of the implementation, because the class can change its member variables at will. So the mutating modifier in the protocol is completely transparent to the implementation of the class and can be treated as non-existent
protocol Vehicle {
var numberOfWheels:Int{get}
mutating func changeNumberOfWheels()
}
struct MyCar:Vehicle {
var numberOfWheels: Int = 4
mutating func changeNumberOfWheels() {
numberOfWheels = 4
}
}
class Cars: Vehicle {
var numberOfWheels: Int = 0
func changeNumberOfWheels() {
numberOfWheels = 2
}
}
Copy the code
3, the Sequence
A Sequence is a collection of values of the same type and provides the ability to iterate over these values.
The most common way to iterate over a Sequence is through a for-in loop
let animals = ["Antelope"."Butterfly"."Camel"."Dolphin"]
for animal in animals {
print(animal)
}
Copy the code
Sequence protocol definition
protocol Sequence {
associatedtype Iterator: IteratorProtocol
func makeIterator() -> Iterator
}
Copy the code
The Sequence protocol has only one method that must be implemented, makeIterator()
MakeIterator () needs to return an Iterator, which is an IteratorProtocol type.
If you provide an Iterator, you can implement a Sequence. What is Iterator?
Iterator Iterator is the IteratorProtocol in the Swift 3.1 standard library. It is used to provide iteration capability for Sequence. For Sequence, we can use for-in to iterate over the elements, but behind the for-in is the IteratorProtocol
The IteratorProtocol is defined as follows:
public protocol IteratorProtocol {
associatedtype Element
public mutating func next() -> Self.Element?
}
Copy the code
For this for… In circulation
let animals = ["Antelope"."Butterfly"."Camel"."Dolphin"]
for animal in animals {
print(animal)
}
Copy the code
The compiler actually converts the above code into the following code
var animalIterator = animals.makeIterator()
while let animal = animalIterator.next() {
print(animal)
}
Copy the code
- 1. Get an Iterator for the Animals array
- 2. In a while loop, the Iterator gets the next element and operates on the element
- 3. Exit the loop when next() returns nil
Implement a reverse order
Class ReverseIterator<T>: IteratorProtocol {typeAlias Element = T var array: [Element] var currentIndex = 0 init(array: [Element]) { self.array = array currentIndex = array.count - 1 } func next() -> Element? {if currentIndex < 0{
return nil
}
else {
let element = array[currentIndex]
currentIndex -= 1
returnStruct ReverseSequence<T>:Sequence {var array:[T] ReverseSequence (array: [T]) { self.array = array } typealias Iterator = ReverseIterator<T> func makeIterator() -> ReverseIterator<T> {return ReverseIterator(array: self.array)
}
}
for item in ReverseSequence(array: animals){
print(item)
}
Copy the code
Reference: Swift Sequence (1)
4. Tuples
Tuple is the only compound type in swift programming language, which can collate any type of a specified finite number into an object at a time. Each type in a tuple can be any structure, enumeration or class type, or even a tuple or empty tuple.
Swapping inputs, for example, is what ordinary programmers have probably written for ages
func swapMel1<T>(a:inout T, b:inout T) {
let temp = a
a = b
b = temp
}
Copy the code
But with multivariate groups, we can swap without using extra space, and suddenly get to literary programmer writing
func swapMel2<T>(a:inout T, b:inout T) {
(a,b) = (b,a)
}
Copy the code
5. Automatic closure (@autoclosure
)
An automatic closure is a closure that is automatically created to package expressions passed to functions as actual arguments. It takes no actual arguments, and when it is called, it returns the value of the internally packaged expression
The nice thing about this syntax is that it lets you omit the parentheses surrounding functional formal arguments by writing plain expressions instead of explicit closures.
func getFirstPositive1(_ v1:Int, _ v2:Int) -> Int {
return v1 > 0 ? v1 : v2
}
getFirstPositive1(1, 2)
func getFirstPositive2(_ v1:Int, _ v2:() -> Int) -> Int {
returnv1 > 0 ? v1 : V2 ()} getFirstPositive2(1, 2) getFirstPositive2(1, {2}) func getFirstPositive3(_ v1:Int, _ v2:@autoclosure () -> Int) -> Int {return v1 > 0 ? v1 : v2()
}
getFirstPositive3(1, 2)
Copy the code
-
@autoClosure will automatically wrap 2 as {2}
-
@autoClosure only supports () -> T format arguments
??
In Swift, there is a very useful operator that can be used to quickly condition nil, which is?? . This operator evaluates the input and returns its value if the left value is non-nil Optional, and the right value if the left value is nil
?? It’s just an @autoclosure
public func ?? <T>(optional: T? , defaultValue: @autoclosure () throws -> T) rethrows -> TCopy the code
Let’s guess…? The implementation of the
func ?? <T>(optional: T? , defaultValue: @autoclosure () -> T) -> T { switch optional {case .Some(let value):
return value
case .None:
return defaultValue()
}
}
Copy the code
You may wonder why this is an autoclosure. It simply accepts T as the argument and returns it. Why would you want to wrap it in () -> T? In fact, this is one of the most laudable things about Autoclosure. If we use T directly, it means that in?? Before the operator can actually take a value, we have to have a default value passed into the method, which is usually not a big problem, but if the default value is computed through a bunch of complicated calculations, it can be a waste of time — because if optional isn’t nil, We don’t actually use the default value at all and return the unpacked value of optional. Such overhead is entirely avoidable by postponing the calculation of the default value until optional determines that it is nil
In the Swift, actually && and | | the two operators use the @ autoclosure
public static func && (lhs: Bool, rhs: @autoclosure () throws -> Bool) rethrows -> Bool
public static func || (lhs: Bool, rhs: @autoclosure () throws -> Bool) rethrows -> Bool
Copy the code
Escaping closures (@escaping) and non-escaping Closures (@Noescaping)
Escaping Closures (@escaping)
When a closure is passed to a function as an actual argument, the closure is said to have escaped because it was called after the function returned. When you declare a function that accepts closures as formal arguments, you can make it clear that closures are allowed to escape by writing @escaping before the formal argument.
One way closures can escape is if they are stored in variables defined outside the function. For example, many functions take the actual parameters of the closure as callbacks to start asynchronous tasks. The function returns after starting the task, but the closure does not complete until the task is complete — the closure needs to escape so that it can be called later
Example: closure that is invoked when the network request ends. The closure is executed some time after the request is made, not necessarily within the scope of the function
override func viewDidLoad() {
super.viewDidLoad()
getData { (data) in
print("Closure returns result: \(data)")
}
}
func getData(closure:@escaping (Any) -> Void) {
print("Function starts execution --\(thread.current)")
DispatchQueue.global().async {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+2, execute: {
print("Closure executed --\(thread.current)")
closure("345")})}print("End of function execution --\(thread.current)")}Copy the code
It can be seen from the results that the life of the escape closure is longer than that of the function.
The life cycle of an escape closure:
- 1. Closures are passed to functions as arguments;
- 2. Exit function;
- The closure is called, and the closure life cycle ends
That is, the life of an escape closure is longer than that of a function. When the function exits, the reference to the escape closure is still held by other objects and will not be released at the end of the function.
Non-escaping closures (@Noescaping)
A function that takes a closure that is called before the function ends.
override func viewDidLoad() {
super.viewDidLoad()
handleData { (data) in
print("Closure returns result :\(data)")
}
}
func handleData(closure:(Any) -> Void) {
print("Function starts execution --\(thread.current)")
print("Closure executed --\(thread.current)")
closure("123")
print("End of function execution --\(thread.current)")}Copy the code
Why are escape closures and non-escape closures separated
In order to manage memory, a closure will forcibly reference all objects it captures. For example, if you access the properties and functions of the current controller in the closure, the compiler will ask you to display a reference to self in the closure, so that the closure will hold the current object, leading to circular references.
A non-escape closure does not generate a circular reference, it is released within the scope of the function, and the compiler is guaranteed that the closure will release any objects it captures at the end of the function; Another benefit of using non-escape closures is that the compiler can apply more powerful performance optimizations. For example, when the life cycle of a closure is known, some retain and release calls can be eliminated. In addition, memory for the context of a non-escape closure can be stored on the stack rather than on the heap.
7. Operators
Unlike Objective-C, Swift supports features such as overloaded operators, which are probably most commonly used to define simple computations
System operator
Let’s say we need a data structure that represents a two-dimensional vector
struct Vector2D {
var x:CGFloat = 0
var y:CGFloat = 0
}
Copy the code
A very simple requirement is to add two Vector2D
letV1 = Vector2D(x: 2.0, y: 3.0)letVector2D(x: 1.0, y: 4.0) Vector2D(x: 1.0, y: 4.0)let v3 = Vector2D(x: v1.x + v2.x, y: v1.y + v2.y)
Copy the code
It might be fine if you just do it once, but in general we do a lot of this. In this case, we might prefer to define a Vector2D addition operation to keep the code simple and clear
func +(left:Vector2D,right:Vector2D) -> Vector2D {
Vector2D(x: left.x + right.x, y: left.y + right.y)
}
let v3 = v1 + v2
Copy the code
8. Custom operators
In Swift language, common operators have +, -, *, /, >, <, = =, &&, | |, and so on, if you don’t like, you can also define their own operators.
precedencegroup
: Defines the precedence of the operatorassociativity
Associative property of the: operatorhigherThan
,lowerThan
: The priority of the operatorPrefix, infix, postfix
: prefix, infix, suffix operators
infix
Precedencegroup MyPrecedence {// higherThan: AdditionPrecedence lowerThan: AdditionPrecedence > AdditionPrecedence > AdditionPrecedence > associativity: Nonefalse // true= assignment operator,false= non-assignment operator} infix operator +++: MyPrecedence // Inherits the MyPrecedence group // infix operator +++: AdditionPrecedence func +++(left: Int, right: Int) -> Int {AdditionPrecedence func +++(left: Int, right: Int)return left+right*2
}
print(2 + + + 3) / / 8Copy the code
The prefix
prefix operator ==+
prefix func ==+(left: Int) -> Int {
return left*2
}
print(= = + 2) / / 4Copy the code
The suffix
postfix operator +==
postfix func +==(right: Int) -> Int {
return right*3
}
print/ / 6 (2 + = =)Copy the code
9,inout
: Input and output parameters
A variable form parameter can only be changed inside a function. If you want a function to be able to change the value of a formal parameter, and you want those changes to persist after the function ends, you need to define formal parameters as input and output formal parameters.
Adding an inout keyword at the beginning of the formal parameter definition defines an input/output formal parameter. An input/output formal parameter has a value that can be input to a function, modified by the function, and printed out to replace the original value.
You can only treat variables as actual parameters of input and output formal parameters. You cannot use constants or literals as actual arguments because constants and literals cannot be modified. When passing a variable as an actual argument to an input/output formal argument, add an ampersand (&) to make it clear that it can be modified by the function.
var b = 10
func test(a:inout Int) {
a = 20
}
test(a: &b)
print(b) //20
Copy the code
You can use inout to define an input and output parameter: you can modify the values of external arguments inside the function
- 1. Immutable parameters cannot be marked as
inout
- 2,
inout
Parameters cannot have default values - 3,
inout
Arguments can only be passed in values that can be assigned more than once - 4,
inout
The essence of a parameter is address passing
10, the subscript
Subscripts are familiar. Using subscripts to read and write data structures such as arrays or dictionaries seems to be the industry standard in most languages. In Swift, Array and Dictionary of course also implement subscript reading and writing
Var arr = [1, 2, 3] arr [2] / / 3 arr [2] = 4 / / arr = var dic = [[1, 4-trichlorobenzene]"cat":"meow"."goat":"mie"]
dic["cat"] // {Some "meow"}
dic["cat"] = "miao" // dic = ["cat":"miao"."goat":"mie"]
Copy the code
As a language that represents advanced productivity, Swift allows us to customize subscripts, and we found the subscript types that Array already supports
subscript (index: Int) -> T
subscript (subRange: Range<Int>) -> Slice<T>
Copy the code
We find that if we want to extract the 0, 2, 4 subscripts, we need to iterate through the enumeration.
A better way to do this is to implement a reading method that takes an array as a subscript input
extension Array {
subscript(input: [Int]) -> ArraySlice<Element> {
get {
var result = ArraySlice<Element>()
for i in input {
assert(i < self.count, "Index out of range")
result.append(self[i])
}
return result
}
set {
for (index,i) in input.enumerated() {
assert(i < self.count, "Index out of range")
self[i] = newValue[index]
}
}
}
}
var arr = ["a"."b"."c"."d"."z"]
print[[0, 3]] (arr) / / /"a"."d"]
Copy the code
11. Nested functions
We can use functions as arguments or variables, with functions nested inside
func forward(_ forward:Bool) -> (Int) -> Int { func next(_ input:Int) -> Int { input + 1 } func previous(_ input:Int) -> Int { input - 1 }return forward ? next : previous
}
Copy the code
namespaces
One of objective-C’s longstanding complaints is the lack of namespaces, as all code and referenced static libraries end up being compiled into the same domain and binary during application development. The result is compile-time conflicts and failures if we have duplicate class names. To avoid this, objective-C types are prefixed with two or three letters, such as Apple’s NS and UI prefixes, SK (StoreKit), CG (CoreGraphic), and so on. Most developers in the Objective-C community follow this convention, prefixing their abbreviations with libraries like AFNetworking or MBProgressHUD. This solves part of the problem, at least when we directly refer to libraries from different people, the probability of conflicts is greatly reduced, but prefixes do not mean that there are no conflicts, and sometimes we do have the same situation even if we use prefixes. On the other hand, you might want to use two different libraries that reference the same popular third-party library in each of them without changing the name. This is fine if you use one of these libraries separately, but once you add both libraries to your project, the shared third-party library conflicts with itself.
In Swift, because namespaces are available, even types with the same name can coexist peacefully as long as they come from different namespaces. Unlike C#, which explicitly specifies namespaces in files, Swift’s namespaces are based on modules rather than explicitly specified in code, with each module representing a namespace in Swift. That is, the type names in the same target cannot be the same. By default, the content added to the main target of the app is in the same namespace when developing the app. We can create a new Module by creating the target of the Cocoa (Touch) Framework. This allows us to add a type with the same name to two different targets
13. Typealias Alias
We can give a complex and difficult to understand type an alias to make it easier to use and understand
The swift library defines Void as an empty tuple
public typealias Void = ()
Copy the code
We know that there are no byte, short, or Long types in Swift, but if we want such types, we can use TypeAlias
typealias Byte = Int8
typealias Short = Int16
typealias Long = Int64
Copy the code
We can also give the function an alias
typealias IntFn = (Int,Int) -> Int
func difference(v1:Int,v2:Int) -> Int {
v1 - v2
}
let fn:IntFn = difference
print(fn (2, 1)) / / 1Copy the code
We can also alias tuples
typealias Date = (year:Int,month:Int,day:Int)
func test(_ date:Date) {
print(date.year)
}
test(,10,30) (2019)Copy the code
14, associatedtype
We can define properties and methods in the Swift protocol and require that they be implemented according to the protocol type:
protocol Food { }
protocol Animal {
func eat(_ food: Food)
}
struct Meat: Food { }
struct Grass: Food { }
Copy the code
struct Tiger: Animal {
func eat(_ food: Food) {
}
}
Copy the code
Since tigers are not vegetarian, we probably need to do some conversion work to use meat in Tiger’s Eat
A colon can be used in the AssociatedType declaration to specify that the type satisfies a protocol
protocol Animal {
associatedtype F: Food
func eat(_ food: F)
}
struct Tiger: Animal {
func eat(_ food: Meat) {
print("eat \(meat)")
}
}
struct Sheep: Animal {
func eat(_ food: Grass) {
print("eat \(food)")}}Copy the code
However, with the addition of AssociatedType, the Animal protocol cannot be used as a separate type. Imagine that we have a function to determine whether an animal is dangerous:
func isDangerous(animal: Animal) -> Bool {
if animal is Tiger {
return true
} else {
return false}}Copy the code
complains
Protocol ‘Animal’ can only be used as a generic constraint because it has Self or associated type requirements
This is because Swift needs to determine all types at compile time, and since Animal contains an indeterminate type, the F of Animal is indeterminate as it changes from one type to another. You will not be able to specify the type of the eat argument. When a protocol adds a constraint such as AssociatedType or Self, it can only be used as a generic constraint, not as a placeholder for independent types, and loses the dynamic dispatch feature. In other words, in this case, we need to rewrite the function to be generic
func isDangerous<T: Animal>(animal: T) -> Bool {
if animal is Tiger {
return true
} else {
return false
}
}
isDangerous(animal: Tiger()) // true
Copy the code
15. Variable parameters
A variable form parameter can accept zero or more values of a specific type. The variable parameter must be of the same type. When calling a function, you can use a variable formal parameter to declare that the formal parameter can be passed a variable number of values. You can do this by inserting three dots (…) after the type name of a formal parameter. To write variable form parameters.
func sum(_ numbers:Int...) -> Int{
var total = 0
for item in numbers {
total += item
}
return total
}
Copy the code
Swift’s own print function
/// - Parameters:
/// - items: Zero or more items to print.
/// - separator: A string to print between each item. The default is a single
/// space (`""`).
/// - terminator: The string to print after all items have been printed. The
/// default is a newline (`"\n"`).
public func print(_ items: Any... , separator: String ="", terminator: String = "\n")
Copy the code
- 1, the first parameter: is the value to print, is a variable parameter
- 2. The second argument: the place where the two print values are connected. The default is a space
- 3, the third argument: the end of the default is \n newline
16. Initialization
Initialization is the process of preparing an instance for a class, structure, or enumeration. This process requires setting an initial value for each storage property in the instance and performing any other necessary configuration or initialization before the new instance can be used
You do this by defining an initializer, which is more like a special method for creating new instances of a particular type. Unlike objective-C initializers, Swift initializers do not return values. The primary role of these initializers is to ensure that new instances of a type are correctly initialized before being used for the first time.
Classes have two types of initializers
- 1. Designated Initializer
- 2. Convenience Initializer
class Person { var age: Int var name: Init (age:Int, Name :String) {self.age = age self.name = name} // Convenience init(age:Int){self.init(age:age,name:"")}}Copy the code
- 1. Every class has at least one designated initializer. The designated initializer is the primary initializer of the class
- 2. The default initializer is always the class’s designated initializer
- 3. Classes tend to have a small number of designated initializers; a class usually has only one
Rules for calling each other to initializers
- The specified initializer must be called from its immediate parent
- 2. The convenience initializer must call another initializer from the same class
- 3. The convenience initializer must eventually call a specified initializer
17, Static & Class
The concept of type scope in Swift has two different keywords, static and class. That’s exactly what both of these words mean
In the context of non-class types, we use static to describe type scope uniformly
18. Default parameter
Swift’s methods support default parameters, which means that you can assign a default value to a parameter when declaring a method. If this parameter is passed in when the method is called, the value is used; if the input parameter is missing, the default value is used
func test(a:String = "1",b:String,c:String = "3") {}Copy the code
19. Match patterns
What are patterns patterns are rules for matching, such as case of switch, catch of error, if guard, while for statement
The mode in Swift has
- 1. Wildcard Pattern
- 2. Identifier Pattern
- 3. Value-binding Pattern
- 4. Tuple Pattern
- 5, Enumeration Case Pattern
- 6. Optional Pattern
- 7. Type-casting Pattern
- 8. Expression Pattern
Wildcard Pattern
_
Match any value_?
Matches non-nil values
enum Life {
case human(name:String,age:Int?)
case animal(name:String,age:Int?)
}
func check(_ life:Life){
switch life {
case .human(let name,_):
print("human:",name)
case .animal(letname,_?) :print("animal",name)
default:
print("other")
break
}
}
check(.human(name: "Xiao Ming", age: 20)) //human: check(. Human (name:"Little red", age: nil))//human: small red check(.animal(name:"dog", age: 5))//animal dog
check(.animal(name: "cat", age: nil))//other
Copy the code
Identifier Pattern
Is to assign the corresponding variable constant
let a = 10
let b = "text"
Copy the code
Value Binding Pattern
letPoint = (2,3) switch point {case (let x,let y):
print("x:\(x) y:\(y)")
}
//x:2 y:3
Copy the code
Tuple Pattern
Matches any primitive ancestor
letPoint = [(0, 0), (1, 0), (2, 0)]for (x,_) in point{
print(x)
}
//0
//1
//2
Copy the code
Enumeration Case Pattern
The if case statement is equivalent to a case switch statement, simplifying some judgment statements
let age = 2
//if
if age >= 0 && age <= 9 {
print("[0, 9]"} // Enumeration modeif case0... 9 = age{print("[0, 9]")}Copy the code
letages:[Int?] = [2, 3, nil, 5]for case nil in ages{
print("Has nil value")}Copy the code
Optional Pattern
letages:[Int?] = [nil, 2, 3, nil]for case let age? in ages{
print(age)
}
// 2
//3
Copy the code
Is equivalent to
letages:[Int?] = [nil, 2, 3, nil]for item in ages{
if let age = item {
print(age)
}
}
Copy the code
Type-casting Pattern
class Animal {
func eat() {
print(type(of: self),"eat")
}
}
class Dog: Animal {
func run() {
print(type(of: self),"run")
}
}
class Cat: Animal {
func jump() {
print(type(of: self),"jump")
}
}
func check(_ animal:Animal) {
switch animal {
case let dog as Dog:
dog.run()
dog.eat()
case is Cat:
animal.eat()
default:
break
}
}
check(Dog())
//Dog run
//Dog eat
check(Cat())
//Cat eat
Copy the code
Expression Pattern
You can customize the matching rules of expression patterns by overloading operators
struct Student {
var score = 0, name = ""
static func ~=(pattern:Int,value:Student) -> Bool{
value.score >= pattern
}
static func ~=(pattern:ClosedRange<Int>,value:Student) -> Bool{
pattern.contains(value.score)
}
static func ~=(pattern:Range<Int>,value:Student) -> Bool{
pattern.contains(value.score)
}
}
var stu = Student(score: 81, name: "tom")
switch stu{
case 100:print("> = 100")
case 90:print("> = 90")
case80.. < 90:print("[80, living]")
case60... 79:print("[60,]")
default:break
}
Copy the code
extension String{
static func ~=(pattern:(String) -> Bool,value:String) ->Bool{
pattern(value)
}
}
func hasPrefix(_ prefix:String) ->((String) -> Bool){{$0.hasPrefix(prefix)}}
func hasSuffix(_ prefix:String) ->((String) -> Bool){{$0.hasSuffix(prefix)}}
var str = "jack"
switch str {
case hasPrefix("j"),hasSuffix("k") :print("Starts with a J, or ends with a K.")
default:
break
}
Copy the code
20,… And.. <
The Range operator is used to simply specify a Range of consecutive counts from X to Y
We can take a closer look at Swift’s definition of the two operators
/// Forms a closed range that contains both `minimum` and `maximum`. func ... <Pos : ForwardIndexType>(minimum: Pos, maximum: Pos) -> Range<Pos> /// Forms a closed range that contains both `start` and `end`. /// Requres: `start <= end` func ... <Pos : ForwardIndexTypewherePos : Comparable>(start: Pos, end: Pos) -> Range<Pos> /// Forms a half-open range that contains `minimum`, but not /// `maximum`. func .. <<Pos : ForwardIndexType>(minimum: Pos, maximum: Pos) -> Range<Pos> /// Forms a half-open range that contains `start`, but not /// `end`. Requires: `start <= end` func .. <<Pos : ForwardIndexTypewherePos : Comparable>(start: Pos, end: Pos) -> Range<Pos> /// Returns a closed interval from `start` through `end` func ... <T : Comparable>(start: T, end: T) -> ClosedInterval<T> /// Returns a half-open interval from `start` to `end` func .. <<T : Comparable>(start: T, end: T) -> HalfOpenInterval<T>Copy the code
As you can see, all of these methods support generics. In addition to our usual input Int or Double, which returns a Range, this operator also has an overload that accepts Comparable input and returns ClosedInterval or HalfOpenInterval
For example, if you want to make sure that all characters in a word are lowercase, you can do this
let test = "helLo"
let interval = "a"."z"
for c in test {
if! interval.contains(String(c)) {print("\(c) Not lowercase"}} // Output // L is not lowercaseCopy the code
AnyClass, metatype and.self
In Swift, the concept of “Any” can be expressed in addition to Any and AnyObject, there is also an AnyClass. AnyClass is defined in Swift by a TypeAlias
public typealias AnyClass = AnyObject.Type
Copy the code
The result of anyObject. Type is a Meta. We always declare.type after the name of the Type, for example, a.type stands for the Type of Type A. That is, we can declare A meta-type to store the type A itself, and to retrieve its type from A, we need to use.self
In Fact, in Swift,.self can be used after a type to get the type itself, or after an instance to get the instance itself. The former method can be used to get a value representing the type, which can be useful at some point; And the latter because of the instance itself
class A {
class func method() {
print("Hello")}}let typeA: A.Type = A.self
typeArjun ethod () / / orlet anyClass: AnyClass = A.self
(anyClass as! A.Type).method()
Copy the code
What’s the point, you may ask, if we can’t just call a.method ()? Yes, we don’t need to care about the metatype of a single type at all, but the concept of metatype, or metaprogramming, can be very flexible and powerful, which comes in handy when we’re writing framework code. For example, when we want to pass some type, we don’t have to constantly change the code. In the example below, we get the MusicViewController and AlbumViewController meta-types declaratively, but this could have been done by reading in a configuration file or something. Metatype programming is hard to replace by storing these metattypes in arrays and passing them on to other methods for configuration
class MusicViewController: UIViewController {
}
class AlbumViewController: UIViewController {
}
let usingVCTypes: [AnyClass] = [MusicViewController.self,
AlbumViewController.self]
func setupViewControllers(_ vcTypes: [AnyClass]) {
for vcType in vcTypes {
if vcType is UIViewController.Type {
let vc = (vcType as! UIViewController.Type).init()
print(vc)
}
}
}
setupViewControllers(usingVCTypes)
Copy the code
In this way, we can build a framework and configure it in a DSL way to perform complex operations without touching the Swift code
22. Dynamic types
In Swift we can use dynamicType to get the dynamicType of an object (that is, the actual type at runtime, not the type specified by code or seen by the compiler). However, in use, Swift does not currently support multi-methods, that is, it cannot make appropriate overloaded method calls based on the type of the object when it is in motion
class Pet {}
class Dog:Pet {}
class Cat:Pet {}
func eat(_ pet:Pet) {
print("pet eat")
}
func eat(_ dog:Dog) {
print("dog eat")
}
func eat(_ cat:Cat) {
print("cat eat")
}
func eats(_ pet:Pet,_ cat:Cat) {
eat(pet)
eat(cat)
}
eats(Dog(), Cat())
//pet eat
//cat eat
Copy the code
Instead of printing the Dog type information to select the appropriate func eat(_ Dog :Dog) {} method at runtime, we ignored it and adopted the Pet method determined at compile time.
Because Swift does not dispatch dynamically by default, method calls can only be determined at compile time
To get around this limitation, we may need to perform judgments and conversions on input types
func eats(_ pet:Pet,_ cat:Cat) {
if let aCat = pet as? Cat {
eat(aCat)
}else if let aDog = pet as? Cat{
eat(aDog)
}
eat(cat)
}
Copy the code
23, attributes,
The properties declared in Swift include
- 1. Storage properties: Storage properties will actually allocate the address in memory for the storage of properties
- 2, computing properties: computing properties do not include storage, only provide
set
andget
methods
Storage properties
We can provide both willSet and didSet property observation methods in storage properties
class Person {
var age:Int = 0{
willSet{
print("About to set age from \(age) to \(newValue)")
}
didSet{
print("Age has been set from \(oldValue) to \(age)")}}}letP = Person() p.age = 10 // About to set age from 0 to 10 // Already set age from 0 to 10Copy the code
In willSet and didSet we can use newValue and oldValue to get the value to be set and the value to be set, respectively.
The setting of the property by the initializer method and the resetting of the property in willSet and didSet do not trigger the property observation call again
Calculate attribute
Attributes declared in Swift include storage attributes and computed attributes. Storing attributes will actually allocate the address in memory to store the attributes, while calculating attributes does not include the storage behind, but only provides two methods of SET and GET. Attribute observation and evaluation attributes cannot coexist in the same type. That is, it is impossible to have both set and willSet or didSet in a property definition.
We can do the same thing for willSet and didSet by rewriting set. If we can’t change the class and want to do something with property observation, we may need to subclass the class and override its properties
Overridden attributes do not know the specific implementation of the parent class attributes, but only inherit the name and type from the parent class attributes, so in the overridden attributes of the child class we can arbitrarily add attributes to observe the parent class attributes, regardless of whether the parent class is storing attributes or calculating attributes
class A {
var number:Int {
get{
print("get")
return1}set{
print("set")
}
}
}
class B:A {
override var number: Int{
willSet{
print("willSet")
}
didSet{
print("didSet")}}}let b = B()
b.number = 10
get
willSet
set
didSet
Copy the code
The calls to set and the corresponding property observations are expected. The thing to notice here is that GET is called first. This is because we implemented didSet, and oldValue is used in didSet, and that value needs to be retrieved and stored for later before the entire set action, otherwise it’s not guaranteed to be correct. If we don’t implement didSet, this get operation won’t exist either.
24, Lazy modifier
Delayed loading or delayed initialization is a common optimization method. When building and generating new objects, memory allocation will take a lot of time at runtime, especially if the properties and contents of some objects are very complex. In addition, some cases do not use all attributes of an object at once, and by default, storage attributes that are not used in a particular environment are also initialized and assigned, which is a waste of time
Swift provides a keyword lazy
class A {
lazy var str:String = {
let str = "Hello"
print("Output on first access")
return str
}()
}
let a = A()
print(a.str)
Copy the code
For simplicity, if we don’t need to do any extra work, we can just write the assignment to the lazy property, right
lazy var str1 = "word"
Copy the code
Reflection and Mirror
The term “reflection” is not often used in Objective-C because the Objective-C runtime is much more flexible and powerful than normal reflection. Things that many readers are used to, like generating a class or selector from a string and then generating an object or calling a method, are actually concrete representations of reflection. And in Swift, even if you put aside the Objective-C runtime, there’s reflection in the pure Swift category, but it’s much weaker.
Swift provides Mirror types for mapping
struct Person {
let name:String
let age:Int
}
let xiaoming = Person(name: "Xiao Ming", age: 10)
let r = Mirror(reflecting: xiaoming)
print("Xiaoming is \ (r.d isplayStyle)")
print("Number of attributes: \(r.dren. Count)")
for child in r.children{
print("Attribute name :\(child.label) Value :\(child.value)")} xiaoming is Optional (Swift. Mirror. DisplayStyle. Struct) attribute number: 2 attribute name: (Optional"name"Attribute name :Optional("age") 值:10
Copy the code
The descriptions of the elements contained in the result of the Mirror initialization are grouped under the children attribute
public typealias Child = (label: String? , value: Any) public typealias Children = AnyCollection<Mirror.Type.Child>Copy the code
If printing one by one is too much hassle, we can simply dump an object by taking a mirror image of it and printing it out as standard output
print(dump(xiaoming)) ▿ reflection. Person - name:"Xiao Ming"
- age: 10
Person(name: "Xiao Ming", age: 10)
Copy the code
A common application scenario is to do valueForKey: like KVC in Objective-C for objects of type Swift. We compare the name of the property to the key value we want to get. It’s very simple
func valueFrom(_ object: Any, key: String) -> Any? {
let mirror = Mirror(reflecting: object)
for child in mirror.children {
let (targetKey, targetMirror) = (child.label, child.value)
if key == targetKey {
return targetMirror
}
}
return nil
}
if let name = valueFrom(xiaoming, key: "name") as? String {
print("Get value by key: \(name)"} get the value by key: xiaomingCopy the code
In the current version, Swift’s reflection is not very powerful, and we can only read properties, not set them. It should also be noted that while it is theoretically possible to apply reflective features to actual app production, this set of mechanisms was originally designed for output in the REPL environment and Playground. So it’s best to stick to Apple’s rule and only use it for deep exploration of an object on the REPL and Playground, and avoid using it in app making — because you never know when they’re going to break down or be changed too much
26. (optional)
Click through to the official documentation and you can see that Optional is an enumeration
enum Optional<Wrapped> : ExpressibleByNilLiteral {
case none
case some(Wrapped)
public init(_ some: Wrapped)
}
Copy the code
Let’s see if these two values are the same anotherString literalString
var string:String? = "string"
var anotherString:String?? = string
var literalString:String?? = "string"
print(anotherString)
print(literalString)
print(anotherString == literalString)
//Optional(Optional("string"))
//Optional(Optional("string")) / /true
Copy the code
- 1.
var anotherString:String?? = string
And the way I write it is thatOptional.some(string)
- 2,
var literalString:String?? = "string"
The writing isOptional.some(Optional.some("string"))
They return the same value, so they’re equal
Are the next two objects equal?
var aNil:String? = nil
var anoterNil:String?? = aNil
var literalNil:String?? = nil
print(anoterNil)
print(literalNil)
print(anoterNil == literalNil)
//Optional(nil)
//nil
//false
Copy the code
- 1.
anoterNil
Yes optional contains an optional value fornil
The optional - 2,
literalNil
Yes The optional value isnil
The optional
We can print specific information using FR V-R
27. Match patterns
What are patterns patterns are rules for matching, such as case of switch, catch of error, if guard, while for statement
The mode in Swift has
- 1. Wildcard Pattern
- 2. Identifier Pattern
- 3. Value-binding Pattern
- 4. Tuple Pattern
- 5, Enumeration Case Pattern
- 6. Optional Pattern
- 7. Type-casting Pattern
- 8. Expression Pattern
Wildcard Pattern
_
Match any value_?
Matches non-nil values
enum Life {
case human(name:String,age:Int?)
case animal(name:String,age:Int?)
}
func check(_ life:Life){
switch life {
case .human(let name,_):
print("human:",name)
case .animal(letname,_?) :print("animal",name)
default:
print("other")
break
}
}
check(.human(name: "Xiao Ming", age: 20)) //human: check(. Human (name:"Little red", age: nil))//human: small red check(.animal(name:"dog", age: 5))//animal dog
check(.animal(name: "cat", age: nil))//other
Copy the code
Identifier Pattern
Is to assign the corresponding variable constant
let a = 10
let b = "text"
Copy the code
Value Binding Pattern
letPoint = (2,3) switch point {case (let x,let y):
print("x:\(x) y:\(y)")
}
//x:2 y:3
Copy the code
Tuple Pattern
Matches any primitive ancestor
letPoint = [(0, 0), (1, 0), (2, 0)]for (x,_) in point{
print(x)
}
//0
//1
//2
Copy the code
Enumeration Case Pattern
The if case statement is equivalent to a case switch statement, simplifying some judgment statements
let age = 2
//if
if age >= 0 && age <= 9 {
print("[0, 9]"} // Enumeration modeif case0... 9 = age{print("[0, 9]")}Copy the code
letages:[Int?] = [2, 3, nil, 5]for case nil in ages{
print("Has nil value")}Copy the code
Optional Pattern
letages:[Int?] = [nil, 2, 3, nil]for case let age? in ages{
print(age)
}
// 2
//3
Copy the code
Is equivalent to
letages:[Int?] = [nil, 2, 3, nil]for item in ages{
if let age = item {
print(age)
}
}
Copy the code
Type-casting Pattern
class Animal {
func eat() {
print(type(of: self),"eat")
}
}
class Dog: Animal {
func run() {
print(type(of: self),"run")
}
}
class Cat: Animal {
func jump() {
print(type(of: self),"jump")
}
}
func check(_ animal:Animal) {
switch animal {
case let dog as Dog:
dog.run()
dog.eat()
case is Cat:
animal.eat()
default:
break
}
}
check(Dog())
//Dog run
//Dog eat
check(Cat())
//Cat eat
Copy the code
Expression Pattern
You can customize the matching rules of expression patterns by overloading operators
struct Student {
var score = 0, name = ""
static func ~=(pattern:Int,value:Student) -> Bool{
value.score >= pattern
}
static func ~=(pattern:ClosedRange<Int>,value:Student) -> Bool{
pattern.contains(value.score)
}
static func ~=(pattern:Range<Int>,value:Student) -> Bool{
pattern.contains(value.score)
}
}
var stu = Student(score: 81, name: "tom")
switch stu{
case 100:print("> = 100")
case 90:print("> = 90")
case80.. < 90:print("[80, living]")
case60... 79:print("[60,]")
default:break
}
Copy the code
extension String{
static func ~=(pattern:(String) -> Bool,value:String) ->Bool{
pattern(value)
}
}
func hasPrefix(_ prefix:String) ->((String) -> Bool){{$0.hasPrefix(prefix)}}
func hasSuffix(_ prefix:String) ->((String) -> Bool){{$0.hasSuffix(prefix)}}
var str = "jack"
switch str {
case hasPrefix("j"),hasSuffix("k") :print("Starts with a J, or ends with a K.")
default:
break
}
Copy the code
28. Property access control
The five access levels in the Swift code are related to the source file that defines the entity and the module to which the source file belongs. Open has the highest access level, private the lowest, and internal is the default access level.
The open and public
: Makes an entity available in the source file of the module it defines, or in the source file of another module by importing the module it defines. whilepublic
Modified classes or class members can only be inherited or overridden by subclasses in the current module; whileopen
Can be inherited in another module or overridden by subclasses. Commonly used when defining a framework’s public interfaceopen
andpublic
;internal
: Enables entities to be used in any source file of the module they define, but not in the source file of other modules. Commonly used when defining the internal structure of an application or frameworkinternal
.fileprivate
: makes an entity usable only in the source file it defines. When these details are used throughout the file, usefileprivate
Hide implementation details for a particular feature block.private
: makes an entity usable in the class it declares, and in extensions of that declaration in the same file. Is used when these details are only used in a single declarationprivate
To hide the implementation details of a particular feature.
Open can be accessed in different modules, public can only be accessed in the current module, internal system default access control rights, Fileprivate can be inherited but cannot be modified or overridden by subclasses, and private cannot be inherited
29. Tail recursion
Recursion is a useful method in programming to translate and describe complex processes in a way that is easy to understand. For example, if we want to write a function that adds from 0 to n, we can do it recursively if we don’t know the formula for summing arithmetic sequences:
func sum(_ n:Int) -> Int {
if n == 0 {
return0}return n + sum(n - 1)
}
sum(1000000)
Copy the code
Thread 1: EXC_BAD_ACCESS (code=2, address= 0x7FFeEF3ffff8
This is because every recursive call to sum needs to keep the current state on the call stack, otherwise we wouldn’t be able to compute the final n + sum(n-1). When n is large enough and the call stack is deep enough, the stack space will be exhausted and an error will result, which is often called stack overflow.
Tail recursion in general for recursion, a good way to solve stack overflow is to write tail recursion. Tail recursion, as the name implies, is the form in which the last action in a function is a function call whose return value is directly returned by the current function, avoiding storing state on the stack
func tailSum(_ n: Int) -> Int {
func sumInternal(_ n: Int, current: Int) -> Int {
if n == 0 {
return current
} else {
return sumInternal(n - 1, current: current + n)
}
}
return sumInternal(n, current: 0)
}
print(tailSum(1000000))
Copy the code
However, you will still get an error if you try to run this code directly in your project because the Swift compiler does not optimize tail recursion in Debug mode. We can change the Run configuration from Debug to Release in scheme Settings and the code will work correctly.
conclusion
- 1, the article is read Wang Wei (onevcat). “Swifter-Swift essential Tips (fourth edition) summary
- 2. The demo address of the code in the article