1. "?"和"!"
Optional:
Optional其实是个enum,里面有None和Some两种类型。其实所谓的nil就是Optional.None, 非nil就是Optional.Some, 然后会通过Some(T)包装(wrap)原始值,这就是为什么在使用Optional的时候要拆包(从enum里取出来原始值)的原因。
使用Optional,只需要在后面紧跟一个?即可:
var path: String?
/*这是Optional的声明,意思不是"我声明了一个Optional的String值",
而是”我声明了一个Optional类型值,它可能包含一个String值,也可能什么都不包含”,
也就是说实际上我们声明的是Optional类型,而不是声明了一个String类型*/
一旦声明为Optional的,如果不显式的赋值就会有个默认值nil。判断一个Optional的值是否有值,可以用if来判断:
if strValue {
//do sth with strValue
}
在使用Optional值的时候需要在具体的操作,比如调用方法、属性、下标索引等前面需要加上一个?,如果是nil值,也就是Optional.None,会跳过后面的操作不执行,如果有值,就是Optional.Some,可能就会拆包(unwrap),然后对拆包后的值执行后面的操作,来保证执行这个操作的安全性,比如:
?
的几种使用场景:
1.声明Optional值变量
var path: String?
2.用在对Optional值操作中,用来判断是否能响应后面的操作
if strValue {
//do sth with strValue
}
3.用于安全调用protocol的optional方法
因为delegate是Downloadable类型的,它的download方法是optional,所以它的具体实现有没有download方法是不确定的。Swift提供了一种在参数括号前加上一个?的方式来安全地调用protocol的optional方法
@objc protocol Downloadable {
@optional func download(toPath: String) -> Bool;
}
@objc class Content: Downloadable {
//download method not be implemented
}
var delegate: Downloadable = Downloadable()
delegate.download?("some path")
4.使用 as? 向下转型(Downcast)
这里也可以把?换成!,但是如果强转的类型不对,会导致程序奔溃,如果使用?,可选类型则不会carsh ,但是会出现意料之外的类型错误。
if let dataSource = object as? UITableViewDataSource {
let rowsInFirstSection = dataSource.tableView(tableView, numberOfRowsInSection: 0)
}
!:
var myLabel: UILabel?
对myLabel操作时,每次依然要加上!来强制拆包(在读取值的时候,也可以用?)
myLabel!.text = "text"
myLabel!.frame = CGRectMake(0, 0, 10, 10)
...
对于这种类型的值,我们可以直接这么声明:var myLabel: UILabel!
, 这种是特殊的Optional,称为Implicitly Unwrapped Optionals
, 直译就是隐式拆包的Optional,就等于说你每次对这种类型的值操作时,都会自动在操作前补上一个!进行拆包,然后在执行后面的操作,当然如果该值是nil,也一样会报错crash掉。
var myLabel: UILabel! //!相当于下面这种写法的语法糖
var myLabel: ImplicitlyUnwrappedOptional<</span>UILabel>
!两种使用场景
1.强制对Optional值进行拆包(unwrap)
2.声明Implicitly Unwrapped Optionals值,一般用于类中的属性
小结:
- ?的4种应用场景和!的2种应用场景,在项目中运用的时候,只要稍加 注意,就可以避免很多错误。
- 在编写代码时,遇到需要?!的时候,一般系统会根据属性定义类型来自动补全到底是可选值还是特定的值。
2.swift中的block
声明并实现一个无参数无返回值的block:
//1.定义一个无参无返回值的闭包类型
//格式: typealias 闭包名称 = (参数名称: 参数类型) -> 返回值类型
typealias funcBlock = () -> ()
//2. 声明一个变量
var callBack: funcBlock!
//3. 定义一个方法,方法的参数为和funcBlock类型一致的闭包,并赋值给callBack
func callBackBlock(block: @escaping () -> Void) {
callBack = block
}
//4.点击返回按钮,调用callBack
@objc private func back(){
if let call = callBack {
call()
}
self.dismiss(animated: true, completion: nil)
}
//5.实现block的回调事件
private func pushToPreview(){
let preVC = CCPreViewController()
let nav = UINavigationController(rootViewController: preVC)
preVC.callBackBlock { () in
//在这里处理回调事件
}
self.present(nav, animated: true, completion: nil)
}
声明并实现一个带参数有返回值的block:
//1.定义一个有两个参数有返回值的闭包类型
typealias funcBlock = (_ str:String,_ a:Int) -> (String)
//2. 声明一个变量
var callBack: funcBlock!
//3. 定义一个方法,方法的参数为和funcBlock类型一致的闭包,并赋值给callBack
func callBackBlock(block: @escaping (_ str:String,_ a:Int) -> String){
callBack = block
}
//4.点击返回按钮,调用callBack
@objc private func back(){
if let call = callBack {
let str = call("调用了block",1)
NSLog("返回值是:%@", str)
}
self.dismiss(animated: true, completion: nil)
}
//5.实现block的回调事件
private func pushToPreview(){
let preVC = CCPreViewController()
let nav = UINavigationController(rootViewController: preVC)
preVC.callBackBlock { (str,a) in
//在这里处理回调事件
return "只是block响应后的返回值"
}
self.present(nav, animated: true, completion: nil)
}
escaping:当一个闭包当做一个参数传进函数里,这个闭包是在这个函数执行完后执行的,这个时候我们就说这个闭包从函数逃出来了(escape)。比如我们进行一个异步的请求,请求时会传入一个handler,比如当请求成功后执行达到回调的目的。
noescape:简单的介绍就是如果这个闭包是在这个函数结束前内被调用,就是非逃逸的即noescape。
函数中的闭包使用:
func reqeustOriginalData(asset:PHAsset ,completion: @escaping (_ result : String ,_ rate:CGFloat )->()) -> () {
if #available(iOS 9.0, *) {
let resource = PHAssetResource.assetResources(for: asset).first
let rate = CGFloat(asset.pixelHeight)/CGFloat(asset.pixelWidth)
let dir = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.cachesDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).last;
let path = (dir)! + "/" + ((resource?.originalFilename)!)
let url = NSURL.fileURL(withPath: path)
let localPath = url.absoluteString
if (FileManager.default.fileExists(atPath: path)){
completion(localPath,CGFloat(rate))
return
}
PHAssetResourceManager.default().writeData(for: resource!, toFile: url, options: nil) { (error) in
completion(localPath,CGFloat(rate))
}
} else {
// Fallback on earlier versions
}
调用
let tool = PhoneAlbumTool()
tool.reqeustOriginalData(asset: detailModel.asset) { (path,rate) in
DispatchQueue.main.async {
self.initPlayer(path: path,rate: rate)
}
}
补充:
swift中的"_"下划线有哪些意义:
- 格式化数字字面量
通过使用下划线能够提高数字字面量的可读性,比如:
let paddedDouble = 123.000_001 实际值:123.000001
let oneMillion = 1_000_000 实际值:1000000
- 忽略元组的元素值
当我们使用元组时,假设有的元素不须要使用。这时能够使用下划线将对应的元素进行忽略,比如:
let http404Error = (404, "Not Found")
let (_, errorMessage) = http404Error
- 忽略区间值
有时候我们并不关心区间内每一项的值,能够使用下划线来忽略这些值。
let base = 3
let power = 10
var answer = 1
for _ in 1...power {
answer *= base
}
- 忽略外部參数名
// 实现
func testFun(_ param1:String,param2:Int) {
NSLog("%@,%d", param1,param2)
}
// 调用
let tool = PhoneAlbumTool()
tool.testFun("str", param2: 3)
方法中的第一个参数名“ param1”加了下划线后,在调用时,就被忽略掉了
小结:
- swift的闭包和OC中的block 在语法中有些许区别,特别是swift闭包中加入了
escaping
和noescape
两个概念,在调用闭包的时候需要将定义好的block赋值检查是否为空猜能调用,而OC 是不需要赋值的,但是使用过程大致是相同的。 - 在写API时不推荐忽略外部參数名,这主要是为了调用的时候能够方便地知道每一个參数的含义
3.全能初始化方法
先来看一段OC的全能初始化方法:
//.h 的方法声明如下
- (instancetype)initWithModel:(NSString *)rowTitle detailText:(NSString *)detailText;
+ (instancetype)initWithModel:(NSString *)rowTitle detailText:(NSString *)detailText;
// .m中实现如下
- (instancetype)initWithModel:(NSString *)rowTitle detailText:(NSString *)detailText{
if ([super init]) {
self.title = rowTitle;
self.detailText = detailText;
}
return self;
}
+ (instancetype)initWithModel:(NSString *)rowTitle detailText:(NSString *)detailText{
return [[self alloc]initWithModel:rowTitle detailText:detailText];
}
@end
在OC中,我们把这种可为对象提供必要信息以便其能完成工作的初始化方法叫做“全能初始化方法”。
swift 的全能初始化方法:
init(path: String, frame: CGRect) {
super.init(frame: frame)
self.path = path
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
以下是错误的初始化方法
init(path: String, frame: CGRect) {
super.init()//这里必须要调用父类指定的初始化方法,否则会报错
self.path = path
}
init(path: String, frame: CGRect) {
self.path = path //这样会导致数据为nil,需要在重写父类方法后调用
super.init(frame: frame)
}
补充:
let:
被用于声明不变量 ,不变量的值一旦被定义就不能再改变,类似于OC中被readonly修饰过一样;但是let
声明的常量是可以在初始化方法中进行赋值的这是编译器所允许的,因为 Swift 中的 init
方法只会被调用一次,这与 Objective-C 不同;即使成员变量是可选类型,如:let param: String?
,仍然是需要进行初始化的。
var:
被用于声明变量,变量则可以在声明之后被随意赋值。
如果在初始化有不可变成员变量时候,没有对这个成员变量赋值,编译时是会报错:Property 'self.name' not initialized at super.init call
,意思是:属性'self.name'未在super.init调用时初始化。
import Foundation
class TestModel: NSObject {
let name:String!
override init() {
// name = "1234" //注释掉就会报错,一定要赋值,对于let 声明的类型
super.init() //这行代码可以不写也是对的,会隐士生成
// name = "5678" //如果是需要修改父类的成员变量,则在调用 super.init() 此方法后,再进行修改
}
}
小结
- 在OC中,全能初始化方法重写父类的
init
方法就行了,但是在swift中,如果该类指定的初始化方法是init
,那么在写全能初始化方法是就必须重写父类的init
方法进行重写,比如:UIView
的指定父类初始化方法是init(frame: frame)
,就必须要重写此方法,才能完成全能初始化方法。 - swift的构造方法,包含了三种:
1 .隐士构造方法:如果你的类中没有构造方法,系统其实会默认生成一个构造方法。
2 .指定构造方法:指定构造器是类中的主要构造器,每个类必须保证有一个以上的构造器(包含默认的隐式构造器),负责初始化本类的存储属性,并且调用父类构造器,由父类来继续初始化过程。
3 . 便携构造方法:便利构造器是次要的,类可以不需要便利构造器,也可以定义多个。它可以调用自身类中的指定构造器,用来设置一些默认值,也可以用作一些特殊用途。只有在便利构造方法中可以显示调用自己类中的构造方法
4.Struct 和 Class
Struct:
结构体,一组内存地址连续的变量。在Swift
中,我们不仅可以在Struct
中声明变量并为变量设置默认值,而且可以在Struct
中添加相应的函数。下面定义一个带有setMyPoint()方法和display()方法的结构体:
struct MyPoint {
var x:Float! // 也可以直接给默认值 var x:Float = 0
var y:Float! // 也可以直接给默认值 var y:Float = 0
mutating func setMyPoint(tempX:Float,tempY:Float){
x = tempX
y = tempY
}
func display() {
print("set is go:",x,y)
}
}
setMyPoint()方法关键字func前边多了一个mutating
关键字。在Struct中的函数,默认(不添加mutating
关键字)的函数对Struct中属性只有只读权限。如果你添加上mutating
, 那么该函数就对属性持有读写的权限。
调用结构体方法:
var myPoint = MyPoint()
myPoint.setMyPoint(tempX: 30, tempY: 40) //修改属性的值
myPoint.display() //打印属性的值 为 :x = 30;y = 40 。
通过构造函数给其属性赋值,也就是在给变量分配内存空间时为其指定初始值,这一点就和类的构造函数即为相似了。
var myPoint = MyPoint.init(x: 20, y: 30)
Class:
swift中的Class与Objective-C中的类大同小异,都是面向对象编程,所以思维方式也是一致的。
- 类的创建与构造器
类中没有构造函数,会自动生成一个默认的无参构造函数,调用的就是默认的无参构造函数进行的类的实例化。
Class MyPoint {
var x:Float = 0
var y:Float = 0
func setMyPoint(tempX:Float,tempY:Float){
x = tempX
y = tempY
}
func display() {
print("set is go:",x,y)
}
}
在构造函数的形参列表中,我们可以为形参指定默认值,下方是一个构造函数。
class MyPoint {
var x:Float = 0
var y:Float = 0
init(tempX:Float,tempY:Float) {
x = tempX
y = tempY
}
func display() {
print("set is go:",x,y)
}
}
var myPoint = MyPoint.init(tempX: 100, tempY: 89)//添加了一个构造函数,并为各个形参指定默认值
- 对象的赋值与比较
对象的赋值跟Object-C是一样的,把一个对象赋值给另一个对象,实质就是指向了同一个内存地址,如果改变了内存里面的内容,那这两个对象的值都会发生改变。
判断两个变量是否指向同一个实例:
var myPoint = MyPoint.init(tempX: 100, tempY: 89)
var newPoint = myPoint
print(myPoint.x,myPoint.y)
print(newPoint.x,newPoint.y)
if myPoint===newPoint { //恒等运算符做比较
print("这是两个一样的对象")
}
//打印:some(100.0) some(89.0)
// some(100.0) some(89.0)
//这是两个一样的对象
- 属性的 set方法
可以在didSet 里面执行一些改变UI的操作,值得注意的是,在带参数初始化类的时候,是不会触发set方法的。
var y:Float!{
willSet{
print("属性将被赋值",y)
// do some thing
}
didSet {
print("属性已经被赋值",y)
// do some thing
}
}
- 实例方法与类方法
在Obj e c t-C中,类方法是由+来修饰的,实例方法是由-号来修饰的。Swift中声明方法时,class 用来声明类方法
class testClass {
func objectMethod() {
print("这是实例方法,需要实例化对象后,对象调用")
}
class func classMethod() {
print("这是类方法,不需要实例化,直接类调用")
}
}
//调用
TestClass.classMethod()
let test = TestClass.init()
test.objectMethod()
小结:
两者的区别:
-
Struct
可直接在构造函数中初始化property
,class
不可直接在构造函数中初始化property
- 变量赋值方式不同(深浅copy),
Struct
赋值“=”的时候,会copy
一份完整相同的內容给另一個变量(开辟了新的内存地址);class
赋值“=”的时候,不会copy
一份完整的内容给另一個变量,只是增加了原变量内存地址的引用而已(没有开辟了新的内存地址)。 - Swift 语言的特色之一就是可变动内容和不可变内容用
var
和let
來甄别,如果初始为let
的变量再去修改会发生编译错误。 struct也遵循这一特性,class
不存在这样的问题:从上面的赋值代码能很清楚的看出来。 -
mutating function
,struct
和class
的差別是struct
的function
要去改变property
的值的时候要加上mutating
,而class
不用。 - 继承
struct
不能继承,class
可以继承。 -
struct
比class更“轻量级”
struct分配在栈中,
class``分配在堆中。 -
struct
是值类型,深拷贝,对一个变量操作不可能影响另一个变量;class
是引用类型,浅拷贝,对一个变量操作可能影响另一个变量所引用的对象。
5.map、flatmap、filter、reduce
map:
map
函数能够被数组调用,它接受一个闭包作为参数,作用于数组中的每个元素。闭包返回一个变换后的元素,接着将所有这些变换后的元素组成一个新的数组。
下面使用map
遍历一个数组中所有的元素,将每个元素自身与自身相加,最后返回一个保存相加后元素的数组:
let numbers = [1,2,3,4,5,6]
//1.直接用不可变的数组就可以使用
let newNumbers = numbers.map { (number) -> Int in
return number + number
}
//2.map闭包里面的类型可以自动推断所以可以省略
let newNumbers = numbers.map { number in
return number + number
}
//3.省略return
let newNumbers = numbers.map { number in number + number }
//4.最简单的写法
let newNumbers = numbers.map { $0 + $0}
Map函数返回数组的元素类型不一定要与原数组相同:
let fruits = ["apple", "banana", "orange", ""]
let counts = fruits.map { fruit -> Int? in
let length = fruit.characters.count
guard length > 0 else {
return nil
}
return length
}
//打印为[Optional(5), Optional(6), Optional(6), nil
Map还能返回判断数组中的元素是否满足某种条件的Bool值数组:
let array = [1,2,3,4,5,6]
// 最洁简的写法
let isEven = array.map { $0 % 2 == 0 }
//isEven = [false, true, false, true, false, true]
flatmap:
flatMap
返回后的数组中不存在 nil 同时它会把Optional解包;
let fruits = ["apple", "banana", "orange", ""]
let counts = fruits.flatMap { fruit -> Int? in
let length = fruit.characters.count
guard length > 0 else {
return nil
}
return length
}
// [5,6,6] 没有被option 包 和nil
flatMap
还能把数组中存有数组的数组 一同打开变成一个新的数组 :
let array = [[1,2,3], [4,5,6], [7,8,9,10]]
// 用flatMap
let arrayFlatMap = array.flatMap { $0 }
// [1, 2, 3, 4, 5, 6, 7, 8, 9]
print(arrayFlatMap)
flatMap
也能把两个不同的数组合并成一个数组 这个合并的数组元素个数是前面两个数组元素个数的乘积:
let fruits = ["apple", "banana", "orange"]
let counts = [1, 2, 3]
let fruitCounts = counts.flatMap { count in
fruits.map { fruit in
// title + "(index)"
// 也可以返回元组
(fruit, count)
}
}
// [("apple", 1), ("banana", 1), ("orange", 1), ("apple", 2), ("banana", 2), ("orange", 2), ("apple", 3), ("banana", 3), ("orange", 3)]
print(fruitCounts)
//可以算是一个 flatMap 和 map 的结合使用
Filter:
filter
可以取出数组中符合条件的元素 重新组成一个新的数组
let numbers = [1,2,3,4,5,6]
let evens = numbers.filter { $0 % 2 == 0 }
// [2, 4, 6]
print(evens)
//可以很好的帮我们把数组中不需要的值都去掉
Reduce:
Reduce可以把所有元素的值合并成一个新的值:
let numbers = [1,2,3,4,5,6]
// reduce 函数第一个参数是返回值的初始化值,最终结果是初始值+所有值的和
let sum = numbers.reduce(0) { $0 + $1 }
// 完整的格式
let sum1 = numbers.reduce(0) { total, num in
return total + num
}
// 21
print(sum)
小结:
- map 闭包返回一个变换后的元素,接着将所有这些变换后的元素组成一个新的数组。
- map和flatMap的不同之处:
1.flatMap返回后的数组中不存在 nil 同时它会把Optional解包;
2.flatMap 还能把数组中存有数组的数组 一同打开变成一个新的数组 ;
3.flatMap也能把两个不同的数组合并成一个数组 这个合并的数组元素个数是前面两个数组元素个数的乘积。 - Filter很好的对数组起到筛选的作用
- Reduce 把所有元素的值合并成一个新的值,类似于对数组求和可以一行代码搞定了
6.guard 和defer
guard:当条件不满足的情况下,跳出作用域。
for i in 0...10 {
let groupModel = GroupModel.init()
groupModel.groupName = String(format: "groupName-%d", i);
groupModel.dataSorce = [1,2,3,4]
guard i<3 else {
//当i>=3时会结束循环
break
}
}
defer:用来包裹一段代码,这个代码块将会在当前作用域结束的时候被调用。这通常被用来对当前的代码进行一些清理工作,比如关闭打开的文件等
defer {
print("在作用域结束时调用,一般用来关闭文件操作,释放资源")
}
小结:
-
guard
跟if
语法一样,两者的作用是相反的,如果不使用return,continue, break,throw跳出当前作用域,编译器会报错。 -
defer
可以在同一个作用域中指定多个defer
代码块,在当前作用域结束时,它们会以相反的顺序被调用,即先定义的后执行,后定义的先执行。
7.获取函数名、行号和文件路径
在Objective-C
中获取函数名、行号、和文件位置,如下:
printf("function:%s,line:%d,file:%s",__FUNCTION__,__LINE__,__FILE__);
在swift
中获取函数名、行号、和文件位置,如下:
print("funciton:\(#function),line:\(#line),file:\(#file)")
小结:
- 在代码编写时,一般在很重要的编码处,附加详细的打印比如函数名和行号,特别是在多人协同模块话开发时,会在debug时提供很多有用的信息。通常我会在提供公用的模块时候会加入这些打印信息,让使用的小伙伴知道调试遇到错误数据时能告诉我数据出错log信息,就能大致定位问题所在。