Swift FAQ

Time๏ผš2022-5-8

1ใ€ Foundation


1. What is the difference between class and struct?

a. Struct will automatically generate the required constructor. The constructor with which attribute is not assigned an initial value will be generated with which attribute as the parameter. But class doesn’t. write it yourself

struct StructTest {
    var name:String
    var age:Int
}

class ClassTest {
    var name:String?
    var age:Int?
}
var structTest = StructTest(data: 66)
var classTest = ClassTest()

b. The attribute of struct can not be assigned an initial value, while the attribute of class must be assigned an initial value or set to an optional type. The following is also possible. The difference is that struct automatically generates an init method with parameters

class ScanInfo: NSObject {
    var advertisementData: [String : Any]
    var rssi: NSNumber
    init(advertisementData: [String : Any], rssi: NSNumber) {
        self.advertisementData = advertisementData
        self.rssi = rssi
    }
}

Class complies with the protocol. If there is no implementation method, it will report an error, but the structure (struct) will not. In normal projects, struct will also report an error. This is a problem with Xcode playground

c. Struct is a value type, which is a deep copy. Class is the reference type, shallow copy.

struct aStruct {
    var data: Int = 77
}
class aClass {
    var data: Int = 77
}

var orig = aStruct()
var copied = orig
orig.data = 88
print("Original data:\(orig.data), copied data:\(copied.data)")
//Original data:88, copied data:77

var ori = aClass()
let ref = ori
ori.data = 88
print("Original data:\(ori.data), copied data:\(ref.data)")
//Original data:88, copied data:88

The above code is ori After the value of data changes, the value of ref.data also changes. And orig The value of data is not followed by copied Data value changes
Ref is decorated with let, and the value of ref.data can still be changed. Instead, change the copied The value of data, copied, must be preceded by var

There can be singleton object attributes in class, but not in struct (the difference is that static is added before let, and singleton can be created in struct)

class ClassTest {
    let sharaedInstance = ClassTest()
    private init() {
        Print ("call simple interest class")
    }
}
struct StructTest {
    static let sharaedInstance = StructTest()
    private init() {
        Print ("call simple interest class")
    }
}

d. Struct cannot inherit, class can inherit
e. Nsuserdefaults: struct cannot be serialized into an nsdata object and cannot be archived

class ClassTest: NSCoding {
    required init?(coder: NSCoder) {
    }
    func encode(with coder: NSCoder) {
    }
    var name: String = "class"
    var age: Int = 0
}

Struct structtest: nscoding {// error
    //Non-class type 'StructTest' cannot conform to class protocol 'NSCoding'
    var name: String = "struct"
    var age: Int = 10
}

Because the class of the solution file must comply with the nscoding protocol, struct cannot comply with the nscoding protocol
iOS-NSUserDefaults
Swift learning record โ€“ how throws is handled in swift
IOS 13 archiving
f. When the code of your project is developed by swift and Objective-C, you will find that Swift’s struct cannot be called in Objective-C code. Because to call swift code in Objective-C, the object needs to inherit from nsobject.
g. Memory allocation: struct is allocated in the stack and class is allocated in the heap. Struct is more “lightweight” than class (struct is a sports car that runs fast, and class is an SUV that can carry more people and goods)

Knowledge extension: the difference between “heap” and “stack”, why access struct than class block?
“Heap” and “stack” are not heap and stack in data structure, but different memory space in program operation. Stack is allocated by the system in advance when the program is started, and the system does not intervene in the process of use; The heap is applied to the system only when it is used up. It needs to be returned when it is used up. The process cost of this application and return is relatively large.

Stack allocates space at compile time, while heap allocates space dynamically (runtime allocates space), so the speed of stack is fast.

Consider from two aspects:
**Allocation and release: the heap needs to call functions (malloc, free) during allocation and release. For example, when allocating, it will go to the heap space to find enough space (because multiple allocation and release will cause holes). These will take some time, but the stack does not need these.
**Access time: to access a specific unit of the heap, you need to access memory twice. The first time you have to obtain the pointer, the second time is the real data, and the stack only needs to be accessed once.


2. What are the ways of code reuse (sharing) without inheritance?

Write the method directly in the swift file, which is equivalent to a global function.
Extensions
Extension is to add new functions to an existing class, structure, enumeration type or protocol type. This includes the ability to extend types without access to the original source code (i.e., reverse modeling). The extension is similar to the classification in Objective-C.
Extensions in swift can:
Add calculated properties and calculated type properties
Define instance method and type method
Provide a new constructor
Define subscript
Define and use new nested types
Make an existing type conform to a protocol
Protocols
The protocol specifies the methods, attributes, and other things needed to implement a specific task or function. Classes, structs, or enumerations can follow the protocol and provide concrete implementations for these requirements defined by the protocol
In addition, it specifies the methods, attributes and other things needed to realize a specific task or function. Classes, structs, or enumerations can follow the protocol and provide concrete implementations for these requirements defined by the protocol


3. What are the unique methods of set?

let setA: Set<Int> = [1, 2, 3, 4, 4] //[1, 2, 3, 4]
let setB: Set<Int> = [1, 3, 5, 7, 9]
//Union set a | B
let setUnion = setA.union(setB)
print(setUnion) //[7, 4, 3, 9, 2, 1, 5]
//Take intersection a & B
let setIntersect = setA.intersection(setB) 
print(setIntersect) //[1, 3]
//Take difference set a - B
let setRevers = setA.subtracting(setB)
print(setRevers) //[4, 2]
//Take the symmetric difference set, a XOR B = a - B | B - A
let setXor = setA.symmetricDifference(setB)
print(setXor) //[9, 7, 4, 5, 2]

Use the equality operator (= =) to determine whether two sets contain the same values.
Use the issubset (of:) method to determine whether all values in one set are also included in another set.
Use the issuperset (of:) method to determine whether one set contains all the values in another set.
Use the isstrictsubset (of:) or isstricsuperset (of:) method to determine whether a set is a subset or parent of another set, and the combination of the two sets is not equal.
Use the isdisjoint (with:) method to determine whether the two sets do not contain the same value (whether there is no intersection).

let houseAnimals: Set = ["๐Ÿถ", "๐Ÿฑ"]
let farmAnimals: Set = ["๐Ÿฎ", "๐Ÿ”", "๐Ÿ‘", "๐Ÿถ", "๐Ÿฑ"]
let cityAnimals: Set = ["๐Ÿฆ", "๐Ÿญ"]
houseAnimals.isSubset(of: farmAnimals)
// true
farmAnimals.isSuperset(of: houseAnimals)
// true
farmAnimals.isDisjoint(with: cityAnimals)
// true

4. Implement a min function that returns two smaller elements

func myMain<T: Comparable>(_ a: T, _ b: T) -> T {
    return a < b ? a : b
}
print(myMain(8, 2)) // 2

5. Functions of map, filter and reduce

Swift’s standard array supports three higher-order functions: map, filter and reduce It is a method not implemented in OC.
a. The function of map is to generate a new array, traverse the original array, take out each element, make some transformations, and then put it into the new array.

[1, 2, 3].map($0 + 1) // -> [2, 3, 4]
[1, 2, 3]. Map {"\ ($0)"} // convert numeric array to string array ["1", "2", "3"]

Map: the function of the map method is to convert the array [t] into the value of type u through the closure function, and finally form the array [u]. It is defined as follows:

func map<T, U>(xs: [T], f: (T) ->U) -> [U] {
    var result: [U] = []
    for x in xs {
        result.append(f(x))
    }
    return result
}

In addition, the callback function of map accepts three parameters: current index element, index and original array

['1','2','3'].map(parseInt) //parseInt('1', 0) -> 1

b. The function of filter is also to generate a new array. When traversing the array, put the elements with the return value of true into the new array. We can use this function to delete some unnecessary elements

[1, 2, 3]. Filter {$0% 2 = = 0} // filter even // [2]
let array = [1, 2, 4, 6]
let newArray = array.filter($0 !== 6)
console.log(newArray) // [1, 2, 4]

Filter is the function of filtering. The parameter is a filtering closure used to judge whether to screen out. The value is filtered according to the bool value returned by the closure function. True to add to the result array. It is defined as follows:

func filter(includeElement: (T) -> Bool) -> [T]

Like map, the callback function of filter also accepts three parameters and is of the same use.
c. The function of reduce is to give an initial value of type u, pass each element in the array [t] into the closure function of combine, and calculate the final result value of type U.
Similar implementations of reduce are:

func reduce<A, R>(arr: [A], _ initialValue: R, combine: (R, A) -> R) -> R {
    var result = initialValue
    for i in arr {
            result = combine(result, i)
    }
    return result 
}

Reduce merge

[1, 2, 3]. Reduce ("") {$0 + "\ ($1)"} // convert to string and splice
// "123"

6. The difference between map and flatmap
Unlike map, flatmap has two overloads.

func flatMap(transform: (Self.Generator.Element) throws -> T?) -> [T]
func flatMap(transform: (Self.Generator.Element) -> S) -> [S.Generator.Element]

The closure of flatmap accepts the elements of an array, but returns a SequenceType type, that is, another array.

let numbersCompound = [[1,2,3],[4,5,6]];
var res = numbersCompound.map { $0.map{ $0 + 2 } }
// [[3, 4, 5], [6, 7, 8]]
var flatRes = numbersCompound.flatMap { $0.map{ $0 + 2 } }
// [3, 4, 5, 6, 7, 8]

So why does flatmap reduce the dimension of the array after calling it? We can peep into one or two from its source code (isn’t swift open source ~).
File location: swift / stdlib / public / core / sequencealgorithms swift. gyb

extension Sequence {
//...
public func flatMap(
@noescape transform: (${GElement}) throws -> S
) rethrows -> [S.${GElement}] {
var result: [S.${GElement}] = []
for element in self {
result.append(contentsOf: try transform(element))
}
return result
}
//...
}

This is the complete source code of flatmap. Its source code is also very simple. Call try transform (element) for each element traversed. The transform function is the closure we passed in.
Then pass the return value of the closure through result The append (contentsof:) function is added to the result array.
Let’s take another look at result What does append (contentsof:) do? Its document definition is as follows:
Append the elements of newElements to self.
Simply put, it is to add all the elements in one set to another set. Take the two-dimensional array as an example:

let numbersCompound = [[1,2,3],[4,5,6]];
var flatRes = numbersCompound.flatMap { $0.map{ $0 + 2 } }
// [3, 4, 5, 6, 7, 8]

Flatmap will first traverse the two elements [1,2,3] and [4,5,6] of the array. Because these two elements are still arrays, we can map them again:0 + 2 }ใ€‚
So, internalThe return value type of 0 + 2} call is still an array. It will return [3,4,5] and [6,7,8].
Then, flatmap receives the two returned results of the internal closure, and then calls result Append (contentsof:) adds the contents of their array to the result set, not the array itself.
Then, of course, the final result of [6] should be [4].

Another overload of flatmap

func flatMap(transform: (Self.Generator.Element) -> T?) -> [T]

From the definition, we can see that its closure receives self Generator. Element type, return a t. As we all know, in swift, the type is followed by a?, Represents the optional value. That is, the closure received in this overload returns an optional value. Further, closures can return nil.
Let’s take an example:

let optionalArray: [String?] = [``"AA"``, nil, ``"BB"``, ``"CC"``]
var optionalResult = optionalArray.flatMap{ $0 }
// ["AA", "BB", "CC"]`

In this way, there is no error, and the nil value in the original array is successfully filtered out in the return result of flatmap. If you look closely, you will find more. After calling flatmap, all elements in the array are unpacked. If you also use the print function to output the original array, you will probably get the following results:

[Optional(``"AA"``), nil, Optional(``"BB"``), Optional(``"CC"``)]

When using the print function to output the result set of flatmap, you will get the following output:

["AA", "BB", "CC"]

That is, the type of the original array is [string?] After flatmap is called, it becomes [string]. This is also a major difference between flatmap and map. If we call the same array with map, we will get the following output:

[Optional(``"AA"``), nil, Optional(``"BB"``), Optional(``"CC"``)]

This is the same as the original array. This is the difference between the two. Map function values transform elements. But it will not affect the structure of the array. Flatmap will affect the structure of the array. Before further analysis, let’s understand this for the time being.

This mechanism of flatmap only helps us to verify the data conveniently. For example, we have a group of image file names, and we can use flatmap to filter out invalid images:

var imageNames = [``"test.png"``, ``"aa.png"``, ``"icon.png"``]
imageNames.flatMap{ UIImage(named: $0) }

So how does flatmap filter out nil values? Let’s take a look at the source code:

extension Sequence {
// ...
public func flatMap(
    @noescape transform: (${GElement}) throws -> T?) rethrows -> [T] {
    var result: [T] = []
    for element in self {
        if let newElement = try transform(element) {
            result.append(newElement)
        }
    }
    return result
}
// ...
}

It is still a call to traverse all elements and apply the try transform (element) closure, but the key point is that the if let statement is used. Only those elements that are successfully unpacked will be added to the result set:

if let newElement = try transform(element) {
    result.append(newElement)
}

In this way, the effect of automatically removing the nil value we just saw is realized.


7. What is copy on write
When the value type (such as struct) is copied, the copied object, meta object and original object actually point to the same object in memory. A new object is recreated in memory when and only when the copied object is modified. give an example:

var array1: [Int] = [0, 1, 2, 3]
var array2 = array1
unsafeAddressOf(address: array1)
unsafeAddressOf(address: array2)
array2.append(4)
unsafeAddressOf(address: array2)

We can see that when the value of array2 does not change, array1 and array2 point to the same address, but when the value of array2 changes, the address pointed by array2 also changes, isn’t it strange. In swift standard library, collection types such as array, dictionary and set are implemented through a technology called copy on write.


8. How to get the function name and line number of the current code

Print (#line) // used to get the current line number
Print (#file) // used to get the current file name
Print (#column) // used to get the current column number
Print (#function) // used to get the current function name

9. How to declare a protocol that can only be used by class conform

When declaring the protocol, add a class. In the inheritance list of the protocol, add the class keyword to restrict that the protocol can only be followed by the class type, while the structure or enumeration cannot follow the protocol. The class keyword must appear first in the inheritance list of the agreement, before other inherited agreements:

protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
    //Here is the definition of class type specific protocol
}

10. Guard usage scenario
Guard is similar to if. The difference is that guard always has an else statement. If the expression is false or the value binding fails, the else statement will be executed, and the function call must be stopped in the else statement
Use guard to express the intention of “early exit”. There are the following usage scenarios:
When verifying entry conditions
Early exit on successful path
When unpacking optional values (flatten if let.. else pyramid)
Return and throw
In logs, crashes, and assertions
The following are the scenarios to avoid:
Don’t use guard: instead of trivial if Else statement
Don’t use guard: as the opposite of if
Don’t: put complex code in the else statement of guard
for example

guard 1 + 1 == 2 else {
    fatalError("something wrong")
}

The common usage scenario is to verify whether the user has entered the user name and password when the user logs in

guard let userName = self.userNameTextField.text,
  let password = self.passwordTextField.text else {
    return
}

11. Defer usage scenario

It’s very simple. In a word, the code in defer block will be executed before the function return, no matter which branch the function returns from, throw, or naturally go to the last line.
This keyword is similar to that in Javatry-catch-finallyFinally, no matter which branch try catch takes, it will execute before the function return. And it is more powerful than Java’s finally is that it can be independent oftry catchSo it can also be a small helper in sorting out the function process. Whatever processing needs to be done before the function return can be put into this block to make the code look cleaner~

//Pseudo code
var fridgeIsOpen = false
let fridgeContent = ["milk", "eggs", "leftovers"]
func fridgeContains(_ food: String) -> Bool {
    fridgeIsOpen = true
    defer {
        fridgeIsOpen = false
    }
    let result = fridgeContent.contains(food)
    return result
}
fridgeContains("banana")
print(fridgeIsOpen)

In this example, the order of execution is: fridgeisopen = true first, then the normal process of the function body, and finally fridgeisopen = false before return.
Several simple usage scenarios

Try catch structure
The most typical scenario, I think, is also the main reason for the birth of the keyword defer:

func foo() {
    defer {
        print("finally")
    }
    do {
        throw NSError()
        print("impossible")
    } catch {
        print("handle error")
    }
}

No matter whether the do block throws error, catches or throws out, it is guaranteed to execute defer before the whole function returns. In this example, first print out “handle error” and then print out “finally”.
Defer can also be written in do block:

do {
    defer {
       print("finally")
    }
    throw NSError()
    print("impossible")
} catch {
    print("handle error")
}

Then its execution order will be before the catch block, that is, print out “finally” and then print out “handle error”.

Clean up and recycle resources
Similar to the example given in swift document, defer is a very suitable use scenario for cleaning up. File operation is a good example:
Close file

func foo() {
    let fileDescriptor = open(url.path, O_EVTONLY)
    defer {
        close(fileDescriptor)
    }
// use fileDescriptor...
}

In this way, we are not afraid of which branch forgets to write, or throw an error in the middle, so that the filedescriptor cannot be closed normally. There are also some similar scenes:
Dealloc manually allocated space

func foo() {
   let valuePointer = UnsafeMutablePointer<T>.allocate(capacity: 1)
   defer {
       valuePointer.deallocate(capacity: 1)
   }
 // use pointer...
}

Add / unlock: the following is a way of writing synchronized block similar to Objective-C in swift. Any nsobject can be used as lock

func foo() {
   objc_sync_enter(lock)
   defer { 
       objc_sync_exit(lock)
   }
 // do something...
}

Methods called in pairs like this can be put together with defer to make them clear at a glance.

Adjust completion block
This is a scene that makes me feel like “if I knew defer at that time”, that is, sometimes there are many branches of a function, and a small branch may forget to call the completion block before returning, resulting in a bug that is not easy to find. With defer, you don’t have to worry about this problem:

func foo(completion: () -> Void) {
    defer {
        self.isLoading = false
        completion()
    }
    guard error == nil else { return } 
// handle success
}

Sometimes completion needs to pass different parameters according to the situation. At this time, defer is not easy to use. However, if the completion block is saved, we can still use it to ensure that it can be released after execution:

func foo() {
    defer {
        self.completion = nil
    }
    if (succeed) {
        self.completion(.success(result))
    } else {
    self.completion(.error(error))
 }
}

Super method
Sometimes overriding a method is mainly used to make preparations before the super method, such as prepare (forcollectionviewupdates:) of uicollectionviewlayout. Then we can put the part calling super in the defer:

func override foo() {
    defer {
        super.foo()
    }
// some preparation before super.foo()...
}

Multiple defers
A scope can have multiple defers, and the order is executed backwards like a stack: each defer encountered is like pressing into a stack. When the scope ends, those who enter the stack later will execute first. As shown in the following code, it will print in the order of 1, 2, 3, 4, 5 and 6.

func foo() {
    print("1")
    defer {
        print("6")
    }
    print("2")
    defer {
        print("5")
    }
    print("3")
    defer {
        print("4")
    }
}

Some details
Any scope can have a defer
Although most usage scenarios are in functions, in theory, defers can be written between any {}. For example, an ordinary cycle:

var sumOfOdd = 0
for i in 0...10 {
 defer {
 print("Look! It's \(i)")
 }
 if i % 2 == 0 {
 continue
 }
 sumOfOdd += i
}

Neither continue nor break will hinder the execution of defer. Defer can even be written in an unexplained closure:

{
 defer { print("bye!") }
 print("hello!")
}

That’s it. It doesn’t make any sense
It cannot be triggered until defer is executed
Suppose there is such a question: can the defer in a scope be guaranteed to be executed? For example, the answer to this example is

func foo() throws {
 do {
 throw NSError()
 print("impossible")
 }
 defer {
 print("finally")
 }
}
try?foo()

Will not execute defer, will not print anything. This story tells us that at least the defer line must be executed before it can be triggered later. In the same way, it is also impossible to return in advance:

func foo() {
 guard false else { return }
 defer {
 print("finally")
 }
}

12. Relationship and difference between string and nsstring
a. String type is a value type (no longer an object type). When a string is assigned to a constant or variable or passed in a function / method, the value will be copied. In any case, a new copy of the existing string value will be created, and the new copy will be passed or assigned.
b. String can support character traversal, but nsstring does not.
c. String is a structure with higher performance; Nsstring is an nsobject object, and its performance is relatively poor.
d. Now there are some functions that are inconvenient to implement with string: take the string of string / judge the inclusion / regular expression.
Nsstring and string can be converted at will

let someString = "123"
let someNSString = NSString(string: "n123")
let strintToNSString = someString as NSString
let nsstringToString = someNSString as String 

13. How to get the length of a string
Do not consider coding, just want to know the number of characters, use characters count

"hello".characters.count // 5
"Hello" characters. count // 2
"ใ“ใ‚“ใซใกใฏ".characters.count // 5

If you want to know how many bytes are occupied under a certain code, you can use

"hello".lengthOfBytes(using: .ascii) // 5
"hello".lengthOfBytes(using: .unicode) // 10
"Hello" lengthOfBytes(using: .unicode) // 4
"Hello" lengthOfBytes(using: .utf8) // 6
"ใ“ใ‚“ใซใกใฏ".lengthOfBytes(using: .unicode) // 10
"ใ“ใ‚“ใซใกใฏ".lengthOfBytes(using: .utf8) // 15

14. How to intercept a string of string
It is convenient to convert to nsstring and then intercept it. The returned object type is string, which does not need to be converted again. It is very convenient


15. Usage and function of throws and rethrows

In swiftthrowandrethrowsKeywords are used for error handling. They are all used in functions. They can be simply understood as throw to handle possible exceptions of functions or methods (for example, do catch). Rethrows only passes throw and is not specific to functions or methods. The following is a detailed explanation:
throw
The throws keyword is first used in the function declaration and placed in front of the return type, such as the function signature of map in the standard library:

func map<T>(_ transform: (Int) throws -> T) rethrows -> [T]

Then, in the function, if there is a possible exception, you can throw the exception. Usually, an enumeration can be used to represent a class of exceptions. This enumeration can implement the error protocol, such as:

enum TestError: Error {
    case errorOne(Int)
    case errorTwo(String)
    case errorThree
    case errorUnknown
}

func testThrow(num: Int) throws -> String {
    switch num {
    case 1:
        throw TestError.errorOne(1)
    case 2:
        throw TestError.errorTwo("2")
    case 3:
        throw TestError.errorThree
    case 4:
        throw TestError.errorUnknown
    default:
        return "No Error"
    }
}

In this way, through the possible exceptions in the throw function, let the function feedback errors, force the programmer calling this function to deal with all possible errors, and reduce the maintenance cost.
The following uses do catch to handle possible exceptions:

do {
    let testResult: String = try testThrow(num: 2)
    print(testResult)  // Will no print
} catch TestError.errorOne(let num) {
    print(num)  // 1
} catch TestError.errorTwo(let str) {
    print(str)  // 2
} catch TestError.errorThree {
    print(TestError.errorThree)  // errorThree
} catch let err {
    print(err)  // errorUnknown
}
//  2

rethrows
Rethrows keyword only serves to pass exceptions. In a function or method, you can call a function that will throw, and then you can pass possible exceptions through rethrows. Follow the above example

func testRethrow(testThrowCall: (Int) throws -> String, num: Int) rethrows -> String {
    try testThrowCall(num)
}

Observe the function declaration. In fact, there is a throw function as a parameter, and then add the keyword rethrows in front of the return type. Call the throw function directly inside the function to pass possible exceptions. The function or method dealing with rethrows is the same as the function or method dealing with throw:

do {
    let testResult: String = try testRethrow(testThrowCall: testThrow, num: 4)
    print(testResult)  // Will no print
} catch TestError.errorOne(let num) {
    print(num)  // 1
} catch TestError.errorTwo(let str) {
    print(str)  // 2
} catch TestError.errorThree {
    print(TestError.errorThree)  // errorThree
} catch let err {
    print(err)  // errorUnknown
}
//  errorUnknown

summary
Throw throws an exception in a function or method so that the caller must explicitly handle possible exceptions. Rethrows itself does not throw or handle exceptions. It only plays the role of passing exceptions. Finally, go back to the map function in the standard library. For example:

enum NegativeError: Error {
    case negative
}

let nums = [-1, 1, 2, 3, 4]

do {
    let strNums = try nums.map { (num) throws -> String in
        if num >= 0 {
            return String(num)
        } else {
            throw NegativeError.negative
        }
    }
    print(strNums)  // Will no print
} catch let err {
    print(err)
}
// negative

Here, throw is used in the function parameter of map, and an exception is thrown when the element is less than 0


16ใ€ try๏ผŸ And try! What do you mean?
These two are used to handle functions that can throw exceptions. You can use these two keywords without writing do catch
try? Is used to decorate a function that may throw an error. The error will be converted to an optional value when try? + When a function or method statement is executed, if the function or method throws an error, the program will not crash and return a nil. If no error is thrown, an optional value will be returned
try! The error delivery chain is ignored and “do or die” is declared. If the called function or method does not throw an exception, then everything is fine; But if you throw an exception and don’t say a word, you’ll die


17. Role of associatedtype


18. When to use final
Final is used to restrict inheritance and rewriting If you just need to add a final before a certain attribute.
If you need to restrict that the whole class cannot be inherited, you can add a final before the class name


19. Open, public and other modifiers
open
The class decorated with open can inherit in this block (SDK) or other modules that introduce this module. If it is a modified attribute, it can be overridden by this module or the module that introduces this block (SDK)
public
After a class is decorated with public (or a lower level constraint (such as private), it can only be inherited in this module (SDK). If public is a modified attribute, it can only be overridden by a subclass in this module (SDK)
internal
A long a, B import a, a can access a, B can not access A. for example, you write an SDK. There are some things in the SDK that you don’t want the outside world to access. At this time, you need the keyword internal (I found that if there is no definition when importing the third-party framework, the SDK is internal by default)
fileprivate
This modification is very similar to the meaning of the name. File private refers to the private relationship between files, that is, it can be accessed in the same source file, but not in other files. A long to file a, a not long to file B, a can be accessed in file a, and a can not be accessed in file B
private
This modification is more restrictive than fileprivate. Private acts on a class, that is, for class A, if attribute a is private, Then you can’t access other places except a (both fileprivate and private are restrictive constraints on a class. The applicable scenario of fileprivate can be the extension under a file. If the variable in your class is defined as private, then this variable cannot be accessed by your class in the expansion of this class’s file, so it needs to be defined as fileprivate)
Finally, the guiding principle of access levels: you cannot define a higher-level modification in a low-level modification, such as public, which cannot modify attributes in a private class


20. Declare an alias of a closure with only one parameter and no return value

typealias SomeClosuerType = (String) -> (Void)
let someClosuer: SomeClosuerType = { (name: String) in
    print("hello,", name)
}
someClosuer("world")
// hello, world

21. What is the difference between the keyword static and class when defining static methods

Static defined methods cannot be inherited by subclasses, but class can

class AnotherClass {
    static func staticMethod(){}
    class func classMethod(){}
}
class ChildOfAnotherClass: AnotherClass {
    override class func classMethod(){}
    //override static func staticMethod(){}// error
}

22. Self and self in swift

When you use self incorrectly, the compiler will prompt you like this

'Self' is only available in a protocol or as the result of a method in a class

Separated words mean two things
a. Self can be used to restrict related types in the protocol
b. Self can be used in a class to act as the return value type of a method
For the first case, you can refer to the examples in the book

protocol Copyable {
    func copy() -> Self
    func clamp(intervalToClamp: Self) -> Self
}

Both methods in this protocol use self to restrict types
The second case can refer to the following example

class A: Copyable {
    var num = 1
    required init() {
    }
    
    func copy() -> Self {
        let types = type(of: self)
        print(types)
        let result = types.init()
        result.num = num
        return result
    }
    
    func clamp(intervalToClamp: A) -> Self {
        let result = type(of: self).init()
        result.num = num
        return result
    }
    
    class func calssFunc() -> Self {
        let type = self
        print(type)
        let result = type.init()
        return result
    }
}

class B: A {
    func clamp(intervalToClamp: B) -> Self {
        let result = type(of: self).init()
        result.num = num
        return result
    }
}

let type = A.self
type.calssFunc()

let typeB = B.self
typeB.calssFunc()

let objectA = A()
objectA.num = 100

let newObjectA = objectA.copy()
objectA.num = 1

let objectB = B()
objectB.num = 100
let newB = objectB.copy()

In this example, there are two classes a and B. A implements two methods in the protocol and contains a class method. B is a subclass of a, which also implements the methods of the protocol.
You can see that for a
In the instance method of a, self represents the current instance and uses type (of: self) to obtain the type of the current object,
In the class method of a, self represents the type of the current class, while self can only be used to represent the type of return value.
Comparing the protocol methods implemented by a and B, we can see that the parameter types received by the methods in the protocol must be changed to the types of their respective classes, otherwise the error at the beginning of the article will be reported.

To sum up, it can be seen that for self, it only represents a specific type and can only be used in the protocol or as the return value type of the method of a class. Self refers to the current instance in the instance method and the current class in the class method.


22. Function of dynamic

Because swift is a static language, there is no dynamic mechanism for sending messages in Objective-C. the function of dynamic is to make swift code have the dynamic mechanism in Objective-C. the common place is KVO. If you want to monitor an attribute, you must mark it as dynamic

Mark the attribute with dynamic keyword to enable the dynamic forwarding function of objc;
Dynamic is only used for classes, not structures and enumerations, because they have no inheritance mechanism, and objc’s dynamic forwarding is to realize forwarding according to the inheritance relationship.


23. When to use @ objc

@Objc is used to call swift code normally when Objective-C and swift are mixed It can be used to modify classes, protocols, methods and properties.
The common place is that in defining the delegate protocol, some methods in the protocol will be declared as optional methods, and @ objc is required

@objc protocol OptionalProtocol {
    @objc optional func optionalFunc()
    func normalFunc()
}
class OptionProtocolClass: OptionalProtocol {
    func normalFunc() {
    }
}
let someOptionalDelegate: OptionalProtocol = OptionProtocolClass()
someOptionalDelegate.optionalFunc?()

25. What is the implementation of optional

Optional is a generic enumeration
Roughly defined as follows:

enum Optional<Wrapped> {
  case none
  case some(Wrapped)
}

In addition to using let somevalue: int= In addition to nil, let optional1: optional < int > = nil can also be used to define


26. How to customize subscript acquisition
Just implement subscript, such as

extension AnyList {
    subscript(index: Int) -> T{
        return self.list[index]
    }
    subscript(indexString: String) -> T?{
        guard let index = Int(indexString) else {
            return nil
        }
        return self.list[index]
    }
}

In addition to numbers, other types of indexes are also possible


27ใ€?? Role of

?? Is a null and void operator. The default value of the optional value. When the optional value is nil, the following value will be returned as
let someValue = optional1 ?? 0
Like a?? b. The optional type A will be judged as null. If a contains a value, it will be unsealed, otherwise a default value B will be returned.
Expression a must be of type optional. The type of default value B must be consistent with the type of stored value a


28. Role of lazy

Lazy loading, when the attribute is to be used, the initialization is completed

lazy var names: NSArray = {
    let names = NSArray()
    Print ("output only on first access")
    return names
}()

Analysis: compared with the implementation in Objective-C, today’s lazy is much simpler and more intuitive. In addition to the above methods that define closure calls directly after the attribute, you can also use instance methods (func) and class methods (class func) to add necessary logic for lazy attribute initialization.

In order to simplify, if we don’t need to do any extra work, we can also write an assignment statement directly for the lazy attribute:

lazy var str: String = "Hello"

Usage scenario
Delayed loading is mainly used in the following two scenarios:

a. The initial value of the attribute depends on other attribute values. The value of the attribute can be obtained only after other attribute values have values.
b. The initial value of an attribute requires a lot of computation.


29. One type represents an option. It can indicate that several options are selected at the same time (similar to uiviewanimation options). What type is used to represent it

It needs to implement self optionset, which is generally implemented by struct Because optionset requires an init (rawvalue:) constructor that cannot fail, enumeration cannot do this (the original value constructor of enumeration can fail, and some combined values cannot be represented by an enumeration value)

struct SomeOption: OptionSet {
    let rawValue: Int
    static let option1 = SomeOption(rawValue: 1 << 0)
    static let option2 =  SomeOption(rawValue:1 << 1)
    static let option3 =  SomeOption(rawValue:1 << 2)
}
let options: SomeOption = [.option1, .option2]

30. Function of inout

The value type can be passed by reference. For example, sometimes it is necessary to change the value of variables outside the function through a function, for example:

var value = 50
Print (value) // at this time, the value is 50

func increment(inout value: Int, length: Int = 10) {
    value += length
}
increment(&value)
Print (value) // at this time, the value is 60, and the value of the external variable value of the function is successfully changed

31. What should I do if I want to be compatible with nserror

In fact, direct conversion is OK, such as someerror Someerror as nserror, but there is no error code, description, etc. if you want to have these things like nserror, you only need to implement localizedererror and customnserror protocols. Some methods have default implementations and can be omitted, such as:

enum SomeError: Error, LocalizedError, CustomNSError {
    case error1, error2
    public var errorDescription: String? {
        switch self {
        case .error1:
            return "error description error1"
        case .error2:
            return "error description error2"
        }
    }
    var errorCode: Int {
        switch self {
        case .error1:
            return 1
        case .error2:
            return 2
        }
    }
    public static var errorDomain: String {
        return "error domain SomeError"
    }
    public var errorUserInfo: [String : Any] {
        switch self {
        case .error1:
            return ["info": "error1"]
        case .error2:
            return ["info": "error2"]
        }
    }
}
print(SomeError.error1 as NSError)
// Error Domain=error domain SomeError Code=1 "error description error1" UserInfo={info=error1}

32. What syntax does the following code use

[1, 2, 3].map{ $0 * 2 }

[1, 2, 3] uses the expressiblebyarrayliteral protocol implemented by array to receive the literal value of the array
When map {XXX} uses closure as the last parameter, it can be written directly after the call. Moreover, if it is the only parameter, the parentheses can also be omitted
Closures do not declare function parameters, return value types and quantities, and rely on the automatic inference of closure types
When there is only one sentence in a closure, the result of this sentence is automatically taken as the return value


33. What is a higher-order function

If a function can take a function as a parameter or return value, it is called a high-order function, such as map, reduce and filter


34. How to solve the reference cycle
a. When converting to value type, only classes will have reference loops, so if you can use no classes, you can dereference loops
b. Delegate uses the weak attribute
c. In closures, use weak or unowned to modify objects that may have circular references


35. Will the following code crash and tell the reason

var mutableArray = [1,2,3]
for _ in mutableArray {
    mutableArray.removeLast()
}

var mutableArray = [1,2,3]
for _ in mutableArray {
mutableArray.removeLast()
}


36. How to add an extension method to the type whose element in the collection is a string

Use the where clause to restrict the element to string

extension Array where Element == String {
    var isStringElement:Bool {
        return true
    }
}
["1", "2"].isStringElement
//[1, 2].isStringElement// error

2ใ€ Advanced