Swift:进阶知识点总结

在很久以前,我写了Swift系列,那还是在Swift2.0时期。不过那些知识点依旧有用。最近在开发新项目,使用的是Swift4.0,把一些个人认为重要的并且可以认为是进阶的知识点进行了总结。现在与大家分享。


一、@escaping(逃逸闭包)

默认情况下,闭包是非逃逸闭包

func doWork(block: () -> ()) {
    block()
}

非逃逸闭包的作用域是不会超过函数本身的,所以说我们不需要担心在闭包内持有self。

而逃逸闭包@escaping就不同了,因为需要确保闭包内的成员依然有效,如果在闭包内引用self以及self的成员的话,就要考虑闭包内持有self的情况了

doWorkAsync 将block放到一个异步操作中

func doWorkAsync(block: @escaping () -> ()) {
    DispatchQueue.main.async {
        block()
    }
}

下面是示例代码:

class S {
    var foo = "foo"
    
    func method1() {
        doWork {
            print(self.foo)
        }
        foo = "bar"
    }
    
    func method2() {
        doWorkAsync {
            print(self.foo)
        }
        self.foo = "bar"
    }
    
    func method3() {
        doWorkAsync {
            [weak self] in
            print(self?.foo ?? "Default")  
        }
        self.foo = "bar"
    }
}

S().method1()
S().method2()
S().method3()
调用method1 打印结果为: foo,因为doWork是同步闭包。

调用method2 打印结果为: bar,因为doWorkAsync是异步闭包, 默认情况下,doWorkAsync会持有self,所以执行完self.foo = "bar"这句之后,self不会被释放掉。

调用method3 打印结果为: Default,因为doWorkAsync是异步闭包,又不希望doWorkAsync持有self,所以使用了 [weak self], 所以执行完self.foo = "bar"这句之后,self就会被释放掉了。 注意,这里的print打印的时候,这里的self有?, 因为到这里,self有可能已经被释放掉了。


二、weak 和 unowned

unowned 有点像oc里面的unsafe_unretained,而weak就是以前的weak。对于这两者的使用,不能说用哪一个要好一点,要视情况而定。用unowned的话,即使它原来的引用的内容被释放了,它仍然会保持对被已经释放了的对象的一个引用,它不能是Optional也不能是nil值,这个时候就会出现一个问题,如果你调用这个引用方法或者访问成员属性的话,就会出现崩溃(野指针)。而weak要稍微友善一点,在引用的内容被释放之后,会自动将weak的成员标记为nil。有人要说,既然这样,那我全部使用weak。但是在可能的情况下,我们还是应该倾向于尽量减少出现Optional 的可能性,这样有助于代码的简化。Apple给我们的建议是如果能够确定访问时不会被释放的话,尽量用unowned,如果存在被释放的可能性的话,就用weak。就我个人的经验来看的话,几乎都是使用weak。


三、@objc

在 Swift 代码中,使用@objc修饰后的类型,可以直接供 Objective-C 调用。可以使用@objc修饰的类型包括:

  • 未嵌套的类
  • 协议
  • 非泛型枚举(仅限于原始值为整形的类型)
  • 类和协议中的属性和方法
  • 构造器和析构器
  • 下标
但我个人觉得,@objc使用主要体现在三个方面。
1. 直接修饰类
例如:
@objc class MyHelper:NSObject {  
    // class code  
} 
注意: 使用@objc修饰的类,必须继承自NSObject

2. 如果协议中有optional修饰的方法,就必须使用@objc来修饰:
例如:
@objc protocol LFProtocol {  
    optional func incrementForCount(count: Int) -> Int  
    optional var fixedIncrement: Int { get }  
}

3. 处理按钮点击事件
定义按钮事件的时候,代码如下:
btn.addTarget(self, action: #selector(btnClick(sender:)), for:
            .touchUpInside)

而btnClick按钮点击事件代码如下:
@objc func btnClick(sender: UIButton) {
        
    }

必须在前面加上@objc,不然直接报错。

四、访问控制修饰符
访问控制修饰符的限制性 高->低 排序为下:
open > public > internal(默认) > fileprivate > private
其中默认缺省的修饰符是internal。
1: open 和 public 都是可以跨 Module 访问的,但 open 修饰的类可以继承,修饰的方法可以重写(此时,open 需同时修饰该方法以及所在类),而 public 不可以。
2. fileprivate 和 private
代码说明如下:
class Animal: NSObject {
    // 默认/缺省 internal
    var name: String?
    
    // private 仅可在本类中访问
    private var privateProp = "Private Property"
    private func privateMethod() {}
    
    // fileprivate 可在当前文件内所有类访问
    fileprivate var filePrivateProp = "File Private Property"
    fileprivate func filePrivateMethod() {}
}

class Cat: Animal {
    
    // private 修饰的内容,只能在当前类中访问,所以下面报错
    //    override func privateMethod() {
    //
    //    }
    
    // fileprivate 可在当前文件内所有类访问(所以可以重写父类的filePrivateMethod方法)
    override func filePrivateMethod() {
        
    }
}

// 假设 Dog是单独定义在一个文件中的
// 由于 Dog 与 Animal 不在同一个文件中,所以不能重写父类方法
class Dog: Animal {
    //    override func filePrivateMethod() {
    //
    //    }
}

五、自定义实现map,filter,reduce
1. map是用来对集合中的每个元素单独处理,然后返回新的集合。
2. filter是用来过滤集合中的元素,然后返回新的集合。
3. reduce是利用集合中的元素,进行相关操作,返回自己想要的结果。
Swift中对这三个方法的定义是基于Sequence的,我为了学习及研究方便,将他们作为Array的扩展处理。实现的代码如下:
extension Array {
    func myMap<T>(_ transform: (Array.Iterator.Element) throws -> T) rethrows -> [T] {
        var temp: [T] = []
        for num in self {
            temp.append(try transform(num))
        }
        return temp
    }
    
    func myFilter(_ transform: (Array.Iterator.Element) throws -> Bool) rethrows -> [Array.Iterator.Element] {
        var temp: [Array.Iterator.Element] = []
        for num in self {
            if try transform(num) {
                temp.append(num)
            }
        }
        return temp
    }
    
    // $0为计算结果类型(T),$1为数组元素类型(Array.Iterator.Element)。
    func myReduce<T>(_ initValue: T, _ transform: (T, Array.Iterator.Element) throws -> T) rethrows -> T {
        var tempValue = initValue
        for num in self {
            tempValue = try transform(tempValue, num)
        }
        return tempValue
    }
}
上述代码说明:
1. 泛型T是指的经过方法处理完毕之后,元素的类型。
2. Array.Iterator.Element是指的处理之前,数组中元素的类型。
3. 以myMap方法说明,transform这个block就是处理数组中的每一个元素,返回自己想要的内容。myFilter和myReduce同理。
4. 注意到myReduce方法,里面的第一句就是 var tempValue = initValue 因为initValue默认是let修饰的,所以不可变。有的同学就会觉得那为什么不在initValue参数前面加一个var进行修饰? 那是因为我们基本都遵循基本原则,方法列表里面定义的变量用let。而且Swift本身就提倡使用let。
5. rethrows
    这个函数如果抛出异常,仅可能是因为传递给它的闭包的调用(transform方法,所以在之前使用关键字try,如果真的有异常,会通过throws抛出异常)导致了异常。如果闭包的调用没有导致异常,编译器就知道这个函数不会抛出异常。那么我们也就不用去处理异常了。
使用举例
struct City {
    let name: String
    let population: Int
}

extension City {
    func scalingPopulation() -> City {
        return City(name: name, population: population * 1000)
    }
}

let paris = City(name: "Paris", population: 2241)
let madrid = City(name: "Madrid", population: 3165)
let amsterdam = City(name: "Amsterdam", population: 827)
let berlin = City(name: "Berlin", population: 3562)
let cities = [paris, madrid, amsterdam, berlin]

// 1. 筛选population 大于1000的,2. 过滤下一,将人口基数乘以1000, 3. 一次打印出来
let finalResult = cities.myFilter{$0.population > 1000}.myMap{$0.scalingPopulation()}.myReduce("City Name, Population:") { (result, city) in
    return result + "\n" + "\(city.name), \(city.population)"
}
print(finalResult)

finalResult 的打印结果为:

City Name, Population:

Paris, 2241000

Madrid, 3165000

Berlin, 3562000


扩展:
flatMap的使用
1. 多维数组合并为一维数组
let flatArr = [[1, 3, 6], [3, 7, 9]]
let flatArrResult = flatArr.flatMap{$0}
flatArrResult
最终结果为 [1, 3, 6, 3 ,7, 9]. 因此flatMap只负责合并数组,不负责去除重复数据。

2. 去除nil,定义的flatArr2中的每一个元素是 Int? 类型
var flatArr2: [Int?] = [3, 6, nil, 1, 8]
let flatArr2Result = flatArr2.flatMap{$0}
flatArr2Result
可以去除数组中的nil,并且将非nil的optional的对象,强制解包,结果flatArr2Result中的每一个元素是Int类型

六、try,try?, try! 的使用
1. try方式 手动捕捉异常
do {
    try NSJSONSerialization.JSONObjectWithData(jsonData, options: .MutableContainers)
} catch {
    print(error)
}

2. try?方式(常用方式) 系统帮助我们处理异常,如果该方法出现了异常,则该方法返回nil.如果没有异常,则返回对应的对象
guard let anyObject = try? NSJSONSerialization.JSONObjectWithData(jsonData, options: .MutableContainers) else {
          return
      }

3. try!方法(不建议,非常危险) 直接告诉系统,该方法没有异常.注意:如果该方法出现了异常,那么程序会报错(崩溃)
let anyObject = try!NSJSONSerialization.JSONObjectWithData(jsonData, options: .MutableContainers)

七、@autoclosure
我们假设有一个场景,现在要设计一个 "或" 运算,第一个运算内容如果为true,则结果直接返回true;如果为false,则需要考虑第二个运算符,但是第二个运算内容是经过很多的逻辑计算之后得到结果的,所以我们应该将其设计为一个闭包,此闭包没有输入参数,返回类型为布尔。所以大致的实现代码如下:
func myOr(first: Bool, second: () -> Bool) -> Bool {
    if first {
        return true
    } else {
        return second()
    }
}
使用的时候,大致如下:
let orResult = myOr(first: 2 > 3, second: {4 > 3})
print(orResult)
注意到second闭包的调用是使用的大括号,而前面first这个调用没有任何的参数形式。所以我们就考虑能不能有一种方法,使得后面的调用也没有任何的括号形式,达到调用的一致性。这个时候就该 @autoclosure 出场了。
@autoclosure:Apple为了让语法看起来更漂亮些,在Swift中为我们提供了这么一个神奇的东西@autoclosure,他可以让我们的表达式自动封装成一个闭包。@autoclosure只适用于这样的()->T无参闭包。
现在我们将 myOr 方法进行改造
func myAutoClosureOr(first: Bool, second: @autoclosure () -> Bool) -> Bool {
    if first {
        return true
    } else {
        return second()
    }
}

调用的时候如下:
let orAutoClosureResult = myAutoClosureOr(first: 2 > 3, second: 4 > 3)
print(orAutoClosureResult)

扩展:Swift中引入了“??” 这个操作,意思是指可选类型的数据如果有值的话,直接取解包之后的数据,如果为nil的话,去?? 后面自己设定的数据值。我们来看一下 ?? 的定义:
func ??<T>(optional: T?, defaultValue: @autoclosure () -> T) -> T {
    switch optional {
    case .some(let value):
        return value
    case .none:
        return defaultValue()
    }
}

可能你会有疑问,为什么这里要使用 autoclosure,直接接受 T 作为参数并返回不行么,为何要用 () -> T 这样的形式包装一遍,岂不是画蛇添足?其实这正是 autoclosure 的一个最值得称赞的地方。如果我们直接使用 T,那么就意味着在 ?? 操作符真正取值之前,我们就必须准备好一个默认值传入到这个方法中,一般来说这不会有很大问题,但是如果这个默认值是通过一系列复杂计算得到的话,可能会成为浪费 -- 因为其实如果 optional 不是 nil 的话,我们实际上是完全没有用到这个默认值,而会直接返回 optional 解包后的值的。这样的开销是完全可以避免的,方法就是将默认值的计算推迟到 optional 判定为 nil 之后。

八、OC,Swift相互调用
1. OC 调用 Swift
1.1)在OC项目中,添加Swift文件后,系统会自动提示你添加桥接文件,确认即可。
1.2)进入TARGETS ->Build Settings -> Packaging 中 设置Defines Module为YES, 设置 Product Module Name ,也可以不设置,默认为工程的名字。这个在后面会用到。
1.3)在OC需要用到的swift文件中 导入文件 "Product Module Name -Swift.h"  因为 Product Module Name 默认是工程的名字所以直接导入 #import "工程名-Swift.h"。
定义的Hello Swift代码如下:
import UIKit

class Hello: NSObject {
    var address: String
    var gender: String
    init(address: String, gender: String) {
        self.address = address
        self.gender = gender
    }
    
    func lfMethod() {
        print("message:\(self.address + self.gender)")
    }
}

在OC中调用如下:
Hello *hollo = [[Hello alloc] initWithAddress:@"shanghai" gender:@"M"];
    [hollo lfMethod];

2. Swift 调用 OC
2.1)在Swift项目中,添加OC文件后,系统会自动提示你添加桥接文件,确认即可。
定义的OC代码如下:
@interface Sec : NSObject
- (void)sayHello;
@end

@implementation Sec
- (void)sayHello {
    NSLog(@"Hello World");
}
@end
2.2)把swift需要用到的OC文件的头文件放到桥接文件里 工程名-Bridging-Header.h
在Swift中调用如下:
let sec = Sec()
sec.sayHello()

猜你喜欢

转载自blog.csdn.net/sinat_27706697/article/details/78858073
今日推荐