Before and after the Spring Festival have been busy doing promotion, the Swift knowledge before the next record, read on the Internet at the same time I also added some interesting interview questions come in, some topic I changed slightly, the feeling will be more interesting and of course as usual all code when finishing to run again confirm ~ update frequency and subsequent articles to speed up, In addition to some articles on iOS development, there will also be some articles on cross-platform, front-end, algorithms, etc., as well as a review of your own technology stack

The statement


  • Let and var

    Let is used to declare constants and var is used to declare variables. Let is used to declare variables in JS, while const is used to declare constants.

  • Type annotation

    Constants/variables can be declared with type annotations to indicate the storage type, for example

    var message: String
    Copy the code

    If no description is displayed, Swift automatically deduces the type based on the assignment at declaration time. In general, it is not necessary to mark the type, but I have encountered some cases that require a large number of declarations, because Xcode does not mark the type, directly caused the Mac to crash, later added to the good.

  • named

    Constants and variable names cannot contain mathematical symbols, arrows, reserved (or illegal) Unicode code points, lines, and tabs. You can’t start with a number either, but you can include numbers elsewhere in constants and variable names, and you can use any character you like, for example

    let 🐢 = ""
    letThe snail jun= ""
    Copy the code

    But not recommended, too much SAO operation is easy to flash waist, Camel-Case is still a better choice.

    In addition, if you want to use Swift to reserve the keyword, you can use ** backquotes’ ** wrapped around it, for example

    enum BlogStyle {
        case `default`
        case colors
    }
    Copy the code

Tuples


Swift supports combining multiple values into a single compound value, called a tuple. Values within a tuple can be of any type and do not need to be of the same type, for example

let size = (width: 10, height: 10)
print("\(size.0)")
print("\(size.width)")

// You can also leave the element unnamed
let size = (10.10)
Copy the code

Tuples are useful when a function needs to return multiple values. However, it is not suitable for complex data expression and should be used only for temporary needs.

Before a colleague through a tuple returns multiple values, and not on the element name, and then is used in many places the tuple, lead to the back of the colleagues take over when unable to understand the meaning of data quickly, and when the need to change the return data, must pass through the code logic to find where the tuple, it took a lot of time.Copy the code

Optional type


Optional types are another new feature that Swift differentiates from ObjC. They handle the possibility of missing values by following the type annotation with a? To indicate that this is an optional type, for example

// If the optional type is not explicitly assigned, it defaults to nil
var message: String?
// Either has an exact value
message = "Hello"
// Either no value, nil
message = nil
Copy the code

Optional is a generic enumeration.

public enum Optional<Wrapped> : ExpressibleByNilLiteral {

    /// The absence of a value.
    ///
    /// In code, the absence of a value is typically written using the `nil`
    /// literal rather than the explicit `.none` enumeration case.
    case none

    /// The presence of a value, stored as `Wrapped`.
    case some(Wrapped)
  
  	.
}
Copy the code

So the initialization above could also be written like this:

str = .none
Copy the code
Nil for Swift is not the same as nil for ObjC. In ObjC, nil is a pointer to an object that does not exist. In Swift, nil is not a pointer -- it is a definite value used to indicate that a value is missing. Any type of optional state can be set to nil, not just object types.Copy the code
  • Optional binding

    However, during daily development, we often need to determine whether an optional type has a value and get that value, which is where we use the optional binding

    if let message = message {
      .
    }
    
    guard let message = message else {
      return
    }
    .
    Copy the code

    The code above can be interpreted as: If the optional String returned by Message contains a value, create a new constant called message and assign the optional contained value to it.

  • Implicitly resolves optional types

    Although we can get values of optional types using optional bindings, it would be bloated to use optional bindings for values that are nil at initialization and no longer nil.

    Implicit resolution of optional types Supports optional types followed by! To get valid values directly, for example

    let message: String = message!
    Copy the code

    If you try to value an optional type when there is no value implicitly resolved, a runtime error will be triggered. So Apple recommends not using implicit resolution of optional types if a variable could later become nil, but using plain optional types.

    But the fact is that it's very dangerous to artificially determine if you can use implicit parsing for alternative types, especially in teamwork, because if something goes wrong, it's going to crash, so we don't allow implicit parsing for alternative types on our team.Copy the code

The operator


  • Null conjunction operator

    The null conjunction operator (a?? B) The optional type a is nullated, unmarshalling if a contains a value, and returning a default value b otherwise. The expression a must be of type Optional. The type of the default value B must be the same as the type of the value a stores.

    let message: String = message ?? "Hello"
    // Is actually equivalent to the ternary operator
    message ! = nil ? message! : "Hello"
    Copy the code
  • Interval operator

    Swift provides several interval operators that are convenient for expressing the value of an interval, I like itπŸ˜€

    let array = [1.2.3.4.5]
    // The closed interval operator, which intercepts an array element with subscripts 0 to 2
    array[0.2]
    // A half-open interval operator that intercepts an array element with subscripts 0 to 1
    array[0..<2]
    // The one-sided interval operator that intercepts an array element from the beginning to subscript 2
    array[.2]
    // A single-sided interval operator that intercepts array elements from subscript 2 to the end
    array[2.]
    Copy the code

    Besides… Or.. < to concatenate two strings. A common usage scenario is to check if a character is a valid character.

    // Determine if uppercase letters are included, and print
    let str = "Hello"
    let test = "A"."Z"
    for c in str {
        if test.contains(String(c)) {
            print("\(c)It's capital letters.")}}// Print H in capital letter
    Copy the code
  • Identity operator

    Sometimes we need to determine whether two constants or variables refer to the same class instance. To do this, Swift has two identity operators built in:

    • Is equivalent to (= = =)
    • Not equivalent to (! = =)

    Use these operators to check whether two constants or variables refer to the same instance.

  • + + and –

    Swift doesn’t support this, and ObjC is used a lot.

closure


Closures are self-contained blocks of function code that can be passed and used in code. Closures in Swift are similar to code blocks in C and ObjC.

Swift’s closure expressions have a concise style and encourage syntactic optimizations in common scenarios. The main optimizations are as follows:

– Use context to infer parameter and return value types

– Implicitly returns a single-expression closure, that is, a single-expression closure can omit the return keyword

– Parameter name abbreviation

– Trailing closure syntax

Closure expression syntax has the following general form:

{ (parameters) -> returnType in
    statements
}
Copy the code
  • Following the closure

    When the last argument to a function is a closure, you can use trailing closures to improve the readability of the function. When using a trailing closure, you don’t have to write its argument tag:

    func test(closure: () - >Void) {
        .
    }
    
    // Do not use trailing closures
    test(closure: {
        .
    })
    
    // Use trailing closures
    test() {
        .
    }
    Copy the code
  • Escape a closure

    When a closure is passed to a function as an argument, but may be used after the function returns, we say the closure escapes from the function. For example,

    var completions: [() -> Void] = []
    func testClosure(completion: () - >Void) {
        completions.append(completion)
    }
    Copy the code

    The compiler will report an error telling you that this is a escaping closure. To indicate that this closure allows “escaping” from this function, @escaping is preceded by the argument name.

    var completions: [() -> Void] = []
    func testEscapingClosure(completion: @escaping() - >Void) {
        completions.append(completion)
    }
    Copy the code

    Also, labeling a closure as @escaping means that you must refer to self explicitly in closures, whereas non-escaping closures do not. This is a reminder that you might accidentally catch self, so watch out for circular references.

  • Automatic closure

    An automatic closure is an automatically created closure that wraps expressions passed to functions as arguments. This closure takes no arguments, allowing you to omit the closure’s curly braces and replace the explicit closure with a plain expression.

    And automatic closures allow you to delay evaluation because the code snippet will not be executed until you call the closure. To mark a closure as an automatic closure, use @Autoclosure.

    // Automatic closures are not used. Curly braces are required to indicate that this parameter is a closure
    func test(closure: () - >Bool) {
    }
    test(closure: { 1 < 2})// Use automatic closures, just pass the expression
    func test(closure: @autoclosure() - >String) {
    }
    test(customer: 1 < 2)
    Copy the code

Recursive enumeration


There might be a scenario where you define a Food enumeration that contains a number of foods and also supports the ability to mix new foods based on those foods in pairs

enum Food {
    case beef
    case potato
    case mix(Food.Food)}Copy the code

The compiler will prompt you to leave Recursive enum ‘Food’ is not marked ‘indirect’ because the enumeration member has a Recursive call. So we need to prefix an enumeration member with indirect to indicate that the member is recursive.

// Mark the entire enumeration as recursive
indirect enum Food {
    case beef
    case potato
    case mix(Food.Food)}// Mark only recursively existing enumerators
enum Food {
    case beef
    case potato
    indirect case mix(Food.Food)}Copy the code

The second is preferred because the compiler inserts a layer of indirection when using recursive enumerations. Marking only enumerators reduces unnecessary overhead.

attribute


  • Storage properties

    Simply put, a stored property is a constant or variable stored in an instance of a particular class or structure. A store property can be a variable store property (defined with the keyword var) or a constant store property (defined with the keyword let).

    struct Person {
        var name: String
        var height: CGFloat
    }
    Copy the code

    We can also use Lazy to indicate that this property is a Lazy storage property, similar to what ObjC calls Lazy loading.

    lazy var fileName: String = "data.txt"
    Copy the code
    If a property marked lazy is accessed by multiple threads at the same time without initialization, there is no guarantee that the property will be initialized only once, meaning that it is not thread-safe.Copy the code
  • Calculate attribute

    In addition to storing properties, classes, structures, and enumerations can define computed properties. Instead of storing values directly, computed properties provide a getter and an optional setter to indirectly obtain and set the values of other properties or variables.

    struct Rect {
        var origin = CGPoint.zero
        var size = CGSize.zero
        var center: CGPoint {
            get {
                let centerX = origin.x + (size.width / 2)
                let centerY = origin.y + (size.height / 2)
                return Point(x: centerX, y: centerY)
            }
            set {
                origin.x = newValue.x - (size.width / 2)
                origin.y = newValue.y - (size.height / 2)}}}Copy the code

    If we only want it to be readable and not writable, the setter method doesn’t provide it

    var center: CGPoint {
        let centerX = origin.x + (size.width / 2)
        let centerY = origin.y + (size.height / 2)
        return Point(x: centerX, y: centerY)
    }
    Copy the code
  • viewer

    Swift provides a very convenient way to observe property changes. The property observer is called every time a property value is set, even if the new value is the same as the current value.

    var origin: CGPoint {
        willSet {
            print("\(newValue)")}didSet {
            print("\(oldValue)")}}Copy the code
  • Calling sequence

    Call the set method of Number to see the order of work

    let b = B()
    b.number = 0
    
    / / output
    // get
    // willSet
    // set
    // didSet
    Copy the code

    Why is there a get?

    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.

unowned


You can declare an attribute or variable with the key unowned ‘in front to indicate that it is an undirected reference. With an ownerless reference, you must ensure that the reference always points to an undestroyed instance.

Like weak, unowned does not hold referenced instances firmly. It is also used to resolve scenarios where there may be circular references and objects of non-optional types.

For example, in a design like this, a customer may or may not have a credit card, but a credit card is always associated with a customer.

class Customer {
    let name: String
    var card: CreditCard?
}

class CreditCard {
    let number: UInt64
    unowned let customer: Customer // Cannot use weak because there is always a value
}
Copy the code

Is and the as


  • is

Is is the functional equivalent of ObjC’s isKindOfClass and can check whether an object belongs to a certain type or its subtypes. The main difference is that it can be used not only for class types, but also for other types of Swift such as struct or enum types.

class ClassA {}class ClassB: ClassA {}let obj: AnyObject = ClassB(a)if (obj is ClassA) {
    print("Belongs to ClassA")}if (obj is ClassB) {
    print("Belongs to the ClassB")}Copy the code
  • as

A constant or variable of a type may actually belong to a subclass behind the scenes. When you determine that this is the case, you can try going down to its subtype using the type conversion operator (as? Or as! .

class Media {}
class Movie: Media {}
class Song: Media {}

for item in medias {
    if let movie = item as? Movie { 
        print("It's Movie")}else if let song = item as? Song {
        print("It's Song")}}Copy the code

as? Returns an optional value of the type you are trying to cast down to. as! Combine the attempted downward transformation and forced unpacking of the transformation result into one operation. Use AS! Only if you can be sure that the transition down will be successful. .

As with implicit resolution of optional types, as! Also has a high risk of collapse, we generally do not allow use.Copy the code

Extend the subscript


Swift supports adding new subscripts to existing types through Extension, for example

extension Int {
    subscript(digitIndex: Int) -> Int {
        var decimalBase = 1
        for _ in 0..<digitIndex {
            decimalBase * = 10
        }
        return (self / decimalBase) % 10}}746381295[0]
/ / return 5
746381295[1]
/ / return
746381295[2]
/ / return 2
746381295[8]
/ / return 7
Copy the code

If the Int does not have enough bits, that is, the subscript is out of bounds, then the subscript implementation returns 0 as if it were auto-filling 0 to the left of the number:

746381295[9]
// Return 0, which is equivalent to:
0746381295[9]
Copy the code

mutating


Methods in structs and enumerated types that modify self or its attributes must mark the instance method as mutating otherwise they cannot change their variables in the method.

struct MyCar {
    var color = UIColor.blue
    mutating func changeColor(a) {
        color = UIColor.red
    }
}
Copy the code

Since Swift’s protocol can be implemented not only with class types, but also with structs and enums, we need to consider using mutating to modify methods when writing interfaces to others.

When implementing a mutating method in a protocol, do not write the mutating keyword if it is of a class type. For structures and enumerations, the mutating keyword must be written.Copy the code

Agreement synthesis


Sometimes you need to comply with multiple protocols at the same time. For example, if a function wants a parameter to comply with both ProtocolA and ProtocolB, you can combine the ProtocolA and ProtocolB formats, which is called protocol composition.

func testComposition(protocols: ProtocolA & ProtocolB){}Copy the code

The selector and @ objc

Such code is often encountered in development

btn.addTarget(self, action: #selector(onClick(_:)), for: .touchUpInside)

@objc func onClick(_ sender: UIButton){}Copy the code

Why use @objc?

Because #selector in Swift takes a selector from the code exposed to ObjC, it’s still the concept of ObjC Runtime, If your selector corresponds to a method that is only visible in Swift (that is, it’s a private method in Swift), you’ll get an unrecognized selector error when calling that selector.

inout


Sometimes we want to modify the input value directly inside the method. In this case, we can use inout to modify the parameter:

func addOne(_ variable: inout Int) {
    variable + = 1
}
Copy the code

Since the value is changed inside the function, there is no need to return it. The call is also changed to the corresponding form, preceded by an ampersand:

incrementor(&luckyNumber)
Copy the code

The singleton


Singletons in ObjC are generally written as:

@implementation MyManager
+ (id)sharedManager {
    static MyManager *staticInstance = nil;
    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{
        staticInstance = [[self alloc] init];
    });
    return staticInstance;
}
@end
Copy the code

But in Swift it becomes very concise:

static let sharedInstance = MyManager(a)Copy the code

The random number


It is often possible to obtain random numbers such as within 100:

let randomNum: Int = arc4random() % 100
Copy the code

Because arc4RANDOM () returns UInt32, type conversion is required.

let randomNum: Int = Int(arc4random()) % 100
Copy the code

It crashed on some models when tested.

This is because Int equals Int32 on 32-bit machines (iPhone5 and below), Int64 on 64-bit machines, and behaves the same as NSInteger in ObjC, whereas arc4RANDOM () always returns the UInt32, so on 32-bit machines it might overstep the bounds and crash.

The quickest way is to cast after mod:

let randomNum: Int = Int(arc4random() % 100)
Copy the code

Variable parameter function


If you want a variable argument function, you simply declare the argument with the type… That’s it.

func sum(input: Int...). -> Int {
    return input.reduce(0, combine: +)}print(sum(1.2.3.4.5))
Copy the code

The optional protocol


The Swift Protocol itself does not allow options, requiring all methods to be implemented. However, since Swift and ObjC can be mixed, Swift supports using the optional keyword in protocol as a prefix to define optional requirements for easy communication with ObjC, and both protocols and optional requirements must carry the @objc attribute.

@objc protocol CounterDataSource {
    @objc optional func incrementForCount(count: Int) -> Int
    @objc optional var fixedIncrement: Int { get}}Copy the code

But the protocol that marks the @objc property can only be followed by classes that inherit from objc or @objc. Other classes, structs and enumerations cannot follow this protocol. This is a significant limitation for the Swift Protocol.

Since Protocol supports extensibility, we can declare a protocol and then use extension to give some of the default implementations of methods that are optional in the actual class.

protocol CounterDataSource {
    func incrementForCount(count: Int) -> Int
    var fixedIncrement: Int { get}}extension CounterDataSource {
    func incrementForCount(count: Int) -> Int {
        if count = = 0 {
            return 0
        } else if count < 0 {
            return 1
        } else {
            return -1}}}class Counter: CounterDataSource {
    var fixedIncrement: Int = 0
}
Copy the code

Protocol extensions


Here’s an example:

protocol A2 {
    func method1(a)
}

extension A2 {
    func method1(a) {
        return print("hi")}func method2(a) {
        return print("hi")}}struct B2: A2 {
    func method1(a) {
        return print("hello")}func method2(a) {
        return print("hello")}}let b2 = B2()
b2.method1()
b2.method2()
Copy the code

The printed result is as follows:

hello
hello
Copy the code

The result looks predictable, so if we change it a little bit:

let a2 = b2 as A2
a2.method1() 
a2.method2() 
Copy the code

So what’s the result here? Is it the same as before? The print result is as follows:

hello
hi
Copy the code

For method1, because it is defined in protocol, it is safe to assume that an instance of a type that is declared to comply with the interface (that is, for A2) must implement method1, We can safely use the final implementation (whether it is a concrete implementation in a type or a default implementation in an interface extension) in a dynamically distributed manner. But with Method2, we just defined it in the interface extension, and there’s nothing that says it has to be implemented in the final type. When used, because A2 is just an instance that conforms to the A2 interface, the only thing the compiler can be sure of with Method2 is that there is a default implementation in the interface extension, so when called, it is not safe to dynamically distribute and instead falls back on the default implementation determined at compile time.

Value type and reference type


Swift has two types: value types, which are copied when passing and assigning values, and reference types, which use only one “point” of the reference object.

  • What are the value types?

    Struct and enum types in Swift are value types, and class types are reference types. Interestingly, all of the built-in types in Swift are value types, including not only traditional meanings like Int and Bool, but also String, Array and Dictionary.

  • What are the benefits of value types?

    One obvious advantage over traditional reference types is that they reduce the number of memory allocations and deallocations on the heap. One of the features of value types is that they are copied on transfer and assignment. Each copy certainly incurs an overhead, but in Swift this cost is kept to a minimum and copying of value types does not occur when copying is not necessary.

    var a = [1.2.3]
    var b = a
    let c = b
    b.append(5) // The memory addresses of a, C, and B are not the same
    Copy the code

    A value type is copied only when its contents change.

  • The downside of value types?

    In rare cases, we can obviously store a lot of stuff in an array or dictionary and add or remove things from it. At this point, the container type of the value type built into Swift needs to be copied every time, even if the reference type is stored, we still need to store a large number of references when copying, and this overhead becomes significant.

  • Best practices

    To address this problem, we can use Cocoa’s container classes for reference types: NSMutableArray and NSMutableDictionary.

    Therefore, the best practice when using a combinatorial dictionary is to use a value container or a reference container depending on the data size and operation characteristics: NSMutableArray and NSMutableDictionary are better choices when you need to process a large amount of data and frequently manipulate (add or subtract) its elements, but when the items in the container are small and the container itself is large, You should use the Swift language’s built-in Array and Dictionary.

Getting the object type


let str = "Hello"
print("\ [type(of: str))")
print("\(String(describing: object_getClass(str)))")

// String
// Optional(NSTaggedPointerString)
Copy the code

KVO


KVO is a very powerful feature in Cocoa, and it has many applications in ObjC. It was introduced in iOS Development Notes – Basics, if you are interested in this article.

We can also use KVO in Swift, but only in subclasses of NSObject. This is understandable, as KVO is implemented based on KEY-value Coding (KVC) and dynamic distribution techniques, which are concepts of ObjC runtime. In addition, since Swift disables dynamic dispatch by default for efficiency, we need to do the additional work of marking the object we want to observe as @objc Dynamic ‘if we want to implement KVO with Swift.

For example, ObjC’s usage habits tend to look like this:

class Person: NSObject {
    @objc dynamic var isHealth = true
}

private var familyContext = 0
class Family: NSObject {

    var grandpa: Person

    override init(a) {
        grandpa = Person(a)super.init(a)print("Grandpa's health:\(grandpa.isHealth ? "Health" : "Unhealthy")")
        grandpa.addObserver(self, forKeyPath: "isHealth", options: [.new], context: &familyContext)
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) {
            self.grandpa.isHealth = false}}override func observeValue(forKeyPath keyPath: String? .of object: Any?.change: [NSKeyValueChangeKey : Any]?.context: UnsafeMutableRawPointer?). {
        if let isHealth = change?[.newKey] as? Bool,
            context = = &familyContext {
            print("Grandpa's physical condition changed:\(isHealth ? "Health" : "Unhealthy")")}}}Copy the code

But Swift 4 actually optimizes the implementation of KVO with closures, so we can change the above example to:

class Person: NSObject {
    @objc dynamic var isHealth = true
}

class Family: NSObject {

    var grandpa: Person
    var observation: NSKeyValueObservation?

    override init(a) {
        grandpa = Person(a)super.init(a)print("Grandpa's health:\(grandpa.isHealth ? "Health" : "Unhealthy")")

        observation = grandpa.observe(\.isHealth, options: .new) { (object, change) in
            if let isHealth = change.newValue {
                print("Grandpa's physical condition changed:\(isHealth ? "Health" : "Unhealthy")")}}DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) {
            self.grandpa.isHealth = false}}}Copy the code

Doesn’t that make it look friendlier?

In the introduction to KVO in wwDC-What’s New in Foundation, observe returns an NSKeyValueObservation object, and the developer only needs to manage its life cycle without removing the observer, So don’t worry about forgetting to remove and crash.

Observe = grandpa. Observe, let observe = grandpa. Observe.

  • Development drawbacks

    In ObjC we can listen on almost any property that satisfies KVC without limitation, and now we need the property to be decorated with @objc Dynamic. But in many cases, the listening class attribute does not meet this condition and cannot be modified. One solution currently available is to implement a similar set of substitutes of its own through attribute observers.

    In the description of this section, meowgod, because of the earlier Swift version, suggests that "a possible solution is to inherit this class and override the properties to be observed using 'dynamic'." In fact, using 'dynamic' decorations alone is not enough. Swift 4 starts with '@objc', but adding '@objc' after inheritance will not compile. (This is easy to understand because the superclass is no longer a subclass of 'NSObject' for ObjC.)Copy the code

lazy


As mentioned earlier, lazy can be used to indicate lazy loading of attributes, and it can be used in conjunction with methods like Map or filter that accept closures and run them to make the entire behavior lazy. Doing so can also help performance in some cases.

For example, when using map directly:

let data = 1.3
let result = data.map {
    (i: Int) - >Int in
    print(It is being processed.\(i)")
    return i * 2
}

print("Prepare the results of the interview")
for i in result {
    print("The result after operation is\(i)")}print("Operation complete")
Copy the code

Its output is:

// Process 1
// Process 2
// Process 3
// Prepare the results of the visit
// The result after the operation is 2
// The result of the operation is 4
// The result after the operation is 6
// The operation is complete
Copy the code

If we do a lazy operation first, we get the lazy version of the container:

let data = 1.3
let result = data.lazy.map {
    (i: Int) - >Int in
    print(It is being processed.\(i)")
    return i * 2
}

print("Prepare the results of the interview")
for i in result {
    print("The result after operation is\(i)")}print("Operation complete")
Copy the code

Run result at this time:

1 // The result is 2 // The result is 2 // The result is 4 // The result is 6 // The operation is completeCopy the code

In cases where you don’t need to run fully and may exit early, using lazy to optimize performance can be very effective.

Log and compile symbols


Sometimes we want to print the current file name as an argument along with the necessary information. Swift has several useful compiler symbols for handling such requirements:

symbol type describe
#file String Path to the file containing the symbol
#line Int The line number where the symbol appears
#column Int The column where the symbol appears
#function String The name of the method that contains the symbol

Thus, we can write a better Log output method by using these symbols:

override func viewDidLoad(a) {
    super.viewDidLoad()

    detailLog(message: "Hey, there's a problem here.")}func detailLog<T> (message: T.file: String = #file,
                  method: String = #function,
                  line: Int = #line) {
    #if DEBUG
    print("\((file as NSString).lastPathComponent)[\(line)].\(method): \(message)")
    #endif
}
Copy the code

Optional Map


We often use the Map method on arrays, which applies some rule to all the elements in the array and returns a new array.

For example, if you want to multiply all the numbers in an array by 2:

let nums = [1.2.3]
let result = nums.map{ $0 * 2 }
print("\(result)")

// Output: [2, 4, 6]
Copy the code

But if I change it to some Int, right? Take 2? The expectation is that if this Int? If there is a value, take it out and multiply it by two; If it’s nil, you just assign nil to the result.

let num: Int? = 3
// let num: Int? = nil

var result: Int?
if let num = num {
    result = num * 2
}
print("\(String(describing: result))")

// if num = 3, print Optional(6)
// print nil when num = nil
Copy the code

But there’s a more elegant and concise way to write it, and that’s an Optional Map. Not only is it possible to use a map in Array or CollectionType, but in the Optional declaration, it also has a map method:

/// Evaluates the given closure when this `Optional` instance is not `nil`,
/// passing the unwrapped value as a parameter.
///
/// Use the `map` method with a closure that returns a non-optional value.
/// This example performs an arithmetic operation on an
/// optional integer.
///
/// let possibleNumber: Int? = Int("42")
/// let possibleSquare = possibleNumber.map { $0 * $0 }
/// print(possibleSquare)
/// // Prints "Optional(1764)"
///
/// let noNumber: Int? = nil
/// let noSquare = noNumber.map { $0 * $0 }
/// print(noSquare)
/// // Prints "nil"
///
/// - Parameter transform: A closure that takes the unwrapped value
/// of the instance.
/// - Returns: The result of the given closure. If this instance is `nil`,
/// returns `nil`.
@inlinable public func map<U> (_ transform: (Wrapped) throws -> U) rethrows -> U?
Copy the code

As described in the function description, this method makes it easy to change and manipulate an Optional value without having to manually unpack it. The input is automatically judged ina way similar to Optinal Binding. If there is a value, it is transformed in the closure of the transform and returns a U? ; If the input is nil, it just returns nil U, okay? .

Therefore, the previous example could be changed to:

let num: Int? = 3
// let num: Int? = nil

let result = num.map { $0 * 2 }
print("\(String(describing: result))")

// if num = 3, print Optional(6)
// print nil when num = nil
Copy the code

Delegate


When we first started writing agents at Swift, we might write something like this:

protocol MyProyocol {
    func method(a)
}

class MyClass: NSObject {
    weak var delegate: MyProyocol?
}
Copy the code

Weak ‘weak’ must not be applied to non-class-bound ‘MyProyocol’; weak must not be applied to non-class-bound ‘MyProyocol’; Consider adding a protocol conformance that has a class bound.

This is because Swift’s protocol can be followed by any type other than class, and for a type like struct or enum, memory is not managed by reference counting itself, so it is not possible to modify the concept of ARC like weak.

So to use weak delegate in Swift, we need to restrict protocol to class, for example

protocol MyProyocol: class {
    func method(a)
}

protocol MyProyocol: NSObjectProtocol {
    func method(a)
}
Copy the code

The Class restriction protocol is only used in class, and the NSObjectProtocol restriction is only used in NSObject. Obviously, the class scope is much wider and can be used in everyday development.

@synchronized


In ObjC’s daily development, @synchronized is often used to modify a variable and automatically add and remove mutex to ensure that the variable cannot be changed by other threads within its scope.

Unfortunately it doesn’t exist in Swift anymore. What @synchronized does behind the scenes is call the objc_sync_Enter and objc_sync_exit methods of objc_sync and add some exception checks. So, in Swift, if we ignore those exceptions and we want to lock a variable, we can say:

private var isResponse: Bool {
    get {
        objc_sync_enter(lockObj)
        let result = _isResponse
        objc_sync_exit(lockObj)
        return result
    }

    set {
        objc_sync_enter(lockObj)
        _isResponse = newValue
        objc_sync_exit(lockObj)
    }
}
Copy the code

literal


A literal is a value such as a particular number, string, or Boolean that directly identifies its type and assigns a value to a variable. Here, for example:

let aNumber = 3
let aString = "Hello"
let aBool = true
Copy the code

In development we might encounter the following situation:

public struct Thermometer {
    var temperature: Double
    public init(temperature: Double) {
        self.temperature = temperature
    }
}
Copy the code

To create a Thermometer object, use the following code:

let t: Thermometer = Thermometer(temperature: 20.0)
Copy the code

Actually, though, the Thermometer’s initialization only needs a base Double, which would be nice if it could be assigned by a literal, like:

let t: Thermometer = 20.0
Copy the code

In fact, Swift provides us with a very interesting set of interfaces for converting literals to specific types. For those types that implement a literal conversion interface, when providing literal assignment, you can simply convert values to the corresponding type by assignment “seamlessly” following the rules defined in the interface methods. These interfaces contain a variety of native literals, which we may often use in real development:

  • ExpressibleByNilLiteral
  • ExpressibleByIntegerLiteral
  • ExpressibleByFloatLiteral
  • ExpressibleByBooleanLiteral
  • ExpressibleByStringLiteral
  • ExpressibleByArrayLiteral
  • ExpressibleByDictionaryLiteral

In this way, we can realize the idea just now:

extension Thermometer: ExpressibleByFloatLiteral {
    public typealias FloatLiteralType = Double

    public init(floatLiteral value: Self.FloatLiteralType) {
        self.temperature = value
    }
}

let t: Thermometer = 20.0
Copy the code

Struct and class


  • In common
  1. Define attributes to store values
  2. Define methods to provide functionality
  3. Define subscript operations so that values contained in instances can be accessed through subscript syntax
  4. Define the constructor used to generate initialization values
  5. Extend to add functionality implemented by default
  6. Implement the protocol to provide some standard functionality
  • Classes are more powerful
  1. Inheritance allows a class to inherit the characteristics of another class
  2. Type conversions allow the type of an instance of a class to be checked and interpreted at run time
  3. A destructor allows an instance of a class to free any resources it has allocated
  4. Reference counting allows multiple references to a class
  • The difference between the two
  1. structIs a value type,classIs a reference type.
  2. structThere is an automatically generated member-by-member constructor that initializes the attributes of members in the new struct instance; whileclassNo.
  3. structChanges in theselfOr its property must annotate the instance method asmutating; whileclassNot necessarily.
  4. structCan’t inherit,classYou can inherit.
  5. structAssignment is a copy of the value, it copies the content;classIt’s a reference copy, it’s a pointer copy.
  6. structIs automatically thread-safe; whileclassIt isn’t.
  7. structStored in thestack,classStored in theheap,structFaster.
  • How to choose?

The general advice is to use minimal tools to accomplish your goals, and use structs more often if they meet your expectations.

Currie (Currying)


A Currying is a way to make a method that takes multiple arguments more flexible by modifying it. The idea of functional programming runs through Swift, and the currization of functions is an important manifestation of the functional characteristics of this language.

For example, there is a problem: implement a function whose input is any integer and whose output returns the input integer + 2. The general implementation is:

func addTwo(_ num: Int) -> Int {
    return num + 2
}
Copy the code

What if I did +3, +4, +5? Do I need to add the above functions one by one? We could actually define a generic function that takes the number to be added to the input number and returns a function:

func add(_ num: Int)- > (Int) - >Int {
    return { (val) in
        return val + num
    }
}

let addTwo = add(2)
let addThree = add(3)
print("\(addTwo(1))  \(addThree(1))")
Copy the code

This allows us to output templates through Curring to avoid writing duplicate methods, thus achieving the goal of mass producing similar methods.

What’s the difference between defining constants in Swift and defining constants in Objective-C?


Swift uses the let keyword to define constants. Let simply identifies this as a constant that is determined at Runtime and cannot be changed thereafter. ObjC uses the const keyword to define constants, which need to be determined at compile time or compile parsing time.

What are the ways code can be reused (shared) without inheritance?


  • Global function
  • extension

Implement a min function that returns two smaller elements


func min<T : Comparable> (_ a : T , b : T) -> T {
    return a < b ? a : b
}
Copy the code

Interchange of two elements


A common way to write this is:

func swap<T> (_ a: inout T._ b: inout T) {
    let tempA = a
    a = b
    b = tempA
}
Copy the code

But with multivariate groups, we could write:

func swap<T> (_ a: inout T._ b: inout T) {
    (a, b) = (b, a)
}
Copy the code

This is neat and doesn’t add any extra space to the display.

Why add extra space without display? The cat god mentioned not using extra space when talking about multiple group swapping. However, some developers believe that multi-group swapping will duplicate the two values, resulting in excess memory consumption. There is some truth in this argument, but there is no substantial evidence for this. In addition, snail has tested the execution time and memory consumption of the two writing methods in [Leetcode- Switching numbers](https://leetcode-cn.com/problems/swap-numbers-lcci/). Basically, the execution speed of multiple group switching is better than that of ordinary switching. But the former memory consumption is higher, interested students can try.Copy the code

The map and flatmap


The closure function is called once for each element in the array and returns the value mapped to that element, eventually returning a new array. But flatMap goes a step further and does a few more things:

  1. Will be removed from the returned resultnilAnd can unpackOptionalType.
  2. Returns an n-dimensional array as a 1-dimensional array.

defer


The block that defer declared will be called when the current code execution exits. Because it provides a way to delay calls, it is often used for resource release or destruction, which is especially useful when a function has multiple return exits.

func testDefer(a) {
    print("Start holding resources")
    defer {
        print("End of resource holding")}print("Program runs ing")}// Start holding resources
// The program runs ing
// End resource holding
Copy the code

Using defer makes it easy to put the necessary logic together for readability and maintenance, but incorrect use can also cause problems. For example, check whether the resource is already held by another person before holding it:

func testDefer(isLock: Bool) {
    if !isLock {
        print("Start holding resources")
        defer {
            print("End of resource holding")}}print("Program runs ing")}// Start holding resources
// End resource holding
// The program runs ing
Copy the code

Note that the scope of defer is not the entire function, but the current scope. What if there are multiple copies of defer?

func testDefer(a) {
    print("Start holding resources")

    defer {
        print("End of holding resource A")}defer {
        print("End of holding resource B")}print("Program runs ing")}// Start holding resources
// The program runs ing
// End holding resource B
// End holding resource A
Copy the code

When there are more than one defer, the last one executes first, and you can guess that Swift uses stack to manage the defer.

The relationship and difference between String and NSString


String is a Swift type, NSString is a Foundation class, and the two can be seamlessly converted. String is a value type, NSString is a reference type, the former is better suited to the String “immutable” feature, and the value type is automatic multithreaded safety, performance improvement in use.

Unless you need some NSString-specific method, use String.

How do I get the length of a String?


To simply get the number of characters in the string, use count directly:

let str = "Hello, Hello"
print("\(str.count)") / / 7
Copy the code

If you want to get the number of bytes taken up by a string, you can get it according to your encoding environment:

print("\(str.lengthOfBytes(using: .utf8))")    / / 11
print("\(str.lengthOfBytes(using: .unicode))") / / 14
Copy the code

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


  1. [1, 2, 3] uses literals to initialize,ArrayTo achieve theExpressibleByArrayLiteralThe agreement.
  2. A trailing closure is used.
  3. Parameter lists and return values are not explicitly declared, using automatic inference of closure types.
  4. The closure can be omitted if it has only one line of codereturnAutomatically returns the result of this sentence.
  5. $0Represents the first parameter when the parameter list is not explicitly declared, and so on.

Does the following code work? What are the results?


var mutableArray = [1.2.3]
for i in mutableArray {
    mutableArray.removeAll()
    print("\(i)")}print(mutableArray)
Copy the code

The result is as follows:

1
2
3
[]
Copy the code

Why is it called three times?

Because Array is a value type, it is assigned at write time. If the value of mutableArray changes in the loop, mutableArray on for in will be copied, which will still have the value [1, 2, 3], so it will loop three times.

It’s not easy to create the article, any mistake, welcome to feng comment (Diao), please click on πŸ‘, thank you very much!