swift 中 ? 单问号 和 ?? 双问号 使用说明

 swift  swift 中 ? 单问号 和 ?? 双问号 使用说明已关闭评论
3月 202020
 

一、?单问号使用:

1. optional变量声明和使用时:

声明时: var number: Int?

操作时: number?.hashValue

2. 三元表达式使用

t = true

t ? print(“true”) : print(“false”)

 

二、??双问号使用:

??运算符可以用于判断变量/常量 的数值是否是 nil.不为 nil ,则取变量或者常量本身的值,如果是 nil 则使用后面的值替代,此方法可以非常方便的用于nil出现时赋默认值

如:

a = check ?? 3  //如果条件check为nil时,a被赋值为3

swift 闭包使用$0和$1等代替参数介绍

 swift  swift 闭包使用$0和$1等代替参数介绍已关闭评论
3月 162020
 

swift自动为闭包提供参数名缩写功能,可以直接通过$0$1、$2等…来表示闭包中的第一个第二个、第三个参数及后续的参数,并且对应的参数类型会根据函数类型来进行判断。如下代码:

  • 不使用$0 $1这些来代替
let nbs = [10,15,13,14,23,26]
snbs = nbs.sorted(by: { (a, b) -> Bool in
  return a < b
})
print("sorted -" + "\(snbs)") 
  • 使用$0,$1

let nbs = [10,15,13,14,23,26]
var snbs = nbs.sorted(by: {$0 < $1})
print(“sorted -” + “\(snbs)”)

可以发现使用$0、$1的话,参数类型可以自动判断,并且in关键字也可以省略,也就是只用写函数体就可以了, 确实方便。

swift闭包的@escaping和@noescape介绍与理解

 swift  swift闭包的@escaping和@noescape介绍与理解已关闭评论
3月 162020
 
swift闭包中关于@escaping与@noescape,下面的文章内容简洁易懂,转自链接:https://www.jianshu.com/p/905ba2a85455

OC的Block有一个坑,就是它的调用时机。

看下面两个方法,思考一下它们到底有什么区别。

- (void)methodAWithBlock:(void(^)())block {
    _block = block;
}
- (void)methodBWithBlock:(void(^)())block {
    block();
}
  • 第一个方法是将block作为实例变量存入当前的对象。常见的例子是异步的网络请求回调。
  • 第二个方法是立即调用这个传入来的block。常见的例子是数组的排序。

如果这是一个私有的类,@implementation看不到。那怎么判断这个block是拿来干什么的呢?答案是无法判断。

第一个方法里的Block是被当作实例变量接收了,例如该对象是A。对象A同时也被对象B持有,就成了这样B->A->block,这时block实现里引用了B,那么就变成了经典的B->A->block->B,引用循环。

可喜可贺的是如果我们不看内部实现,根本无法确切地判断出这个block是被对象A持有的。当然这是比较极端的例子,一般在声明方法时都会注明这个Block是作什么用的,只是在语言上无法防止这种不确定行为而已。

swift的闭包

有意思的是,swift在闭包上加强了静态检查。它有两个修饰词@escaping和@noescape。这个看代码就能说明。

    func addClosure(_ closure: @escaping ()->Void) {
        self.closure = closure
    }
    
    func doSomething(_ closure: ()->Void) {
        closure()
    }
  • 第一个方法加了@escaping,意味着“逃脱”,闭包的生命周期可以逃脱方法的作用域,在方法return后不会销毁,这意味着它的调用时机是不确定的,是异步的。一般用于异步网络请求。
  • 第二个没有修饰词,所以是默认的@noescape,这意味着该闭包不能超出方法的作用域,方法return后闭包就销毁了,所以它是安全的。

下面这种做法是会报错的,因为在方法doSomething返回后,闭包还存在于异步队列里等候调用。

    func doSomething(_ closure: ()->Void) {
        DispatchQueue(label: "queue").async {
            closure()
        }
    }

总结

看来swift作为编程语言在静态检查上比很多语言都强,但其实这么强的静态检查对养成好的编程习惯也是好事。

swift下类和结构体的比较(相同点与不同点,什么情况下使用)

 swift  swift下类和结构体的比较(相同点与不同点,什么情况下使用)已关闭评论
3月 122020
 

网上看到的一篇好文章,清楚明了的解释了swift下类与结构体的相同点和不同点,并说明了该在什么情况下使用,推荐! 来自: https://juejin.im/post/5b57e75ef265da0f87593513

结构体是构建代码所用的一种通用且灵活的构造体。我们可以使用完全相同的语法规则来为类和结构体定义属性(变量,常量)和添加方法。从而扩展类和结构体的功能。

与其他编程语言所不同的是,Swift并不要求你为自定义类和结构去创建独立的接口和实现文件。你所要做的是在一个单一文件中定义一个类或者结构体,系统将会自动生成面向其他代码的外部接口。

注: 通常一个的势力被称为对象。在Swift中,类和结构体的关系要比在其他语言中更加密切,本章中所讨论的部分功能都可以在类和结构体上。因此主要使用实例。内容包含类和结构体对比结构体和枚举是值类型类是引用类型类和结构体的选择字符串、数组和字典类型的赋值和复制行为


类和结构体对比

Swift中的类和结构体有很多共同点:

  • 定义属性用于储存值
  • 定义方法用于提供功能
  • 定义下标操作通过下标语法可以访问它们的值
  • 定义构造器用于生成初始化值
  • 通过扩展以增加默认实现的功能
  • 遵循协议以提供某种标准功能

与结构体相比,类还有如下的附加功能:

  • 继承允许一个类继承另一个类的特征
  • 类型转换允许在运行时检查和解释一个类实例的类型
  • 析构器允许一个类实例释放任何其所被分配的资源
  • 引用计数允许对一个类的多次引用

注: 结构体总是通过被复制的方式在代码中传递,不使用引用计数。

定义语法

类和结构体有着类似的定义方式。通过关键字classstruct来分别表示类和结构体,并在一对大括号中定义它们的具体内容:

class SomeClass {
	// 在这里定义类
}
struct SomeStructure {
	// 在这里定义结构体
} 

注: 在每次定义一个新类或者结构体的时候,实际上是定义了一个新的Swift类型。因此请使用UpperCamelCase这种命名方式命名(如SomeClassSomeStructure等),已便符合标准Swift类型的大写命名风格(如StringIntBool)。相反的,请使用lowerCamelCase这种方式为属性和方法命名(如:framerateincrementCount),以便和类型名区分。

以下是定义结构体和定义类的示例:

struct Resolution {
	var width = 0
	var height = 0
}
class VideoMode {
	var resolution = Resolution()
	var interlaced = false
	var frameRate = 0.0
	var name: String?
} 

在上面的示例中我们定义了一个名为Resolution的结构体,用来描述一个显示器的像素分辨率。这个结构体包含了两个名为widthheight的存储属性。存储属性是被捆绑和存储在类或结构体中的常量或变量。这两个属性被初始化为整数0的时候,它们会被推断为Int类型。

在上面的示例中我们还定义了一个名为VideoMode的类,用来描述一个视频显示器的特定模式。这个类包含了四个变量存储属性。第一个是分辨率,它被初始化为一个新的Resolution结构体的实例,属性类型被推断为Resolution。新的VideoMode实例同时还会初始化其他三个属性,它们分别是,初始化为falseinterlaced,初始值为0.0frameRate,以及可选值为Stringnamename属性会被自定赋值nil,为可选类型。

类和结构体实例

Resolution结构体和VideoMode类的定义仅描述了什么是ResolutionVideoMode。它们并没有描述一个特定的分辨率(resolution)和视频模式(video mode)。为了描述一个特定的分辨率或者视频模式,我们需要生成一个它们的实例。

let someResolution = Resolution()
let someVideoMode = VideoMode() 

结构体和类都使用构造器语法来生成新的实例。构造器语法的最简单形式是在结构体或者类的类型名称后跟随一对空括号,如Resolution()VideoMode()。通过这种方式所创建的类或者结构体实例,其属性均会被初始化为默认值。

属性访问

通过使用点语法,你可以访问实例的属性。其语法规则则是,实例名后面紧跟属性名:

print("The width of someResolution is \(someResolution.width)")
// 打印 "The width of someResolution is 0"

print("The width of someVideoMode is \(someVideoMode.resolution.width)")
// 打印 "The width of someVideoMode is 0"

someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
// 打印 "The width of someVideoMode is now 1280" 

注: 与OC语言不通的是,Swift允许直接设置结构体属性的子属性。上面的最后一个例子,就是直接设置了someVideoModeresolution属性的width这个子属性,以上操作并不需要重新为整个resolution属性设置新值。

结构体类型的成员逐一构造器

所有结构体都有一个自动生成的成员逐一构造器,用于初始化新结构体实例中成员的属性。新实例中各个属性的初始值可以通过属性的名称传递到成员逐一构造器之中,如下:

let vga = Resolution(width: 640, height: 480) 

与结构体不同,类实例没有默认的成员逐一构造器

结构体和枚举是值类型

值类型被赋予给一个变量、常量或者传递给一个函数的时候,其值会被拷贝

在Swift中,所有的基本类型:整数,浮点数,布尔值,字符串,数组,字典都是值类型,并且在底层都是以结构体的形式所实现。所有的结构体和枚举类型都是值类型。这意味着他们的实例,以及实例中所包含的任何值类型属性,在代码中传递都会被复制。

let hd = Resolution(width: 1920, height: 1080)
var cinema = hd 

在以上示例中,声明了一个名为hd的常量,其值为一个初始化为全高清视频分辨率(1920 像素宽,1080 像素高)的Resolution的实例。

下面,为了符合数码影院的放映需求(2048 像素宽,1080 像素高),cinemawidth 属性需要作如下修改:

cinema.width = 2048 

这里,将会显示 cinemawidth 属性确已改为了 2048

print("cinema is now  \(cinema.width) pixels wide")
// 打印 "cinema is now 2048 pixels wide" 

然而,初始的 hd 实例中 width 属性还是 1920

print("hd is still \(hd.width) pixels wide")
// 打印 "hd is still 1920 pixels wide" 

证明将hd赋值给cinema的时候,实际是将hd中所有储存的值进行拷贝,然后将拷贝的数据存储到新的cinema实例中。由于两者相互独立,因此将 cinemawidth 修改为 2048 并不会影响 hd 中的 width 的值。

枚举也遵循相同的行为准则:

enum CompassPoint {
	case North, South, East, West
}
var currentDirection = CompassPoint.West
let rememberedDirection = currentDirection
currentDirection = .East
if rememberedDirection == .West {
	print("The remembered direction is still .West")
}
// 打印 "The remembered direction is still .West" 

类是引用类型

与值类型不同,引用类型在被赋予到一个变量、常量挥着被传递到一个函数时,其值不会被拷贝。因此,引用的是已存在的实例本身而不是其拷贝。使用VideoMode举例如下:

let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0

let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0

print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// 打印 "The frameRate property of theEighty is now 30.0" 

以上示例中,声明了一个名为tenEighty的常量,其引用了一个VideoMode了的新实例。在之前的示例中,这个视频模式被赋予了HD分辨率(1920*1080)的一个拷贝(即 hd 实例)。同时设置为interlaced,命名为“1080i”。最为帧率为25.0帧每秒。然后,tenEighty 被赋予名为 alsoTenEighty 的新常量,同时对 alsoTenEighty 的帧率进行修改。

因为类是引用类型,所以 tenEightalsoTenEight 实际上引用的是相同的 VideoMode 实例。换句话说,它们是同一个实例的两种叫法。

通过查看 tenEightyframeRate 属性,我们会发现它正确的显示了所引用的 VideoMode 实例的新帧率,其值为 30.0

注: tenEightyalsoTenEighty 被声明为常量而不是变量。然而你依然可以改变 tenEighty.frameRatealsoTenEighty.frameRate,因为 tenEightyalsoTenEighty 这两个常量的值并未改变。它们并不“存储”这个 VideoMode 实例,而仅仅是对 VideoMode 实例的引用。所以,改变的是被引用的 VideoModeframeRate 属性,而不是引用 VideoMode 的常量的值。

恒等于运算符

因为类是引用类型,有可能有多个常量和变量在幕后同时引用同一个类实例。(对于结构体和枚举来说,这并不成立。因为它们作为值类型,在二笔赋予到常量、变量或者传递到函数时,其值总是被拷贝。)

Swift里有两个恒等于符号:等价于(===不等价于!== 来判定两个常量是否引用同一个类实例。

if tenEighty === alsoTenEighty {
	print("tenEighty and alsoTenEighty refer to the same Resolution instance.")
}
//打印 "tenEighty and alsoTenEighty refer to the same Resolution instance." 

注: 等于(==等价于(===)意义不同:

等于表示两个实例的值相等相同 等价于表示两个类类型的常量或者变量引用同一个类实例。

指针

在OC中,指针是来引用内存中的地址。一个引用某个引用类型实例的Swift常量或者变量,与OC的指针类似,但并不直接指向某个内存地址,也不要求使用(*)来表明你在创建一个引用。Swift中的这些引用于其他的常量或者变量的定义相同。

类和结构体的选择

在我们的代码中,我们可以使用类和结构体来定义我们的自定义数据类型。

然而,结构体实例总是通过值传递,类实例总是通过引用传递。这意味两者使用不同的任务。当你在考虑一个工程项目的数据和功能的时候,你需要决定每个数据结构是定义成类还是结构体。

按照通用准则,当符合一条或多条一下条件时,可以考虑结构体:

  • 该数据结构的主要目的是用来封装少量相关简单数据值
  • 有理由预计该数据结构的实例在被赋值或传递时,封装的数据将会被拷贝而不是被引用。
  • 该数据结构中存储的值类型属性,也应该被拷贝,而不是被引用。
  • 该数据结构不需要去继承另一个既有类型的属性或者行为。

字符串、数组、字典类型的赋值与复制行为

Swift中,许多基本类型如StringArrayDictionary类型均以结构体的形式实现。这意味着被赋值给新的常量,或者被传入函数或方法中时,它们的值会被拷贝。

OC中NSStringNSArrayNSDictionary类型均以类的形式实现,而并非结构体。它们在被赋值或者被传入函数或者方法时,不会发生值拷贝,而是传递现有实例的引用。

 

Swift自动布局SnapKit的使用介绍

 swift  Swift自动布局SnapKit的使用介绍已关闭评论
3月 042020
 
网上看到的一篇介绍snapkit使用的文章,推荐:https://www.jianshu.com/p/2bad53a2a180, 转发下:

简介

SnapKit,一个经典的Swift版的第三方库,专门用于项目的自动布局,目前在github上的stars就高达9340颗星,这是一个不小的数字,亦足以证明它存在的非凡意义和作用。作者认为,在iOS开发(swift)中,它是用于项目最优秀的自动布局的必选库之一。它的作者仍然是写Objective-C的第三方库Masonry的大牛 – @Robert Payne,开门见山,本文将详细介绍介绍SnapKit的详细使用和安装,相信对于初入门该库的开发者或许会有一定的帮助。

Snapkit的安装(CocoaPods)

环境配置要求:

  • iOS 8.0 / Mac OS X 10.11+
  • Xcode 8.0+
  • Swift 3.0+

安装

在已经安装CocoaPods的前提下, 即可以进行下列步骤。

  • 在你的项目工程里的Podfile文件里面添加

 

source 'https://github.com/CocoaPods/Specs.git'

platform :ios, '10.0'

use_frameworks!

target '这里是你的工程名称' do
    pod 'SnapKit', '~> 3.0'
end
  • 老生常谈,运行CocoaPods的如下命令

 

pod install

到此,不出意外的话,你已经将SnapKit集成到你的项目中了。然后,就开始讲怎么使用它了。

Snapkit的布局使用

1、 实现一个宽高为100,居于当前视图的中心的视图布局,示例代码如下

 

import UIKit
import SnapKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
     
        let testView = UIView()
        testView.backgroundColor = UIColor.cyan
        view.addSubview(testView)
        testView.snp.makeConstraints { (make) in
            make.width.equalTo(100)         // 宽为100
            make.height.equalTo(100)        // 高为100
            make.center.equalTo(view)       // 位于当前视图的中心
        }
        
    }
}

更简洁的写法可以

 

import UIKit
import SnapKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
     
        let testView = UIView()
        testView.backgroundColor = UIColor.cyan
        view.addSubview(testView)
        testView.snp.makeConstraints { (make) in
            make.width.height.equalTo(100)    // 链式语法直接定义宽高
            make.center.equalToSuperview()    // 直接在父视图居中
        }
        
    }
}

效果图

TestPicture1

2、View2位于View1内, view2位于View1的中心, 并且距离View的边距的距离都为20

 

import UIKit
import SnapKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
     
         // 黑色视图作为父视图
         let view1 = UIView()
         view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
         view1.center = view.center
         view1.backgroundColor = UIColor.black
         view.addSubview(view1)
         
         // 测试视图
         let view2 = UIView()
         view2.backgroundColor = UIColor.magenta
         view1.addSubview(view2)
         view2.snp.makeConstraints { (make) in
              make.top.equalToSuperview().offset(20)      // 当前视图的顶部距离父视图的顶部:20(父视图顶部+20)
              make.left.equalToSuperview().offset(20)     // 当前视图的左边距离父视图的左边:20(父视图左边+20)
              make.bottom.equalToSuperview().offset(-20)  // 当前视图的底部距离父视图的底部:-20(父视图底部-20)
              make.right.equalToSuperview().offset(-20)   // 当前视图的右边距离父视图的右边:-20(父视图右边-20)
         }
        
    }
}

更简洁的写法

 

import UIKit
import SnapKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
     
         // 黑色视图作为父视图
         let view1 = UIView()
         view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
         view1.center = view.center
         view1.backgroundColor = UIColor.black
         view.addSubview(view1)
         
         // 测试视图
         let view2 = UIView()
         view2.backgroundColor = UIColor.magenta
         view1.addSubview(view2)
         view2.snp.makeConstraints { (make) in
            make.edges.equalToSuperview().inset(UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20))
         }
        
    }
}

效果图

TestPicture2

3、布局一个视图view2, 让它的水平中心线小于等于另一个视图view2的左边,可以这样布局

 

import UIKit
import SnapKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
     
         // 黑色视图作为父视图
         let view1 = UIView()
         view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
         view1.center = view.center
         view1.backgroundColor = UIColor.black
         view.addSubview(view1)
        
         // 测试视图
         let view2 = UIView()
         view2.backgroundColor = UIColor.magenta
         view1.addSubview(view2)
         view2.snp.makeConstraints { (make) in
            // 让顶部距离view1的底部为10的距离
            make.top.equalTo(view1.snp.bottom).offset(10)
            // 设置宽、高
            make.width.height.equalTo(100)
            // 水平中心线<=view1的左边
            make.centerX.lessThanOrEqualTo(view1.snp.leading)
         }
        
    }
}

效果图

TestPicture

视图的属性说明

通过上面的大致简单布局我们对SnapKit有了一个基本的了解,那么, 它的布局属性是怎么来的呢?和原生的布局类有什么关联? 下面看一个SnapKit的布局属性表

从表中,我们知道,Snapkit的布局属性都是源自于系统的NSLayoutAttribute,那么,NSLayoutAttribute是个什么呢?其实,它在swift中是一个枚举,内部列举了很多布局属性诸如top、left、leading、centerX等,Snapkit的布局属性与它们都存在一一的对应关系。

Snapkit 的 greaterThanOrEqualTo 属性

如果想让视图View2的左边>=父视图View1的左边, 这时我们就可以用到greaterThanOrEqualTo

 

import UIKit
import SnapKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
         // 黑色视图作为父视图
         let view1 = UIView()
         view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
         view1.center = view.center
         view1.backgroundColor = UIColor.black
         view.addSubview(view1)
        
         // 测试视图
         let view2 = UIView()
         view2.backgroundColor = UIColor.magenta
         view1.addSubview(view2)
         view2.snp.makeConstraints { (make) in
            // 让顶部距离view1的底部为10的距离
            make.top.equalTo(view1.snp.bottom).offset(10)
            // 设置宽、高
            make.width.height.equalTo(100)
            // 水平中心线<=view1的左边
            make.left.greaterThanOrEqualTo(view1)
            
            // 或者, 和上面一行代码一样的效果
//            make.left.greaterThanOrEqualTo(view1.snp.left)
         }
    }
}

效果图

TestPicture

其实,greaterThanOrEqualTo这个属性有点多余,比如上面这行代码 make.left.greaterThanOrEqualTo(view1) , 我们可以换成 make.left.equalToSuperview()make.left.equalTo(view1.snp.left), 效果是一样的,也就是说,一般情况下 >=<= 我们都可以直接用equalTo来代替!

SnapKit的greaterThanOrEqualTo和lessThanOrEqualTo联合使用

当我们想要让某个视图的width或height大于等于某个特定的值,小于等于某个特定的值的时候,一般而言,Snapkit会以greaterThanOrEqualTo为准,这里举一个width的例子,为了方便,这里指贴出了viewDidLoad中的代码(其他没必要)

 

 // 黑色视图作为父视图
 let view1 = UIView()
 view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
 view1.center = view.center
 view1.backgroundColor = UIColor.black
 view.addSubview(view1)
    
 // 测试视图
 let view2 = UIView()
 view2.backgroundColor = UIColor.magenta
 view1.addSubview(view2)
 view2.snp.makeConstraints { (make) in
    make.width.lessThanOrEqualTo(300)
    make.width.greaterThanOrEqualTo(200)
    make.height.equalTo(100)
    make.center.equalToSuperview()
 }

接着,我们来看一下效果图

Test6

很明显,最后的宽度是以make.width.greaterThanOrEqualTo(200)为标准的,也可以这样的,在同时使用两者的情况下,greaterThanOrEqualTo的优先级略比lessThanOrEqualTo的优先级高。值得一提的是, 在上面的例子中,如果我们只设置make.width.lessThanOrEqualTo(300),那么view2是不会显示出来的,因为view2不知道你要表达的是要显示多少,小于等于300,到底是100还是200呢?(这里指针对width和height)所以它不能确定这个约束的值,但是,如果我们单独设置make.width.greaterThanOrEqualTo(200),那么就和上面的效果一样,因为它会以200为标准布局约束!

lessThanOrEqualTo的用于上、下、左、右

如果我们想要视图view2的左边 <= view1.left + 10, 那么就可以直接用到lessThanOrEqualTo布局了,看下面这个例子

 

 // 黑色视图作为父视图
 let view1 = UIView()
 view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
 view1.center = view.center
 view1.backgroundColor = UIColor.black
 view.addSubview(view1)
    
 // 测试视图
 let view2 = UIView()
 view2.backgroundColor = UIColor.magenta
 view1.addSubview(view2)
 view2.snp.makeConstraints { (make) in
    make.left.lessThanOrEqualTo(20)     // <= 父视图的左边+20
    make.right.equalTo(-40)             // = 父视图的右边-40
    make.height.equalTo(100)
    make.center.equalToSuperview()
 }

效果图

Test7

Snapkit布局的灵活性

  • Snapkit布局灵活性很强, 我们看下面的例子, 他们的效果是一样的

 

make.left.equalToSuperview().offset(10)
make.left.equalTo(10)
make.left.equalTo(view1.snp.left).offset(10)
  • 设置视图的大小(width,height),他们效果是一样的

 

make.width.height.equalTo(100)
或
make.width.equalTo(100)
make.height.equalTo(100)
或
make.size.equalTo(CGSize(width: 100, height: 100))
  • 优先级(priority)

我们来看一下Snapkit的优先级设置, 优先级都是附加在约束链的末尾处,看下面的使用方法

 

// 黑色视图作为父视图
let view1 = UIView()
view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
view1.center = view.center
view1.backgroundColor = UIColor.black
view.addSubview(view1)
    
// 测试视图
let view2 = UIView()
view2.backgroundColor = UIColor.magenta
view1.addSubview(view2)
view2.snp.makeConstraints { (make) in
    make.width.equalTo(100).priority(666)
    make.width.equalTo(250).priority(999)
    make.height.equalTo(111)
    make.center.equalToSuperview()
}

效果图

priorityimage

从上面我们可以知道, 我们设置了两个优先级:make.width.equalTo(100).priority(666) make.width.equalTo(250).priority(999), 那运行结果是一个哪个为准呢?显然是以优先级为 999的为准,因为 priority(999)>priotity(666), 所以在使用Snapkit的过程中,有时我们可以使用优先级priority来设置我们的约束, 另外,值得一提的是,SnapKit的优先级最大值只能是 1000, 如果优先级的数值超过1000,则运行时就会Crash!这里要尤其注意。

</br>

  • 更新约束(引用约束)

</br>

我们可以通过保存某一个约束布局来更新相应的约束,或者保存一组约束布局到一个数组中更新约束, 具体看下面代码

 

// 保存约束(引用约束)
var updateConstraint: Constraint?
    
override func viewDidLoad() {
    super.viewDidLoad()
    
    // 黑色视图作为父视图
    let view1 = UIView()
    view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
    view1.center = view.center
    view1.backgroundColor = UIColor.black
    view.addSubview(view1)

    // 测试视图
    let view2 = UIView()
    view2.backgroundColor = UIColor.magenta
    view1.addSubview(view2)
    view2.snp.makeConstraints { (make) in
        make.width.height.equalTo(100)  // 宽高为100
        self.updateConstraint = make.top.left.equalTo(10).constraint   // 距离父视图上、左为10
    }
    
    let updateButton = UIButton(type: .custom)
    updateButton.backgroundColor = UIColor.brown
    updateButton.frame = CGRect(x: 100, y: 80, width: 50, height: 30)
    updateButton.setTitle("更新", for: .normal)
    updateButton.addTarget(self, action: #selector(updateConstraintMethod), for: .touchUpInside)
    view.addSubview(updateButton)
}
    
// 更新约束
func updateConstraintMethod() {
    self.updateConstraint?.update(offset: 50)   // 更新距离父视图上、左为50
}
testgif1
  • 更新约束(snp.updateConstraints)

说起这个updateConstraints, 我也懵逼过,那么它到底有何作用呢?又怎么用呢?它和一开始就使用的makeConstraints又有什么明确的区别呢?请继续往下看

说明1:如果你这是更新某个约束或某几个约束的常量值,你就可以使用updateConstraints而不是makeConstraints

说明2:这个也是苹果推荐用来添加或更新约束的方式

说明3:这个方法可以调用多次,会相应setNeedsUpdateConstraints, 在控制器中,可以写在override func updateViewConstraints()方法里面(当然也可以写在你想要什么时候更新的点击事件里面)

 

import UIKit
import SnapKit

class ViewController: UIViewController {

    lazy var blackView = UIView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        blackView.backgroundColor = UIColor.black
        view.addSubview(blackView)
        blackView.snp.makeConstraints { (make) in
            
            // 四个约束确定位置和大小
            make.width.equalTo(100)
            make.height.equalTo(150)
            make.top.equalTo(10)
            make.centerX.equalToSuperview()
        }
 
    }
    
    override func updateViewConstraints() {
        
        blackView.snp.updateConstraints { (make) in
            // 更新距离父视图顶部的约束(从 10 ---> 300 )
            make.top.equalTo(300)
        }
        
        // 根据苹果,调用父类应该放在末尾
        super.updateViewConstraints()
    }
}

注意: 从上面的代码中我们很明确地知道, blackView通过widthheighttopcenterX确定了它本身的大小和位置, 但是, 在 run 出来之后,它的top改变了距离, 从 10 变成了 300,其他三个约束保持不变, 见下图效果:

test10

显而易见, 除了top约束, 其他都没有改变! 也就是说,约束被更新(相当于系统升级一样,是一个道理)

现在,我们通过UIButton的点击事件来证明一下制作约束makeConstraintsupdateConstraints具体的区别在哪里?

 

lazy var blackView = UIView()
    
override func viewDidLoad() {
    super.viewDidLoad()
    
    blackView.backgroundColor = UIColor.black
    view.addSubview(blackView)
    blackView.snp.makeConstraints { (make) in
        
        make.width.equalTo(100)
        make.height.equalTo(150)
        make.top.equalTo(50)
        make.centerX.equalToSuperview()
    }
    
    let btn = UIButton(type: .custom)
    btn.backgroundColor = UIColor.brown
    btn.frame = CGRect(x: 100, y: 200, width: 60, height: 30)
    btn.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
    view.addSubview(btn)
 
}
    
// 点击更新/制作约束
func buttonAction() {
    
    blackView.snp.makeConstraints { (make) in
        make.width.height.equalTo(20)
        make.top.equalTo(300)
    }
    
}

先看效果图

点击事件发生前(图1):

test11

点击事件发生后(图2)

test12

图3

test14

图4

test13

上面,我们知道,视图 blackView一开始是由四个约束确定位置和大小,在点击事情响应后,我们又给 blackView 制作(记住,是制作,而不是重做,两者有明确的区别)了3个约束,分别是 widthheighttop, 那么这样做问题出现在哪里呢? 第一, 点击事情发生前(图1), 在点击事件发生后(见图2), 我们发现,blackViewwidthheight约束改变了,但是 top却没有改变,还是原来的距离父视图顶部 50 的距离, 原因在于,我们在原来的约束基础上又添加了多余的约束, 也就是说,约束从4个变成了7个(见图3左边constraints), 这样就产生了约束不明确,进而导致snapkit的警告(见图4), 这样布局显然是不可取的,在项目中这样做极其危险,甚至可能会导致异常奔溃!!!!

现在, 我们该将点击事件中的约束布局从makeConstraints改变成updateConstraints来试试两者有什么区别(下面只添加了点击事件的代码,其他事重复的就不多此一举了)

 

func buttonAction() {
    // 注意这里是updateConstraints, 而不是makeConstraints
    blackView.snp.updateConstraints { (make) in
        make.width.height.equalTo(20)
        make.top.equalTo(300)
    }
    print("这里试试snapkit有没有报警告")
}

接着看点击事件后的效果图

图5

test5

图6

test6

图7

test7

发现没有,在将makeConstraints改变成updateConstraints之后,约束还是4个,snapkit没有报警告,点击事件中的widthheighttop全部起了作用,而这就是两者的本质区别makeConstraints是制作约束,在原来的基础上再添加另外的约束,也就是画蛇添足,约束增加,视图布局就有不确定性,从而有些约束起作用,有些不起作用(如上面的top),snapkit报警告!!!而updateConstraints是更新约束,改变原有约束,约束不会增加,没经过updateConstraints处理的保持原有约束,经过处理就更新约束,约束不会减少,snapkit不会产生警告,这是正常标准的更新约束的正确方式!!!

</br>

  • 重做约束(remakeConstraints)

重做约束的本质就是:去掉已有的所有约束, 重新做约束,记住,是做约束, 也就是说, 使用了remakeConstraints后,重做的约束必须要能确定相应视图的大小位置, 之前makeConstraints的约束已经不会存在了,完全销毁!!!

 

// 点击更新/制作约束
func buttonAction() {
    
    // 注意这里是 remakeConstraints
    blackView.snp.remakeConstraints { (make) in
        make.width.height.equalTo(20)
        make.top.equalTo(300)
    }
    
    print("这里试试snapkit有没有报警告")
}

效果图

图(1)

test18

图(2)

test19

图(3)

test20

我们看到, blackView重做了约束, 之前的约束不起任何作用,由于它在重做约束后只有 3 个约束分别是 widthheighttop, 但是这里有一个问题,就是这 3 个约束只能确定大小,无法确定视图的位置, 所以在水平方向上或者左右缺少一个布局条件, 故 blackView整体视图的x紧靠左边(默认)! 另外我们发现, 在图(3)中,右上角出现了一个感叹号“!”, 那是因为告诉你缺少了一个约束条件:x-xcode-debug-views://7f81fcbc7900: runtime: Layout Issues: Horizontal position is ambiguous for UIView.

小结

通过以上学习,我们或深或浅地学习了布局三方库SnapKit的使用, 我相信,只要将上述布局会使用,并且懂得布局的原则和道理,基本上就可以“高枕无忧”了,至于涉及动态布局、动画布局等知识,后续有时间会更新文档。

</br>

 

swift内存管理中WEAK 和 UNOWNED介绍与使用

 swift  swift内存管理中WEAK 和 UNOWNED介绍与使用已关闭评论
1月 062020
 

网上看到的一篇关于swift 的内存管理的weak和unowned介绍和使用的文章,觉得写的挺好,分享下。文章来源地址: onevcat

 

不管在什么语言里,内存管理的内容都很重要,所以我打算花上比其他 tip 长一些的篇幅仔细地说说这块内容。

Swift 是自动管理内存的,这也就是说,我们不再需要操心内存的申请和分配。当我们通过初始化创建一个对象时,Swift 会替我们管理和分配内存。而释放的原则遵循了自动引用计数 (ARC) 的规则:当一个对象没有引用的时候,其内存将会被自动回收。这套机制从很大程度上简化了我们的编码,我们只需要保证在合适的时候将引用置空 (比如超过作用域,或者手动设为 nil 等),就可以确保内存使用不出现问题。

但是,所有的自动引用计数机制都有一个从理论上无法绕过的限制,那就是循环引用 (retain cycle) 的情况。

什么是循环引用

虽然我觉得循环引用这样的概念介绍不太应该出现在这本书中,但是为了更清晰地解释 Swift 中的循环引用的一般情况,这里还是简单进行说明。假设我们有两个类 A 和 B, 它们之中分别有一个存储属性持有对方:

class A {
    let b: B
    init() {
        b = B()
        b.a = self
    }

    deinit {
        print("A deinit")
    }
}

class B {
    var a: A? = nil
    deinit {
        print("B deinit")
    }
}

在 A 的初始化方法中,我们生成了一个 B 的实例并将其存储在属性中。然后我们又将 A 的实例赋值给了 b.a。这样 a.b 和 b.a 将在初始化的时候形成一个引用循环。现在当有第三方的调用初始化了 A,然后即使立即将其释放,A 和 B 两个类实例的 deinit 方法也不会被调用,说明它们并没有被释放。

var obj: A? = A()
obj = nil
// 内存没有释放

因为即使 obj 不再持有 A 的这个对象,b 中的 b.a 依然引用着这个对象,导致它无法释放。而进一步,a 中也持有着 b,导致 b 也无法释放。在将 obj 设为 nil 之后,我们在代码里再也拿不到对于这个对象的引用了,所以除非是杀掉整个进程,我们已经永远也无法将它释放了。多么悲伤的故事啊..

在 Swift 里防止循环引用

为了防止这种人神共愤的悲剧的发生,我们必须给编译器一点提示,表明我们不希望它们互相持有。一般来说我们习惯希望 “被动” 的一方不要去持有 “主动” 的一方。在这里 b.a 里对 A 的实例的持有是由 A 的方法设定的,我们在之后直接使用的也是 A 的实例,因此认为 b 是被动的一方。可以将上面的 class B 的声明改为:

class B {
    weak var a: A? = nil
    deinit {
        print("B deinit")
    }
}

在 var a 前面加上了 weak,向编译器说明我们不希望持有 a。这时,当 obj 指向 nil 时,整个环境中就没有对 A 的这个实例的持有了,于是这个实例可以得到释放。接着,这个被释放的实例上对 b 的引用 a.b 也随着这次释放结束了作用域,所以 b 的引用也将归零,得到释放。添加 weak 后的输出:

A deinit
B deinit

可能有心的朋友已经注意到,在 Swift 中除了 weak 以外,还有另一个冲着编译器叫喊着类似的 “不要引用我” 的标识符,那就是 unowned。它们的区别在哪里呢?如果您是一直写 Objective-C 过来的,那么从表面的行为上来说 unowned 更像以前的 unsafe_unretained,而 weak 就是以前的 weak。用通俗的话说,就是 unowned 设置以后即使它原来引用的内容已经被释放了,它仍然会保持对被已经释放了的对象的一个 “无效的” 引用,它不能是 Optional 值,也不会被指向 nil。如果你尝试调用这个引用的方法或者访问成员属性的话,程序就会崩溃。而 weak 则友好一些,在引用的内容被释放后,标记为 weak 的成员将会自动地变成 nil (因此被标记为 @weak 的变量一定需要是 Optional 值)。关于两者使用的选择,Apple 给我们的建议是如果能够确定在访问时不会已被释放的话,尽量使用 unowned,如果存在被释放的可能,那就选择用 weak

我们结合实际编码中的使用来看看选择吧。日常工作中一般使用弱引用的最常见的场景有两个:

  1. 设置 delegate 时
  2. 在 self 属性存储为闭包时,其中拥有对 self 引用时

前者是 Cocoa 框架的常见设计模式,比如我们有一个负责网络请求的类,它实现了发送请求以及接收请求结果的任务,其中这个结果是通过实现请求类的 protocol 的方式来实现的,这种时候我们一般设置 delegate 为 weak

// RequestManager.swift
class RequestManager: RequestHandler {

    @objc func requestFinished() {
        print("请求完成")
    }

    func sendRequest() {
        let req = Request()
        req.delegate = self

        req.send()
    }
}

// Request.swift
@objc protocol RequestHandler {
    optional func requestFinished()
}

class Request {
    weak var delegate: RequestHandler!;

    func send() {
        // 发送请求
        // 一般来说会将 req 的引用传递给网络框架
    }

    func gotResponse() {
        // 请求返回
        delegate?.requestFinished?()
    }
}

req 中以 weak 的方式持有了 delegate,因为网络请求是一个异步过程,很可能会遇到用户不愿意等待而选择放弃的情况。这种情况下一般都会将 RequestManager 进行清理,所以我们其实是无法保证在拿到返回时作为 delegate 的 RequestManager 对象是一定存在的。因此我们使用了 weak 而非 unowned,并在调用前进行了判断。

闭包和循环引用

另一种闭包的情况稍微复杂一些:我们首先要知道,闭包中对任何其他元素的引用都是会被闭包自动持有的。如果我们在闭包中写了 self 这样的东西的话,那我们其实也就在闭包内持有了当前的对象。这里就出现了一个在实际开发中比较隐蔽的陷阱:如果当前的实例直接或者间接地对这个闭包又有引用的话,就形成了一个 self -> 闭包 -> self 的循环引用。最简单的例子是,我们声明了一个闭包用来以特定的形式打印 self 中的一个字符串:

class Person {
    let name: String
    lazy var printName: ()->() = {
        print("The name is \(self.name)")
    }

    init(personName: String) {
        name = personName
    }

    deinit {
        print("Person deinit \(self.name)")
    }
}

var xiaoMing: Person? = Person(personName: "XiaoMing")
xiaoMing!.printName()
xiaoMing = nil
// 输出:
// The name is XiaoMing,没有被释放

printName 是 self 的属性,会被 self 持有,而它本身又在闭包内持有 self,这导致了 xiaoMing 的 deinit 在自身超过作用域后还是没有被调用,也就是没有被释放。为了解决这种闭包内的循环引用,我们需要在闭包开始的时候添加一个标注,来表示这个闭包内的某些要素应该以何种特定的方式来使用。可以将 printName 修改为这样:

lazy var printName: ()->() = {
    [weak self] in
    if let strongSelf = self {
        print("The name is \(strongSelf.name)")
    }
}

现在内存释放就正确了:

// 输出:
// The name is XiaoMing
// Person deinit XiaoMing

如果我们可以确定在整个过程中 self 不会被释放的话,我们可以将上面的 weak 改为 unowned,这样就不再需要 strongSelf 的判断。但是如果在过程中 self 被释放了而 printName 这个闭包没有被释放的话 (比如 生成 Person 后,某个外部变量持有了 printName,随后这个 Persone 对象被释放了,但是 printName 已然存在并可能被调用),使用 unowned 将造成崩溃。在这里我们需要根据实际的需求来决定是使用 weak 还是 unowned

这种在闭包参数的位置进行标注的语法结构是将要标注的内容放在原来参数的前面,并使用中括号括起来。如果有多个需要标注的元素的话,在同一个中括号内用逗号隔开,举个例子:

// 标注前
{ (number: Int) -> Bool in
    //...
    return true
}

// 标注后
{ [unowned self, weak someObject] (number: Int) -> Bool in
    //...
    return true
}

UIViewContentMode 中ScaleToFill、AspectFit、AspectFill等区别

 swift  UIViewContentMode 中ScaleToFill、AspectFit、AspectFill等区别已关闭评论
12月 262019
 

UIViewContentMode/ content Mode 中ScaleToFill、AspectFit、AspectFill有什么区别,见下面两张图就一目了然了:

swift 下 init()与init?()区别

 swift  swift 下 init()与init?()区别已关闭评论
12月 252019
 

swift 的类代码里,init()是正常初始化类,但如果初始数据不满足要求,或需要返回nil时,怎么处理呢? init?()就是派这个用场地!

引用https://www.hackingwithswift.com/example-code/language/whats-the-difference-between-init-and-init的内容,下面说的很详细,也有例子举例,大家一看就明白:

It’s the job of a regular Swift initializer to create a fully fledged instance of a new type, however sometimes the data that has been provided is insufficient or incorrect, and creation can’t proceed.

For example, consider this code:

struct Person {
    var ssn: String

    init(socialSecurityNumber: String) {
        self.ssn = socialSecurityNumber
    }
}

let person = Person(socialSecurityNumber: "111-11-1111")
print(person)

That defines a Person struct that can be created using a nine-digit social security number, then creates an instance of that struct.

But what should happen here?

let person = Person(socialSecurityNumber: "FISH")

In that instance we’re passing an invalid social security number, so really we expect creating a Person to fail.

This is where failable initializers come in: they are written as init?(), and can return nil rather than a value if something goes wrong during creation. For example, we could write a quick check to make sure the social security number is more or less correct like this:

struct Person {
    var ssn: String

    init?(socialSecurityNumber: String) {
        if socialSecurityNumber.count < 11 {
            return nil
        } else {
            self.ssn = socialSecurityNumber
        }
    }
}

Notice the initializer is now called init?() to reflect that it returns an optional – the process might return nil if the creation fails. The logic is pretty simple: if there are 11 digits we assume it’s correct, otherwise we return nil. Note: if you really wanted to validate that number you’d need to use a regular expression.

XCODE中不同添加方式下获取文件/图片的路径的方法(Bundle.main.path,Bundle)

 swift, xcode  XCODE中不同添加方式下获取文件/图片的路径的方法(Bundle.main.path,Bundle)已关闭评论
12月 202019
 

XCODE中获取添加的文件的路径的3个方法,以下以显示图片测试获取图片路径的方法:

 

测试的UIViewController的demo源码(TestImageViewController.swift),注释中详细描述3个不同添加文件方式下获取路径的方法,GOOD LUCK!

 

import AVFoundation

 

class TestImageViewController: UIViewController {

    

    lazy var imageView: UIImageView = {

       

        let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))

        //方法1:  选择Targets中项目 –> build Phases中 –> copy Bundle Resources点击下面的+ 直接添加的alalay.jpg

        //let testImage = UIImage(contentsOfFile: Bundle.main.path(forResource: “alalay”, ofType: “jpg”)!)

        

        //方法2:  新建目录任意命名如imgs,将测试图片alalay.jpg文件拖入imgs目录后,将imgs目录名称修改为imgs.bundle,将这个imgs.bundle拖入xcode项目

        //let testImage = UIImage(contentsOfFile: Bundle.main.path(forResource: “imgs”, ofType: “bundle”)! + “/” + “alalay.jpg”)

        

        //方法3:   将alalay.jpg图片放在Assets.xcassets中

        let testImage = UIImage(named:“alalay.jpg”)!

        

        imageView.image = testImage

        imageView.contentMode = .scaleAspectFit

        return imageView

    }()

    

    override func viewDidLoad() {

        super.viewDidLoad()

        view.addSubview(imageView)

    }

}

GPUImage2(GPUImage swift版本) 使用pod安装

 swift  GPUImage2(GPUImage swift版本) 使用pod安装已关闭评论
12月 192019
 

GPUImage2常见的安装方法是project导入和静态库方法,实在太麻烦了,在GPUImage2 github的issue中有人提供了一个pod安装方法(详见:https://github.com/BradLarson/GPUImage2/issues/1):

 

在Podfile文件所在的同级目录下新建一个podspec文件,如:GPUImage2.podspec, 并且输入下面内容:

Pod::Spec.new do |s|
s.name = ‘GPUImage2’
s.version = ‘0.1.0’
s.license = ‘BSD’
s.summary = ‘An open source iOS framework for GPU-based image and video processing.’
s.homepage = ‘https://github.com/BradLarson/GPUImage2’
s.author = { ‘Brad Larson’ => ‘[email protected]’ }

# This commit on that fork of GPUImage should contain just upgrades needed for Swift 4 compatibility. See https://github.com/BradLarson/GPUImage2/pull/212
# Replace with https://github.com/BradLarson/GPUImage2.git when merged
# into BradLarson’s repository.
s.source = { :git => ‘https://github.com/andrewcampoli/GPUImage2’, :commit => ‘148c84e6b4194daeba122e77449f5ee9c8188161’ }

s.source_files = ‘framework/Source/**/*.{swift}’
s.resources = ‘framework/Source/Operations/Shaders/*.{fsh}’
s.requires_arc = true
s.xcconfig = { ‘CLANG_MODULES_AUTOLINK’ => ‘YES’, ‘OTHER_SWIFT_FLAGS’ => “$(inherited) -DGLES”}

s.ios.deployment_target = ‘8.0’
s.ios.exclude_files = ‘framework/Source/Mac’, ‘framework/Source/Linux’, ‘framework/Source/Operations/Shaders/ConvertedShaders_GL.swift’
s.frameworks = [‘OpenGLES’, ‘CoreMedia’, ‘QuartzCore’, ‘AVFoundation’]

end

 

并且在Podfile中加入:
pod ‘GPUImage2’, :podspec => ‘./GPUImage2.podspec’

 

然后pod install 即可

swift在iOS 12 下deprecated 废弃的接口替换收集

 swift  swift在iOS 12 下deprecated 废弃的接口替换收集已关闭评论
9月 172019
 

ios 12后 swift中一些方法被废弃掉了,代以其它的方法,有些xcode提示直接使用fix就完成,有些变化比较大,收集了下自己碰到的,举几个例子:

1、***NSKeyedArchiver.archiveRootObject***
老方法:
NSKeyedArchiver.archiveRootObject(allItems, toFile: itemArchiveURL.path)

新方法:
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: allItems, requiringSecureCoding: false)
try data.write(to: itemArchiveURL)
} catch {
print(error)
}

2、***NSKeyedUnarchiver.unarchiveObject***
老方法:
if let archivedItems = NSKeyedUnarchiver.unarchiveObject(withFile: itemArchiveURL.path) as? [Item] {
allItems = archivedItems
}
新方法:
do {
let data = try Data(contentsOf: itemArchiveURL)
if let archivedItems = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? [Item] {
allItems = archivedItems
}
} catch {
print(“unarchive failure in init”)
}

3、**UIImageJPEGRepresentation**
老方法:
let imageData = UIImageJPEGRepresentation(image, 0.75)

新方法:
let imageData = image.jpegData(compressionQuality: 0.75)
类似的, UIImagePNGRepresentation 替换成 pngData().

Swift中的!和?, 问号 和叹号 的使用和区别介绍

 swift  Swift中的!和?, 问号 和叹号 的使用和区别介绍已关闭评论
8月 132019
 

一般我们在一下两种情况下会遇到!和?的使用
1.声明变量时

var number :Int?
var str : String

2.在对变量操作时

number?.hashValue
str!.hashValue

由于这两种情况的意义不同,所以分开进行解释:

1.声明变量时
在声明一个变量时如果不手动初始化,Swift不会自动初始化该变量为一个默认值的。

var a : String
var b = a           //error:因为没有初始化a,a没有值

但是对于Optional的变量则不同,Optional的变量在声明时如果不初始化,Swift会自动将该变量初始化为nil。声明变量时在类型后添加?或者!就是告诉编译器这个一个Optional的变量,如果没有初始化,你就将其初始化为nil

var a : String?           //a 为nil
var b : String!           //b 为nil
var a_test = a            //a_test为nil
var b_test = b            //b_test为nil

但是这两者之间还是有一些区别的,介绍后面之后再讲。
Optional事实上是一个枚举类型,从下图可以看出,Optional包含None和Some两种类型,而nil就是Optional.None,非nil就是Optional.some。如果Optional变量在声明时不初始化,Swift会调用init()来初始化变量为nil,而用非nil的值初始化变量时,会通过Some(T)把该原始值包装,所以在之后使用的时候我们需要通过解包取出原始值才能使用。

2.对变量进行操作时

var arrayCount = dataList?.count

这时问号的意思类似于isResponseToSelector,即如果变量是nil,则不能响应后面的方法,所以会直接返回nil。如果变量非nil,就会拆Some(T)的包,取出原始值执行后面的操作。

var arrayCount = dataList!.count

这里的叹号和之前的问号则不同,这里表示我确定dataList一定是非nil的,所以直接拆包取出原始值进行处理。因此此处如果不小心让dataList为nil,程序就会crash掉。

回到上面声明时?和!区别的问题上去
声明变量时的?只是单纯的告诉Swift这是Optional的,如果没有初始化就默认为nil,而通过!声明,则之后对该变量操作的时候都会隐式的在操作前添加一个!。

总结

  1. 问号?
    a.声明时添加?,告诉编译器这个是Optional的,如果声明时没有手动初始化,就自动初始化为nil
    b.在对变量值操作前添加?,判断如果变量时nil,则不响应后面的方法。
  2. 叹号!
    a.声明时添加!,告诉编译器这个是Optional的,并且之后对该变量操作的时候,都隐式的在操作前添加!
    b.在对变量操作前添加!,表示默认为非nil,直接解包进行处理

 

来自:https://segmentfault.com/a/1190000000533936。

Swift闭包的用法,简介, IN用法

 swift  Swift闭包的用法,简介, IN用法已关闭评论
8月 072019
 

非常好的资料https://www.jianshu.com/p/7dcecea05dbb

 

本篇将详细总结介绍Swift闭包的用法;
闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift中的闭包与C和 Objective-C中的代码块(blocks)以及其他一些编程语言中的匿名函数比较相似。

主要内容:
1.闭包表达式
2.闭包的使用与优化
3.值捕获
4.逃逸闭包
5.自动闭包

一、闭包表达式

Swift闭包的三种存在形式:
1.全局函数是一个有名字但不会捕获任何值的闭包
2.嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
3.闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包
闭包表达式的语法一般有如下的一般形式:

{ (parameters) -> returnType in
    statements
}

说明:
1.闭包的外层是一个大括号,先写的参数和返回值,然后操作部分之前使用in;
2.闭包就相当于OC中的block, 也可以看做是匿名函数;
3.闭包表达式参数可以是in-out参数,但不能设定默认值;
4.闭包的函数体部分由关键字in引入,该关键字表示闭包参数和返回值类型已经完成,闭包函数体开始;

二、闭包的使用与优化

下面,我们使用Swift标准库中的sorted(by:)方法来测试闭包的使用。sorted(by:)方法允许外部传入一个用于排序的闭包函数将已知类型数组中的值进行排序,完成排序之后,该方法会返回一个与原数组大小相同,包含同类型元素已正确排序的新数组:

//定义一个整型数组
var someInts: [Int] = [5,9,7,0,1,3]
//定义一个排序函数
func biggerNumFirst(num1:Int, num2:Int) -> Bool{
    return num1 > num2
}
//普通用法:将biggerNumFirst函数传入sorted函数,实现排序
var sortInts = someInts.sorted(by: biggerNumFirst)
print(sortInts)     //[9, 7, 5, 3, 1, 0]

//闭包用法:为sorted函数参数传入一个闭包,实现排序
sortInts = someInts.sorted(by:{ (a:Int, b:Int) -> Bool in
    return a > b
})
print(sortInts)     //[9, 7, 5, 3, 1, 0]

注意:因为闭包不会在其他地方调用,所以不使用外部参数名

闭包使用起来十分灵活,我们可以在某些特定情况下对齐进行优化,下面是对上述闭包的优化:

2.1.根据上下文推断类型,省略参数类型与括号

由于排序闭包函数是作为sorted(by:)方法的参数传入的,Swift可以推断其类型和返回值类型。所以sorted(by:)方法被一个Int类型的数组调用,其参数必定是(Int,Int)->Bool类型的函数。最后,根据上下文推断类型,我们可以省略参数类型和参数周围的括号。

sortInts = someInts.sorted(by: {a,b in
    return a > b
})
print(sortInts)

2.2.对于不会发生歧义的闭包,可将其写成一行

sortInts = someInts.sorted(by:{a,b in return a > b})
print(sortInts)

2.3.单行闭包表达式,省略return关键字

省略return关键字的条件:
sorted(by:)方法的参数类型明确了闭包必须返回一个Bool类型值
单行闭包表达式中,其返回值类型没有歧义

sortInts = someInts.sorted(by: {a,b in a > b})
print(sortInts)

2.4.使用参数名缩写(不推荐使用)

Swift 自动为内联闭包提供了参数名称缩写功能,你可以直接通过$0,$1,$2 来顺序调用闭包的参数,以此类推。
如果我们在闭包表达式中使用参数名称缩写, 我们就可以在闭包定义中省略参数列表,并且对应参数名称缩写的类型会通过函数类型进行推断。in关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成:

sortInts = someInts.sorted(by: {$0>$1})
print(sortInts)

2.5.使用运算符简化闭包(不推荐使用)

Swift的Int类型定义了关于大于号(>)的字符串实现,其作为一个函数接受两个Int类型的参数并返回Bool类型的值。而这正好与sorted(by:)方法的参数需要的函数类型相符合。可以使用大于号来代替闭包

sortInts = someInts.sorted(by: >)
print(sortInts)

2.6.尾随闭包,解决长闭包的书写问题

如果你需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包来增强函数的可读性。
尾随闭包的写法:将闭包书写在函数括号之后,函数会支持将其作为最后一个参数调用,使用尾随闭包,不需要写出它的参数标签。

func someFunctionThatTakesAClosure(closure: () -> Void) {
    //函数体部分
    closure(); //调用闭包
}

//不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure(closure: {
    //闭包主体部分
})

//使用尾随闭包进行函数调用
someFunctionThatTakesAClosure() {
    //闭包主体部分
}

//注意:如果闭包表达式是函数或方法的唯一参数,则当你使用尾随闭包时,你甚至可以把 () 省略掉:
someFunctionThatTakesAClosure {
    print("Hello World!")    //打印:Hello World!
}

总结Swift闭包主要的四种优化方法:
1.利用上下文推断参数和返回值类型,省略参数类型与括号
2.隐式返回单表达式闭包,即单表达式闭包可以省略return关键字
3.参数名称缩写
4.尾随闭包语法

三、值捕获

闭包可以在其被定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。Swift会为你管理在捕获过程中涉及到的所有内存操作。

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

代码分析:
1.makeIncrementer函数以amount为参数,以()->Int作为返回值类型,其函数体中还嵌套了另一个函数incrementer。
2.如果我们把incrementer单独拿出来,会发现其中runingTotal和amount变量都无法使用,因为这两个变量的引用是incrementer从外部捕获的。
3.Swift会负责被捕获变量的所有内存管理工作,包括对捕获的一份值拷贝,也包括释放不再需要的变量。

现在再来测试makeIncrementer函数的使用:

let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen();    //10
incrementByTen();    //20

let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()   //7
incrementBySeven();  //14

let alsoIncrementByTen = incrementByTen
alsoIncrementByTen() //30

代码分析:
1.incrementByTen与incrementBySeven,是通过makeIncrementer函数传入不同的增量参数amount而创建的;
2.两个函数都有属于各自的引用,其中的runningTotal变量都是从makeIncrementer中捕获的,但是已经各自没有关系;
3.函数和闭包都是引用类型,将其赋值给变量或者常量,都只是操作的它们的引用,而不会改变闭包或者函数本身;

四、逃逸闭包

当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。
逃逸闭包:在定义接受闭包作为参数的函数时,我们需要在参数名之前标注@escaping,以此表明这个闭包是允许”逃逸”出这个函数的。

var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    //代码1:执行闭包,不需要添加@escaping
    //completionHandler();
    //代码2:函数外部对闭包进行了操作
    completionHandlers.append(completionHandler)
 }

代码分析:
someFunctionWithEscapingClosure(_:) 函数接受一个闭包作为参数,该闭包被添加到一个函数外定义的数组中。如果不将这个参数标记为@escaping,就会得到一个编译错误。

4.1.逃逸闭包的使用

逃逸闭包和非逃逸闭包在使用上有所不同。将一个闭包标记为@escaping(即逃逸闭包)后,在调用这个闭包时就必须在闭包中显式地引用 self。一个示例如下:

//定义一个带有非逃逸闭包参数的函数
func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()
}

//定义一个可以使用闭包的类
class SomeClass {
    var x = 10
    func doSomething() {
        //调用逃逸闭包:必须在闭包中显式引用self
        someFunctionWithEscapingClosure { self.x = 100 }
        //调用非逃逸闭包:可以隐式引用self
        someFunctionWithNonescapingClosure { x = 200 }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)         //打印出 "200”

五、自动闭包

自动闭包:一种自动创建的闭包,用与包装传递给函数作为参数的表达式;自动闭包的特点:
1.自动闭包不接受任何参数;
2.自动闭包被调用的时候,会返回被包装在其中的表达式的值;
3.自动闭包是用一个普通的表达式来代替显式的闭包,能够省略闭包的花括号;

其实,我们经常调用采用自动闭包的函数,但是却少去实现这样的函数,assert函数就是其中之一:

 assert(condition:, message:)

assert函数中的condition参数可以接受自动闭包作为值,condition参数仅会在debug模式下被求值,在condidtion被调用返回值为false时,message参数将被使用。

5.1.自动闭包的基本使用

自动闭包能够实现延迟求值,直到调用这个闭包时,代码才会被执行。这对于有副作用或者高计算成本的代码来说是有益处的;下面的代码展示了自动闭包实现延时求值的具体做法:

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)    //打印出 “5"

//自动闭包不接受参数,只是一个表达式
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)    //打印出 “5"

//调用自动闭包
print("Now serving \(customerProvider())!")   // Prints "Now serving Chris!"
print(customersInLine.count)               //打印出 "4”

代码分析:闭包实现了移除第一元素的功能,但是在闭包被调用之前,这个元素是不会被移除的。这就实现了延迟的作用

5.2.自动闭包在函数中的使用

现在将闭包作为参数传递给一个函数,同样可以实现延时求值行为。下面的serve函数接受了一个闭包参数(具有删除第一个元素且返回这个元素的功能)。

//customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}

//以闭包的形式传入参数
serve(customer: { customersInLine.remove(at: 0) } )  //打印出"Now serving Alex!”

现在使用自动闭包来实现上述函数功能,使用@autoclosure关键字,标明参数使用的是自动闭包,具体示例如下:

// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
}
//由于标明了自动闭包,这里直接省略了闭包的花括号
serve(customer: customersInLine.remove(at: 0))  //打印出"Now serving Ewa!\n"

注意:
过度使用 autoclosures 会让你的代码变得难以理解。上下文和函数名应该能够清晰地表明求值是被延迟执行的。

5.3.可”逃逸”的自动闭包

一个自动闭包可以“逃逸”,这时候应该同时使用 @autoclosure 和 @escaping 属性,下面举例说明:

// customersInLine is ["Barry", "Daniella"]
var customerProviders: [() -> String] = []
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
    customerProviders.append(customerProvider)
}
//调用collectCustomerProviders,向数组中追加闭包
collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))
print("Collected \(customerProviders.count) closures.")   //打印 "Collected 2 closures."
//循环数组中闭包,并且执行
for customerProvider in customerProviders {
    print("Now serving \(customerProvider())!")
}
// 打印 "Now serving Barry!"
// 打印 "Now serving Daniella!”

代码分析:
作为逃逸闭包:
collectCustomerProviders函数中,闭包customerProvider被追加到customerProviders中,而这个数据是定义在函数作用域范围之外的,这意味数组内的闭包能够在函数返回之后被调用,所以customerProvider必须允许
“逃逸”出函数作用域。

作为自动闭包:
调用collectCustomerProviders函数时,传入的闭包是表达式的形式,自动闭包省略了闭包花括号