Swift基础知识学习整理
2016-12-20 » iOS开发基础学习Swift过程中整理的一些容易遗漏的基础知识点
1、Swift和OC的不一样
1、 语言特性方面
1、swift语法简单易读、代码更少,更加清晰、易于维护
2、更加安全,optional的使用更加考验程序员对代码安全的掌控
3、泛型、结构体、枚举都很强大
4、函数为一等公民,便捷的函数式编程
5、有命名空间 基于module
6、类型判断
2、初始化方法
swift的初始化方法并没有返回值、需要复写 required initCoder方法
swift初始化在调用super.init之前要完成所有存储属性的赋值
2、数据结构
1、Int、 UInt的区别
带U表示无符号整数,不带U表示有符号整数 至于后面的位数选择,编译器会根据当前运行环境自动选择
2、
swift中 除了十进制,还可以使用十六进制 (0x) 八进制 (0o) 二进制 (0b)
可以使用千位分隔符 let million = 1_000_000
3、
type(of:PI) 查看类型
Double(fifteen_10) //这里实际是重新生成了一个Double类型的值,并非类型转换
PI = 15 + 314e-2 正常编译、 let PI_fifteen = PI + fifteen_10 错误
保留两位精度
round(combinationFee * 100)/100
Bool类型在OC中 1 和 0 就代表true false
在Swift中,判断过程是不可以这样转的
let i = 1
if i {
// 这个例子不会通过编译,会报错
}
在 Objective-C 中,nil 是一个指向不存在对象的指针。在 Swift 中,nil 不是指针,它是一个确定的值,用来表示值的缺失。不只是对象类型。
optional
什么是optional
Swift中的Optional作为一种类型,既可以存储一个值,也可以为空(也就是Swift里的nil),通常在类型后面加一个?表示它是Optional类型的:
var number: Int? = 32
其实 ? 只不过是一个语法糖,Optional的实际类型是一个enum:
public enum Optional<Wrapped> : ExpressibleByNilLiteral {
case none
/// The presence of a value, stored as `Wrapped`.
case some(Wrapped)
/// Creates an instance that stores the given value.
public init(_ some: Wrapped)
上面的 var number: Int? = 32 也就可以表示为:
var numbet: Optional<Int> = 32
Optional的使用
Swift通过引入Optional解决了Objective-C中“有”与“无”的问题,使代码的安全性得到了很大的提高,同时我们也应该知道,Swift是一种类型安全的语言。
optional函数链:
swift delegate 代理调用 不再使用resonse响应去校验方法是否存在
直接optional去调用就可以
self.delegate?.GXQActionSheetClickAtIndex(index: type!, viewController: self)
一个Optional对象只存在两种状态:包含一个值,或者为空,我们都可以通过解包(unwrap)来获取。
出于类型安全的考虑,我们不能再把Optional当作Boolean值处理。像下面这条语句在swift中会遇到编译错误
var myString: String? = "Hello"
if myString {
print(myString)
}
但是你可以通过==和!=,将Optional值和nil做比较来判断它是否包含一个值。如果不包含任何值,则为空。
if myString != nil{
print("myString contain a string value of \(myString!)")
}
在上面的语句里,当我们确定myString包含一个值时,我们通过在myString后面添加一个!来进行强制解包(forced unwrapping),获取Optional内包含的值。
Optional的解包
解包分为显式解包、隐式解包
隐式解包:
就是在定义可选类型值的时候,加上 !进行强制解包,隐式解包的写法会带来一个潜在的危险,如果尝试访问一个为空的隐式解包Optional, 就会遇到一个runtime error。
显式解包:
if-let解包
if let x = someOptional{
print("someOptional value is \(x)")
}
case let 解包
if case .some(let x) = someOptional{
print("someOptional value is \(x)") //如果为nil 情况 可不会输出东西 因为匹配的是Some!
}
if case let x? = someOptional{
print("someOptional value is \(x)") //如果为nil 情况 可不会输出东西 因为匹配的是Some!
}
用处:
比如元祖枚举解包、 switch中得解包
var roles = [
GameRole.Player(name: "玩家一"),
GameRole.Player(name: "玩家二"),
GameRole.NPC(name: "NPC1", faction: "光明"),
GameRole.NPC(name: "NPC2", faction: "黑暗"),
GameRole.Monster(name: "怪物1", element: 1),
GameRole.Monster(name: "怪物2", element: 2),
]
// 早前使用方法
// 遍历整个角色
for role in roles{
// 因为是枚举 所以要switch来匹配
switch role{
case let .NPC(name,faction):
print("有 \(name)-\(faction)出没")
default:
break
}
}
简化上面的代码
for case let .NPC(name,faction) in roles {
print("有 \(name)-\(faction)出没")
}
还可以继续加条件
for case let .NPC(name,faction) in roles where faction == "黑暗"{
print("有 \(name)-\(faction)出没")
}
2、运算符
基础运算符
1…3 闭区间 包括 1、2、3
1..<3 开区间 包括 1、2
2... 、 ...2 单侧区间
== 相等
!= 不等
Swift的 == 可以覆盖重写,自定义判等条件
=== 内存地址比较 相等
!== 内存地址比较 不相等
自定义运算符
1、声明
复写一些已声明的运算符可以不需要声明,例如 == 、 + 、 -
2、实现
infix、 prefix、 postfix 用于自定义表达式的声明, 分别表示 中缀、前缀、后缀
比如定义一个 ** 表达式
//下面这里 实现是类似 ?? 方法 ,扩展optional 实现这么一个方法 static 方法
infix operator ** //这句话只能定义在文件,类的外面,其余类可以针对这个定义 做不同实现
func ** <T>(optional : T? , defaultValue : @autoclosure () -> T) -> T {
if let value = optional {
return value
}
return defaultValue()
}
==========================================================
实现一个正则匹配运算符
//MARK: - 正则匹配
struct MyRegex {
let regex: NSRegularExpression?
init(_ pattern: String) {
regex = try? NSRegularExpression(pattern: pattern, options: .caseInsensitive)
}
func match(input: String) -> Bool {
if let matches = regex?.matches(in: input, options: [], range: NSRange(location: 0, length: input.length)) {
return matches.count > 0
}
return false
}
}
infix operator =~
func =~ (str: String, pattern: String) -> Bool {
return MyRegex(pattern).match(input: str)
}
string =~ "^[0-9]$"
3、字符串(值类型)和字符
1、字符串能使用下标
字符串索引: 获取对应区间的字符,根据 String.Index 字符串索引使用下标方式获取
let greeting = "Guten Tag!"
greeting[greeting.startIndex]
// G
greeting[greeting.index(before: greeting.endIndex)]
// !
greeting[greeting.index(after: greeting.startIndex)]
// u
let index = greeting.index(greeting.startIndex, offsetBy: 7)
greeting[index]
// a
如何截取字符串?
substring 已废弃
let star = str.index(str.startIndex, offsetBy: 0)
let end = str.index(str.startIndex, offsetBy: 4)
let substr = str[star..<end]
2、
字符串比较直接通过 ==
String 已实现Equalable协议 重写了 == 方法
多行字符串书写:由一对三个双引号包裹着的具有固定顺序的文本字符集:
let data2 = """
do sth
as you want
"""
//打印结果会根据字符串格式打印:
do sth
as you want
3、最直接的Collection的用法
特别是Swift 4.0 的改变,让Swift String更加人性化
str.count: 字符总数获取
str.isEmpty: 判断是否为空
str.reversed: 反向
集合用法
var str = "Hello, Swift 4.0"
print(str.count) // Swift4.0写法
/// 遍历
str.forEach {
$0
}
允许直接for in 遍历字符串:
for item in greeting! {
print(item)
}
//输出 G u t e n T a g !
4、集合(值类型)
1、数组相加
数组可以直接相加
var anotherThreeDoubles = Array(repeating: "2.5", count: 2)
var sixDoubles = anotherThreeDoubles + anotherThreeDoubles
// sixDoubles类型是 [String], 包含元素:["2.5", "2.5", "2.5", "2.5"]
sixDoubles = sixDoubles + ["2.5"]
array = array + ["111","222","333"]
array[0...1] = ["444","555"];
print(array)
结果: ["444", "555", "333"]
array = array + ["111","222","333"]
array[0...2] = ["444","555"]; // 0...2 size 为3 ,后面数组size为 2
print(array)
结果: ["444", "555"] //并不会出现两边因为数组size不一样产生错误,以更小size为准
但是数组中只能存储同一类型的数据, 可以有nil, 但是不能是不同类型
2、常用方法
var array = Array(repeating:0,count:5) -> [0,0,0,0,0]
数组常用方法
insert(_:at:): 在某个位置插入元素 不能超出数组大小
remove(at:): 移除某个位置的元素
removeLast(): 移除最后一个元素
enumerated(): 该方法返回一个包含索引和对应位置的值的元祖(tuple)数组(Example)。
for item in array {
print("----\(item)")
}
或者
如果需要元祖的方式获取index 加 item 就需要使用下面这种方式
for (index, item) in array.enumerated() {
print("\(index)----\(item)")
}
// 遍历数组
for i in 0..<array.count {
print(array[i])
}
// forin 方式
for item in array {
print(item)
}
// 设置遍历的区间
for item in array[0..<2] {
print(item)
}
// enumerated 遍历
for en in array2.enumerated() {
print(en)
// 下标
print(en.offset)
// 值
print(en.element)
}
// 下标和数值同时遍历 , 元祖
for (n,s) in array2.enumerated() {
print(n , "==", s);
}
// 反序遍历
for s in array2.reversed() {
print(s)
}
// 遍历下标和数值 反序
for (xxx,ooo) in array2.enumerated().reversed() {
print(xxx ,"====" ,ooo)
}
5、字典(值类型)
字典赋值
airports["LHR"] = "London"
字典取值
let airportName = airports["DUB"]
字典是否为空
airports.isEmpty
空字典
namesOfIntegers = [:]
6、属性
存储属性
一个存储属性就是存储在特定类或结构体实例里的一个常量或变量。存储属性可以是变量存储属性(用关键字 var 定义),也可以是常量存储属性(用关键字 let 定义)。
Swift和OC不同,Objective-C 为类实例存储值和引用提供两种方法。除了属性之外,还可以使用实例变量作为属性值的后端存储。Swift 编程语言中把这些理论统一用属性来实现。Swift 中的属性没有对应的实例变量,属性的后端存储也无法直接访问。
var fileName = "data.txt"
let fileName = "data.txt"
计算属性
计算属性不直接存储值,而是提供一个 getter 和一个可选的 setter,来间接获取和设置其他属性或变量的值。
var width = 0.0, height = 0.0, depth = 0.0
var volume: Double {
return width * height * depth
}
只读计算属性
只有 getter 没有 setter 的计算属性就是只读计算属性。只读计算属性总是返回一个值,可以通过点运算符访问,但不能设置新的值。
必须使用 var 关键字定义计算属性,包括只读计算属性,因为它们的值不是固定的。let 关键字只用来声明常量属性,表示初始化后再也无法修改的值。
延迟属性 lazy属性的使用
//定义一个闭包
lazy var html: () -> String = {
if let text = self.text {
return "11111";
} else {
return "2222";
}
}
//定义一个字符串
lazy var second:String = {
if let text = self.text {
return "11111";
} else {
return "2222";
}
}() //不要忘记最后的小括号,只有加了小括号,才表示是要返回值 而不是返回闭包。
//要类型声明lazy var second:String,这样Xcode会进行类型检查。
懒加载初始化的定义
懒加载初始化只进入一次 并且在需要的时候才调用
在Objective C中, 懒加载初始化是这样写的
@property (strong,nonatomic) CAShapeLayer * shapelayer;
-(CAShapeLayer *)shapelayer{
if (!_shapelayer) {
_shapelayer = [CAShapeLayer layer];
}
return _shapelayer;
}
那么何为 懒加载初始化呢?
从OC的代码中不难看出, 懒加载初始化,就是在变量第一次使用的时候才进行初始化。
Swift中,有两种方式来 懒加载初始化。
第一种,简单表达式
lazy var first = NSArray(objects: "1","2")
第二种,闭包
lazy var second:String = {return "second" }()
懒加载初始化的使用场景
1、属性本身依赖于外部因素才能初始化
completeURL表示完整的URL,这个变量依赖于自身的url是否含有http://前缀
class Demo{
var url:NSString
lazy var completeURL:NSString = {
[unowned self] in //防止闭包循环其实是没有必要的,因为闭包执行完成后就销毁了,并无强引用
if self.url.hasPrefix("http://"){
return self.url
}else{
return "http://".stringByAppendingString(self.url)
}
}()
init(url:NSString){
self.url = url
}}
2、属性需要复杂计算,消耗大量CPU
lazy var second:Int = {
var sum = 0
for i in 1...100000{ sum += i}
return sum
}()
3、属性不确定是否会使用到
官网的例子,注意,对于Manager来说,使用的时候,可能导入,也可能不倒入数据。从硬盘读取数据的代价是很大的,不导入数据的时候,不要初始化。
4、定制化的初始化
有些初始化只需要初始化一次,在变量定义的地方初始化,有助于代码维护
lazy var dataArray:NSMutableArray = {
var array = NSMutableArray()
for i in 1...100{
array.addObject(NSNumber(integer: i))
}
return array
}()
属性观察器
get set willSet didSet
var count: String {
return ""
}
var _count: String = "" //防止循环引用
var count: String {
get {
return _count
}
set {
_count = newValue;
}
}
var count: String = "1" {
willSet {
print(newValue)
}
didSet {
print(oldValue)
}
}
当你为存储型属性设置默认值或者在构造器中为其赋值时,它们的值是被直接设置的,不会触发任何属性观察者。
struct NameSpaceTwo {
var length: Int {
didSet(newValue) {
print(newValue)
}
}
var height: Int
func countTwo() -> Int {
return height + length
}
}
NameSpaceTwo(length: 2, height: 3) //并不会触发didSet
var name = NameSpaceTwo(length: 2, height: 3)
name.length = 2 //会触发didSet
7、循环
循环
for num in 0...3 {
print(interestingNumbers[num])
}
和
for num in 0..<3 {
print(interestingNumbers[num])
}
的区别 ... 指0到3之间包括下界(0,1,2,3) , ..< 不包括下界(0,1,2)
while
上面的就没什么好分析
下面的 repeat while 类似于其他语言的 do while,先进一次循环,然后执行到condition为false
repeat {
statements
} while condition
8、Switch
let someCharacter: Character = "z"
switch someCharacter {
case "a":
print("The first letter of the alphabet")
case "z":
print("The last letter of the alphabet")
default:
print("Some other character")
}
每一个 case 分支都必须包含至少一条语句。
为了让单个case同时匹配a和A,可以将这个两个值组合成一个复合匹配,并且用逗号分开:
case "a", "A":
case 分支的模式也可以是一个值的区间:
case 5..<12:
支持元组匹配
case (0, 1):
值绑定,case 分支允许将匹配的值声明为临时常量或变量,并且在case分支体内使用:
case (let x, 0):
print("on the x-axis with an x value of \(x)")
case 分支的模式可以使用where语句来判断额外的条件:
case let (x, y) where x == y:
print("(\(x), \(y)) is on the line x == y")
复合匹配也可以包含值绑定
使用switch 支持任意类型的数据以及各种比较操作——不仅仅是整数以及测试相等。
运行 switch 中匹配到的子句之后,程序会退出switch语句,并不会继续向下运行,所以不需要在每个子句结尾写break。
一定要有必执行项 (default,let x 这种)不然编译错误:switch must be exhaustive,枚举如果全部列举完成则不需要default
fallthrough 关键字
贯穿,如果希望case执行完成之后接下去的case也执行,需要使用 fallthrough:【紧跟的后一个】
fallthrough 后面项不能是let x
否则编译错误
'fallthrough' from a case which doesn't bind variable 'x'
case中使用 where关键字 进行条件判断
switch self {
case .paramone(let name, let age) where age > 0:
return ("/search", ["name": name, "age": age])
case let .paramtwo(name):
return ("/search", ["name": name])
}
9、函数、闭包
1、函数
第一等公民,可作为返回类型,可嵌套函数,比如柯里化操作
可变参数
func arithmeticMean(_ numbers: Double...) -> Double {
var total: Double = 0
for number in numbers {
total += number
}
return total / Double(numbers.count)
}
arithmeticMean(1, 2, 3, 4, 5)
// 返回 3.0, 是这 5 个数的平均数。
arithmeticMean(3, 8.25, 18.75)
// 返回 10.0, 是这 3 个数的平均数。
var newEvens = [Int]()
for i in 1...10 {
if i % 2 == 0 {
newEvens.append(i)
}
}
print(newEvens) // [2, 4, 6, 8, 10]
可以写成
var evens = Array(1...10).filter { $0 % 2 == 0 }
print(evens)
// [2, 4, 6, 8, 10]
输入输出参数
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
关键字放类型前面
声明函数时,在参数前面用inout修饰,在函数内部实现改变外部参数,方法调用的时候参数需要 & (&在形参中表示“引用”实参)
var str = "789789"
getOne(&str)
print(str)
func getOne(_ b:inout String){
b = "123456"
}
结果:123456
2、闭包
什么是闭包
闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数比较相似。闭包是引用类型。
表达式语法:
{ (parameters) -> returnType in
statements
}
{num1,num2 -> Bool in num1 > num2}
缩写成 { $0, $1 in $0 > $1} 还可以缩 {$0 > $1} 或者{ > }
类型:(parame Type) -> (returnType) ()为Void 空类型
(s1: String, s2: String) -> Bool
值捕获
闭包可以在其被定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
由于这个特性 也容易产生循环引用
自动闭包和逃逸闭包
自动闭包和逃逸闭包
@noescape 变成默认属性
@escaping 逃逸闭包,闭包是引用类型,所以为了异步依然可以使用之前定义的闭包,可以使用逃逸闭包,脱离函数作用域的约束。
逃逸的闭包常用于异步的操作,这类函数会在异步操作开始之后立刻返回,但是闭包直到异步操作结束后才会被调用。例如这个闭包是异步处理一个网络请求,只有当请求结束后,闭包的生命周期才结束。当闭包作为函数的参数传入时,很有可能这个闭包在函数返回之后才会被执行
@autoclosure 自动闭包
(1)默认非逃逸
(2)闭包也可以被自动的生成,这种闭包被称为自动闭包,自动闭包自动将表达式封装成闭包。
(3)自动闭包不接收任何参数,被调用时会返回被包装在其中的表达式的值。
(4)当闭包作为函数参数时,可以将参数标记 @autoclosure 来接收自动闭包。
(5)自动闭包能够延迟求值,因为代码段不会被执行直到你调用这个闭包。
(6)自动闭包默认是非逃逸的,如果要使用逃逸的闭包,需要手动声明: @autoclosure @escaping
逃逸闭包的使用
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
自动闭包的使用
func serve(customer customerProvider: @autoclosure () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
10、枚举 (非常强大、值类型)
OC枚举方式
typedef NS_ENUM 推荐使用
typedef enum
默认原始值是整型,也只能是整型原始值,只能判断整型
oc 定义的枚举 大驼峰命名规则
注意:OC中定义的枚举在OC中可以不需要通过枚举名字获取,但在swift中需要用枚举.方法获取,所以把枚举名字写在枚举变量前面做前缀, swift解析的时候会自动去掉前缀。既方便oc又方便swift。
typedef NS_ENUM(NSInteger, FundType) {
FundTypeMonetary = 1,//公募货币基金
FundTypeBond = 2,//公募债基基金
};
Swift枚举方式
Swift枚举
Swift的枚举可以用在class、struct、等
enum Result {
case success
case error
}
enum Planet {
case mercury, venus, earth, mars, jupiter, saturn, uranus, nepturn
}
可以定义为多种类型,String, char,Int等 或者不使用类型,原始值不会是整型 0、1、2
如果没有指定枚举类型 是无法获取原始值rowValue的
@objc enum FPCheckResult: NSInteger{
case success //成功
case sailed //失败
case passcodeNotSet //未设置手机密码
case touchidNotSet //未设置指纹
case touchidNotAvailable //不支持指纹
}大驼峰命名名称 小驼峰命名成员
如果需要在OC中使用Swift定义的枚举,则有限制,枚举只能是int类型,需要加@objc标记
swift 定义的枚举在OC中使用 会自动把枚举名字添加到前面做前缀 比如FPCheckResult.success 适配在OC中是 FPCheckResultSuccess
并不像 C 和 Objective-C 一样,Swift 的枚举成员在被创建时不会被赋予一个默认的整数值。在上面的FPCheckResult例子中,Success,Failed,PasscodeNotSet和TouchidNotSet不是隐式得等于0,1,2和3。相反的,这些不同的枚举成员在FPCheckResult的一种显示定义中拥有各自不同的值。
带参数枚举:
enum QrCode {
case qrCode(String)
}
多参数枚举,元祖枚举
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
使用枚举成员的rawValue属性可以访问该枚举成员的原始值:
let earthsOrder = Planet.earth.rawValue
// earthsOrder is 3
通过rawValue去获取对应枚举成员
let possiblePlanet =Planet(rawValue: 7)
// possiblePlanet is of type Planet? and equals Planet.Uranus
值关联
enum Trade {
case Buy(stock: String, amount: Int)
case Sell(stock: String, amount: Int)
}
let trade = Trade.Buy(stock: "Car", amount: 100)
if case let Trade.Buy(stock, amount) = trade {
print("buy \(amount) of \(stock)")
}
与Switch的一起使用
switch self {
case .Buy(let w, let a):
do sth
}
枚举属性
Swift的枚举中可以自定义属性,甚至在嵌套枚举中,枚举可以使用枚举作为自身属性,但是不可以作为存储值属性。
enum Character {
case Thief
case Warrior
case Knight
var demo: String {
get { return "good" }
}
}
无setter原因
Cannot assign to property: 'Thief' is not settable
.Thief.demo
枚举方法
枚举本身可以实现方法
enum Character {
case Thief
case Warrior
case Knight
func description() {
print("这是描述")
}
}
.Thief.description()
枚举嵌套
枚举里面可以进行枚举嵌套,可以用
enum Character {
enum Weapon {
case Bow
case Sword
case Lance
case Dagger
}
enum Helmet {
case Wooden
case Iron
case Diamond
}
case Thief
case Warrior
case Knight
}
let character = Character.Thief
let weapon = Character.Weapon.Bow
let helmet = Character.Helmet.Iron
枚举的使用
我们应该在什么情况下考虑使用枚举呢?只要结果可能是有限的集合的情况下,我们就尽量考虑使用枚举。
善于使用枚举、结构体等
如下枚举,由于可以用于实现一些聚合类型的功能,比如handler,service
11、类(引用类型) 和 结构体(值类型)
Swift 中类和结构体有很多共同点。共同处在于:
- 定义属性用于存储值
- 定义方法用于提供功能
- 定义下标操作使得可以通过下标语法来访问实例所包含的值
- 定义构造器用于生成初始化值
- 通过扩展以增加默认实现的功能
- 实现协议以提供某种标准功能
与结构体相比,类还有如下的附加功能:
- 继承允许一个类继承另一个类的特征
- 类型转换允许在运行时检查和解释一个类实例的类型
- 析构器允许一个类实例释放任何其所被分配的资源
- 引用计数允许对一个类的多次引用
- 结构体对象是值类型,总是通过被复制的方式在代码中传递,不使用引用计数。
- 类对象是引用类型
类
与值类型不同,引用类型在被赋予到一个变量、常量或者被传递到一个函数时,其值不会被拷贝。因此,引用的是已存在的实例本身而不是其拷贝。
结构体
所有结构体都有一个自动生成的成员逐一构造器,用于初始化新结构体实例中成员的属性。新实例中各个属性的初始值可以通过属性的名称传递到成员逐一构造器之中:
struct NameSpaceTwo {
var length: Int
var height: Int
}
NameSpaceTwo(length: <#T##Int#>, height: <#T##Int#>)
值类型被赋予给一个变量、常量或者被传递给一个函数的时候,其值会被拷贝。
let spacetwo = NameSpaceTwo(protocolParam: "8888", length: 5, height: 5)
let spacethree = spacetwo
print(Unmanaged<AnyObject>.passUnretained(spacethree as AnyObject).toOpaque())
print(Unmanaged<AnyObject>.passUnretained(spacetwo as AnyObject).toOpaque())
let 的方式的 struct 内部参数不能改变
spacethree 和 spacetwo 的改变都不会对对方产生影响。
mutating 关键字
使用 mutating 关键字修饰方法是为了能在该方法中修改 struct 或是 enum 的变量,在设计接口的时候,也要考虑到使用者程序的扩展性。所以要多考虑使用mutating来修饰方法。
struct test {
var name: String = "111"
mutating func changeName() -> String {
name = "2222"
return name
}
}
按照通用的准则,当符合一条或多条以下条件时,请考虑构建结构体:
该数据结构的主要目的是用来封装少量相关简单数据值。
有理由预计该数据结构的实例在被赋值或传递时,封装的数据将会被拷贝而不是被引用。
该数据结构中储存的值类型属性,也应该被拷贝,而不是被引用。
该数据结构不需要去继承另一个既有类型的属性或者行为。
12、协议
属性要求
协议可以要求遵循协议的类型提供特定名称和类型的实例属性或类型属性。协议不指定属性是存储型属性还是计算型属性,它只指定属性的名称和类型。此外,协议还指定属性是可读的还是可读可写的。
协议总是用 var 关键字来声明变量属性,在类型声明后加上 { set get } 来表示属性是可读可写的,可读属性则用 { get } 来表示:
protocol SomeProtocol {
var mustBeSettable: Int { get set }
var doesNotNeedToBeSettable: Int { get }
}
在协议中定义类类型属性时,总是使用 static 关键字作为前缀。当类类型遵循协议时,除了 static 关键字,还可以使用 class 关键字来声明类型属性:
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
}
方法要求
正如属性要求中所述,在协议中定义类方法的时候,总是使用 static 关键字作为前缀。当类类型遵循协议时,除了 static 关键字,还可以使用 class 关键字作为前缀:
protocol SomeProtocol {
static func someTypeMethod()
}
有时候方法中需要修改方法所属的实例,比如方法所属对象是值类型的 结构体、枚举,修改这种实例 则需要在方法前面添加mutating关键字,如果是class实现这个协议则可以不写这个关键字。结构体和枚举实现此协议则必须加上关键字。
protocol Togglable {
mutating func toggle()
}
构造器要求
协议可以要求遵循协议的类型实现指定的构造器。你可以像编写普通构造器那样,在协议的定义里写下构造器的声明,但不需要写花括号和构造器的实体:
protocol SomeProtocol {
init(someParameter: Int)
}
构造器要求在类中的实现
你可以在遵循协议的类中实现构造器,无论是作为指定构造器,还是作为便利构造器。无论哪种情况,你都必须为构造器实现标上 required 修饰符:
class SomeClass: SomeProtocol {
required init(someParameter: Int) {
// 这里是构造器的实现部分
}
}
使用 required 修饰符可以确保所有子类也必须提供此构造器实现,从而也能符合协议。
如果一个子类重写了父类的指定构造器,并且该构造器满足了某个协议的要求,那么该构造器的实现需要同时标注 required 和 override 修饰符
协议作为类型
作为函数、方法或构造器中的参数类型或返回值类型
作为常量、变量或属性的类型
作为数组、字典或其他容器中的元素类型
协议是一种类型,因此协议类型的名称应与其他类型(例如 Int,Double,String)的写法相同,使用大写字母开头的驼峰式写法,例如(FullyNamed 和 RandomNumberGenerator)。
代理模式
这个和OC没什么太大的区别,使用的也是很频繁
协议的继承和组合
继承
protocol A { }
protocol B: A { }
如果我们需要定义一个协议仅仅用于整合多个其他协议
protocol A { }
protocol B { }
protocol C: A, B {
}
可以直接用 typealias D = A & B
可选协议
协议可以定义可选要求,遵循协议的类型可以选择是否实现这些要求。在协议中使用 optional 关键字作为前缀来定义可选要求。
可选要求用在你需要和 Objective-C 打交道的代码中。
协议和可选要求都必须带上@objc属性。
标记 @objc 特性的协议只能被继承自 Objective-C 类的类或者 @objc 类遵循,其他类以及结构体和枚举均不能遵循这种协议。
@objc
protocol C: A, B {
@objc optional func dosth()
}
协议扩展
swift协议扩展可以提供协议方法的默认实现
extension C {
func dosth() {
print("123")
}
}
协议扩展可以添加对实现者的约束
比如我定义一个协议扩展,但是我需要实现者满足某个条件(实现了另一个协议)
这种情况下,使用where来限制
extension Collection where Iterator.Element: TextRepresentable {
var textualDescription: String {
let itemsAsText = self.map { $0.textualDescription }
return "[" + itemsAsText.joined(separator: ", ") + "]"
}
}
13、泛型
泛型代码让你能够根据自定义的需求,编写出适用于任意类型、灵活可重用的函数及类型。它能让你避免代码的重复,用一种清晰和抽象的方式来表达代码的意图。
泛型参数
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
这个函数的泛型版本使用了占位类型名(在这里用字母 T 来表示)来代替实际类型名(例如 Int、String 或 Double)。占位类型名没有指明 T 必须是什么类型,但是它指明了 a 和 b 必须是同一类型 T,无论 T 代表什么类型。只有 swapTwoValues(_:_:) 函数在调用时,才会根据所传入的实际类型决定 T 所代表的类型。
泛型函数和非泛型函数的另外一个不同之处,在于这个泛型函数名(swapTwoValues(_:_:))后面跟着占位类型名(T),并用尖括号括起来(<T>)。这个尖括号告诉 Swift 那个 T 是 swapTwoValues(_:_:) 函数定义内的一个占位类型名,因此 Swift 不会去查找名为 T 的实际类型。
swapTwoValues(_:_:) 函数现在可以像 swapTwoInts(_:_:) 那样调用,不同的是它能接受两个任意类型的值,条件是这两个值有着相同的类型。swapTwoValues(_:_:) 函数被调用时,T 所代表的类型都会由传入的值的类型推断出来。
struct Stack<T> {
var items: [T] = []
mutating func push(item: T) {
items.append(item)
}
mutating func pop() {
items.removeLast()
}
}
var stack = Stack<Int>()
stack.push(item: 4)
stack.push(item: 5)
stack.pop()
关联类型
定义了一个关联类型 ItemType:
protocol Container {
associatedtype ItemType
mutating func append(_ item: ItemType)
var count: Int { get }
subscript(i: Int) -> ItemType { get }
}
associatedtype ItemType: Comparable 声明的关联类型可以做限制
实现者必须添加对关联类型的限定 typealias,同时其他使用此关联类型的地方也会相应的变成实现者选择的类型。
enum Character: Container {
typealias ItemType = Int
var count: Int
func append(_ item: Int) {
}
subscript(i: Int) -> Int {
}
}
泛型的限制
你可以在一个类型参数名后面放置一个类名或者协议名,并用冒号进行分隔,来定义类型约束,它们将成为类型参数列表的一部分。对泛型函数添加类型约束的基本语法如下所示(作用于泛型类型时的语法与之相同):
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// 这里是泛型函数的函数体部分
}
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
泛型 where 语句
类型约束让你能够为泛型函数,下标,类型的类型参数定义一些强制要求。
比如 被检查的两个 Container 可以不是相同类型的容器(虽然它们可以相同),但它们必须拥有相同类型的元素。这个要求通过一个类型约束以及一个 where 子句来表示:
func allItemsMatch<C1: Container, C2: Container> (_ someContainer: C1, _ anotherContainer: C2) -> Bool where C1.ItemType == C2.ItemType, C1.ItemType: Equatable {
或者简单一点:
比如需要设置一个泛型参数继承自一个对象并且实现了某个协议,我们如下操作:
struct Stack<T: UIViewController & Container>
也可以用where语句做限制
struct Stack<T: UIViewController> where T: Container
具有泛型 where 子句的扩展
extension Stack where Element: Equatable
具有泛型 where 子句的关联类型
protocol Container {
associatedtype Iterator: IteratorProtocol where Iterator.Element: UIViewController
}
class MyIterator: IteratorProtocol {
func next() -> UIViewController? {
return nil
}
typealias Element = UIViewController
}
class ViewController: UIViewController, Container {
typealias Iterator = MyIterator
泛型下标
extension Container {
subscript<Indices: Sequence>(indices: Indices) -> [Item]
where Indices.Iterator.Element == Int {
var result = [Item]()
for index in indices {
result.append(self[index])
}
return result
}
}
14、其他
打印内存地址
String(format: "%p", self)
注释
// MARK: - 操作
// TODO: - 记得做
// FIXME: - 提醒
let 属性可以在初始化的时候再赋值,因为初始化只会执行一次
let data: String
init() {
data = ""
super.init(nibName: nil, bundle: nil)
}
类别别名的使用
typealias MyType = UInt16
错误处理
函数抛出错误
func canThrowErrors() throws -> String
do catch 处理错误,在可能抛出错误的函数前面加 try关键字
do {
try canThrowAnError()
// 没有错误消息抛出
} catch {
// 有一个错误消息抛出
}
将错误转换成可选值
let x = try? someThrowingFunction()
禁用错误传递,忽略错误发生,默认一定不会出现错误
let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")
defer语句在即将离开当前代码块时执行一系列语句
let file = open(filename)
defer {
close(file) //保证在操作最后执行
}
检查 API 可用性
if #available(iOS 10, macOS 10.12, *) {
// 在 iOS 使用 iOS 10 的 API, 在 macOS 使用 macOS 10.12 的 API
} else {
// 使用先前版本的 iOS 和 macOS 的 API
}
Swift根据类名字创建实例
任何自定义的对象都是AnyClass的子类
(Demo.classForCoder() as! Demo.Type).init()
通过如上方式去构建实例的时候报错:
Constructing an object of class type ‘**’ with a metatype value must use a ‘required’ initializer.
需要覆写Demo中的
override required init() {
super.init()
}
Swift命名规则
对类、结构体、枚举和协议等类型命名,应该采用大驼峰法,如SplitViewController。
文件名,采用大驼峰法,如BlockOperation.swift。
扩展文件,有的时候扩展是定义在一个独立的文件中的,它的命名是“原始类型名+扩展名”作为扩展文件名,如NSOperation+Operations.swift。
变量和属性,采用应该采用小驼峰法,如studentNumber。
常量,采用大驼峰法,如MaxStudentNumber。
枚举成员,与常量类似,采用大驼峰法,如ExecutionFailed。
枚举case成员 采用小驼峰法。
函数和方法,采用应该采用小驼峰法,如balanceAccount、isButtonPressed等。
Swift工程入口
swift 工程中得main方法入口
@UIApplicationMain
Swift访问修饰符
Swift 3.0 的 open,public,internal,fileprivate,private 关键字
fileprivate和 open
fileprivate 类似之前的private ,文件内私有,单个文件内可以互相访问,
现在的private 就是真正意义的私有,即使同文件内 只要跨类了 就不能访问
open 相当于 可以外部继承 public现在不能外部继承
import Foundation
/// final的含义保持不变
public final class FinalClass { }
// 这个类在ModuleA的范围外是不能被继承的,只能被访问
public class PublicClass {
public func testPublic() {}
// 这是错误的写法,因为class已经不能被继承,
// 所以他的方法的访问权限不能大于类的访问权限
open func testOpen() {}
// final的含义保持不变
public final func testPublicFinal() {}
}
// 在ModuleA的范围外可以被继承
open class OpenClass {
// 这个属性在ModuleA的范围外不能被override
public var size : Int = 0
// 这个方法在ModuleA的范围外不能被override
public func testPublic() {}
// 这个方法在任何地方都可以被override
open func testOpen() {}
///final的含义保持不变
public final func testPublicFinal() {}
}
import Foundation
import ModuleA
// 这个写法是错误的,编译会失败,类访问权限标记的是public,只能被访问不能被继承
class SubA : PublicClass { }
// 这样写法可以通过,Class访问权限为 `open`.
class SubB : OpenClass {
// 这样写也会编译失败,因为这个方法权限为public,不是`open'.
override func testPublic() { }
// 这个方法因为在SubclassableParentClass中标记为open,所以可以这样写
// 这里不需要再声明为open,因为这个类是internal的
override func testOpen() { }
}
open class SubC : OpenClass {
// 这种写法会编译失败
override func testPublicFinal() { }
// 正确的写法,方法也需要标记为open
open override func testOpen() { }
}
open class SubE : OpenClass {
// 也可以显式的指出这个方法不能再被override
public final override func testOpen() { }
}
组合访问控制符
open internal(set) var str: String?
Swift自定义下标
下标语法
extension Int {
subscript(index: Int) -> Int {
get {
// 返回一个适当的 Int 类型的值
}
set(newValue) {
// 执行适当的赋值操作
}
}
}
print(3333[0])
下标还可以多个参数
subscript(row: Int, column: Int) -> Double
matrix[0, 1] = 1.5
static和class的使用
Swift中表示 “类型范围作用域” 这一概念有两个不同的关键字,它们分别是static和class。这两个关键字确实都表达了这个意思,但是在其他一些语言,包括Objective-C中,我们并不会特别地区分类变量/类方法和静态变量/静态函数。但是在Swift中,这两个关键字却是不能用混的。
在非class的类型上下文中,我们统一使用static来描述类型作用域。这包括在enum和struct中表述类型方法和类型属性时。在这两个值类型中,我们可以在类型范围内声明并使用存储属性,计算属性和方法。static适用的场景有这些:
struct Point {
let x: Double
let y: Double
// 存储属性
static let zero = Point(x: 0, y: 0)
// 计算属性
static var ones: [Point] {
return [Point(x: 1, y: 1),
Point(x: -1, y: 1),
Point(x: 1, y: -1),
Point(x: -1, y: -1)]
}
// 类型方法
static func add(p1: Point, p2: Point) -> Point {
return Point(x: p1.x + p2.x, y: p1.y + p2.y)
}
}
enum的情况与这个十分类似,就不再列举了。
class关键字相比起来就明白许多,是专门用在class类型的上下文中的,可以用来修饰类方法以及类的计算属性。要特别注意class中现在是不能出现存储属性的,我们如果写类似这样的代码的话:
class MyClass {
class var bar: Bar?
}
编译时会得到一个错误:
class variables not yet supported
这主要是因为在Objective-C中就没有类变量这个概念,为了运行时的统一和兼容,暂时不太方便添加这个特性。Apple表示今后将会考虑在某个升级版本中实装class类型的类存储变量,现在的话,我们只能在class中用class关键字声明方法和计算属性。
有一个比较特殊的是protocol。在Swift中class、struct和enum都是可以实现protocol的。那么如果我们想在protocol里定义一个类型域上的方法或者计算属性的话,应该用哪个关键字呢?答案是使用static进行定义,不管是否是class类型的协议,都用static,用class编译错误
但是在实现时还是按照上面的规则:在class里使用class关键字、static关键字都行,而在struct或enum中仍然使用static:
protocol MyProtocol {
static func foo() -> String
}
struct MyStruct: MyProtocol {
static func foo() -> String {
return "MyStruct"
}
}
enum MyEnum: MyProtocol {
static func foo() -> String {
return "MyEnum"
}
}
class MyClass: MyProtocol {
class func foo() -> String {
return "MyClass"
}
}
protocol extension 做默认实现
protocol ImageFactory {
func createImage(color: UIColor) -> UIImage?
}
extension ImageFactory {
func createImage(color: UIColor) -> UIImage? {
let rect = CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0)
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
context?.setFillColor(color.cgColor)
context?.fill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image ?? nil
}
}
Swift 中的引用循环
ARC
ARC 苹果版本的自动内存管理的编译时间特性。它代表了自动引用计数(Automatic Reference Counting)。也就是对于一个对象来说,只有在引用计数为0的情况下内存才会被释放。
Strong(强引用)
让我们从什么是强引用说起。它实质上就是普通的引用(指针等等),但是它的特殊之处在于它能够通过使对象的引用计数+1来保护对象,避免引用对象被ARC机制销毁。本质上来讲,任何对象只要有强引用,它就不会被销毁掉。记住这点对我接下来要讲的引用循环等其他知识来说很重要。
强引用在Swift中无处不在。事实上,当你声明一个属性时,它就默认是一个强引用。一般来说,当对象之间的关系为线性时,使用强引用是安全的。当对象之间的强引用是从父层级流向子层级时,用强引用通常也是ok的。
weak(弱引用)
weak 引用并不能保护所引用的对象被ARC机制销毁。强引用能使被引用对象的引用计数+1,而弱引用不会。此外,若弱引用的对象被销毁后,弱引用的指针会被清空。这样保证了当你调用一个弱引用对象时,你能得到一个对象或者nil.
在swift中,所有的弱引用都是非常量的可选类型(对比 var 和 let) ,因为当没有强引用对象引用的的时候,弱引用对象能够并且会变成nil。
例如,这样的代码不会通过编译
class Kraken {
weak let tentacle = Tentacle()
//let is a constant! All weak variables MUST be mutable.
}
因为tentacle是一个let常量。Let常量在运行的时候不能被改变。因为弱引用变量在没有被强引用的条件下会变成nil,所以Swift 编译器要求你必须用var来定义弱引用对象。
值得注意的地方是,使用弱引用变量能够避免你出现可能的引用循环。当两个对象相互强引用的时候会出现一个引用循环。如果2个对象相互引用对方,ARC就不能给这两个对象发出合适的释放信息,因为这两个对象彼此相互依存。下图是从苹果官方简洁的图片,它很好的解释了这种情况:
一个比较恰当的例子就是通知APIs,看一下下面的代码:
class Kraken {
var notificationObserver: ((NSNotification) -> Void)?
init() {
notificationObserver = NSNotificationCenter.defaultCenter().addObserverForName("humanEnteredKrakensLair", object: nil, queue: NSOperationQueue.mainQueue()) { notification in
self.eatHuman()
}
}
deinit {
if notificationObserver != nil {
NSNotificationCenter.defaultCenter.removeObserver(notificationObserver)
}
}
}
在这种情况下我们有一个引用循环。如果在闭包范围之外声明变量,那么在闭包中使用这个变量时,会对该变量产生另一个强引用。唯一的例外是使用值类型的变量,比如Swift中的 Ints、Strings、Arrays以及Dictionaries等。
在这里,当你调用eatHuman( ) 时,NSNotificationCenter就保留了一个闭包以强引用方式捕获self。经验告诉我们,你应该在deinit方法中清除通知监听对象。这段代码的问题在于我们没有清除掉block直到deinit.但是deinit 永远都不会被ARC机制调用,因为闭包对Kraken实例有强引用。
另外在NSTimers和NSThread也可能会出现这种情况。
解决这种情况的方法就是:在闭包的捕获列表中使用对self的弱引用。这样就能够打破强引用循环。那么,我们的对象引用图就会像这样:
把self变成weak不会让self 的引用计数+1,因此ARC机制就能在合适的时间释放掉对象。
想要在闭包使用 weak 和 unowned 变量,你应该用[]把它们括起来。如:
let closure = { [weak self] in
self?.doSomething() //Remember, all weak variables are Optionals!
}
在上面的代码中,为什么要把 weak self 要放在方括号内?看上去简直秀逗了!在Swift中,我们看到方括号就会想到数组。你猜怎么着?你可以在在闭包内定义多个捕获值!例如:
let closure = { [weak self, unowned krakenInstance] in //Look at that sweet Array of capture values.
self?.doSomething() //weak variables are Optionals!
krakenInstance.eatMoreHumans() //unowned variables are not.
}
这样看上去更像是数组了,对吧?现在你知道为什么把捕获值放在方括号里面了吧。那么用我们已了解的东西,通过在闭包捕获列表中加上[weak self],我们就可以解决之前那段有引用循环的通知代码。
NSNotificationCenter.defaultCenter().addObserverForName("humanEnteredKrakensLair", object: nil, queue: NSOperationQueue.mainQueue()) { [weak self] notification in //The retain cycle is fixed by using capture lists!
self?.eatHuman() //self is now an optional!
}
其他我们用到weak和unowned变量的情况是当你使用协议在多个类之间实现代理时,因为Swift中类使用的是reference semantics。在Swift中,结构体和枚举同样能够遵循协议,但是它们用的是value semantics。如果像这样一个父类带上一个子类使用委托使用了代理:
1. class Kraken: LossOfLimbDelegate {
2. let tentacle = Tentacle()
3. init() {
4. tentacle.delegate = self
5. }
6.
7. func limbHasBeenLost() {
8. startCrying()
9. }
10. }
11.
12. protocol LossOfLimbDelegate {
13. func limbHasBeenLost()
14. }
15.
16. class Tentacle {
17. var delegate: LossOfLimbDelegate?
18.
19. func cutOffTentacle() {
20. delegate?.limbHasBeenLost()
21. }
22. }
在这里我们就需要用weak变量了。在这种情况下,Tentacle以代理属性的形式对Kraken有着一个强引用,而Kraken在它的Tentacle属性中对Tentacle也有一个强引用。我们通过在代理声明前面加上weak来解决这个问题:
weak var delegate: LossOfLimbDelegate?
是不是发现这样写不能通过编译?不能通过编译的原因是非 class类型的协议不能被标识成weak。这里,我们必须让协议继承:class,从而使用一个类协议将代理属性标记为weak。
1. protocol LossOfLimbDelegate: class { //The protocol now inherits class
2. func limbHasBeenLost()
3. }
当你有着跟我上述代码一样的引用关系,你就用:class。在结构体和枚举的情况下,没有必要用:class,因为结构体和枚举是value semantics,而类是 reference semantics.
**UNOWNED(弱引用) **
weak引用和unowned引用有些类似但不完全相同。Unowned 引用,像weak引用一样,不会增加对象的引用计数。然而,在Swift里,一个unowned引用有着非可选类型的优点。这样相比于借助和使用optional binding更易于管理。这和隐式可选类型区别不大。此外,unowned引用是non-zeroing(非零的) ,这表示着当一个对象被销毁时,它指引的对象不会清零。也就是说使用unowned引用在某些情况下可能导致野指针。
看到这里是不是有点蛋疼了。既然Weak和unowned引用都不会增加引用计数,它们都能用于解除引用循环。那么我们该在什么使用它们呢?
在引用对象的生命周期内,如果它可能为nil,那么就用weak引用。反之,当你知道引用对象在初始化后永远都不会为nil就用unowned.
现在你就知道了:就像是隐式可选类型,如果你能保证在使用过程中引用对象不会为nil,用unowned 。如果不能,那么就用weak
下面就是个很好的例子。Class 里面的闭包捕获了self,self永远不会为nil。
1. class RetainCycle {
2. var closure: (() -> Void)!
3. var string = "Hello"
4. init() {
5. closure = {
6. self.string = "Hello, World!"
7. }
8. }
9. }
改成
1. closure = { [unowned self] in
2. self.string = "Hello, World!"
3. }
如果你知道你引用的对象会在正确的时机释放掉,且它们是相互依存的,而你不想写一些多余的代码来清空你的引用指针,那么你就应该使用unowned引用而不是weak引用。
像下面这种懒加载在闭包中使用self就是一个使用unowned的很好例子:
1. class Kraken {
2. let petName = "Krakey-poo"
3. lazy var businessCardName: () -> String = { [unowned self] in
4. return "Mr. Kraken AKA " + self.petName
5. }
6. }
我们需要用unowned self 来避免引用循环。Kraken 和 businessCardName在它们的生命周期内都相互持有对方。它们相互持有,因此总是被同时销毁,满足使用unowned 的条件。
然而,不要把下面的懒加载变量与闭包混淆:
1. class Kraken {
2. let petName = "Krakey-poo"
3. lazy var businessCardName: String = {
4. return "Mr. Kraken AKA " + self.petName
5. }()
6. }
在懒加载变量中调用closure时,由于没有retain closure,所以不需要加 unowned self。变量只是简单的把闭包的结果assign 给了自己,闭包在使用后就被立即销毁了。下面的截图很好的证明了这点。
内存安全
15、 Swift 和 OC的 Hybird 编程
混编带来的文件变化
swift 和 OC 混用会有两个新文件
1、工程名-Swift.h 是 用于 OC 调用 swift 、是swift代码在工程中自动更新出的 OC 格式代码
不可见,但是可以跳转进入,所有swift代码生成的类属性 都会在这个文件中转义一份 OC 的样式,如果在pod中的资源无法引用,检查是否未添加@objc 或者是否 public
2、工程名-Bridging-Header.h 是用于 swift 调用 oc 的头文件,所有需要引入swift的OC都需要先在这个文件引入。
swift工程引用OC pod库 不需要通过桥文件 直接import对应的库名称,也尽量少在bridge文件中对OC库的头文件进行引用,影响Xcode编译效率
pod引用注意:
Swift写的 pod 中遇到的错误:import 了对应的 module后还是提示未定义
Swift写的库需要暴露的部分需要使用public or open修饰,所有需要暴露出去的内容都需要修饰,否则默认不开放,外部是无法使用的
public 只能被本module内容 继承重写
open 可以被其他module内容 继承重写
Swift命名空间
Swift的类名 转成 oc 自带了格式,并非直接名字。所以通过class去寻找指定类需要转className
在swift根据类名找xib也需要注意命名空间,一般式工程名.类名
//获取xib
class func nib() -> UINib {
let className = NSStringFromClass(self)
let postfix = className.components(separatedBy: ".").last
return UINib(nibName: postfix!, bundle: nil)
}
//获取对应className
- (Class)swiftClassFromString:(NSString *)className {
NSString *appName =
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
NSString *classStringName =
[NSString stringWithFormat:@"_TtC%d%@%d%@", appName.length, appName, className.length, className];
return NSClassFromString(classStringName);
}
pod资源中的swift类名称 会是 工程名称.pod名称.类名称
oc 通过xib初始化swift控制器的时候, 不能直接用className 去查找对应nib 因为swift存在域名 前面有工程名的前缀
swift viewController xib初始化的时候要实现对应的指定构造器 init()并在构造器里面super.init(根据xib名字去初始化)
Swift的初始化过程
1、Swift初始化规则
1、实现父类的指定初始化器
2、如果实现了自己的指定初始化器,指定初始化器需要调用父类指定初始化器,同时会继承父类的指定初始化器
-----------------关于初始化的继承---------------------
规则 1
如果子类没有定义任何指定初始化器,它将自动继承所有父类的指定初始化器。
规则 2
如果子类提供了所有父类指定初始化器的实现--不管是通过规则1继承过来的,还是通过自定义实现的--它将自动继承所有父类的便捷初始化器。因为便捷初始化器都是基于指定初始化器的
初始化方法的问题
特别是swift继承oc类的情况
1.swift继承自swift 的情况下 父类的初始化器,自动继承所有父类的指定初始化器
2.如果实现了所有的父类指定初始化器 将自动继承所有的便利初始化器
1.当子类继承了父类的时候,如果子类没有覆盖任何父类的指定初始化器,那么它也不能建立便利初始化器,并且自动继承所有父类的指定初始化器。
2.如果子类自己建立了指定初始化器,并且未覆盖父类的指定初始化器,那么它将失去所有的父类初始化器的使用。
2、指定初始化器、便捷初始化器
Swift继承自oc的时候会出现一种情况 就是 要求实现指定初始化器
当AswiftController继承一个BOCcontroller的时候,BocController中必须要有一个指定初始化方法
UIViewController默认的指定初始化方法是
- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil NS_DESIGNATED_INITIALIZER;
或者我们自己指定一个指定初始化方法,也就是说在OC中写一个初始化方法,并用 NS_DESIGNATED_INITIALIZER 标示标出,那么在swift中得初始化方法就必须实现这个初始化方法
OC控制器 A
swift控制器 B 继承自 A
OC控制器 C
swift控制器 B 继承自OC控制器 A ,然后又再另一个OC C 调用 B 控制器 ,oc调用 直接alloc init 那么就会调用到 B 控制器中 的非指定初始化器 init方法,在特定系统(8.几)运行过程中会报错,所以我们需要在swift 继承自OC的时候就复写指定初始化器方法,并实现init方法,在方法中调用父类指定初始化器。
Swift 实现的控制器 需要实现init方法
init() {
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
或者在B 的父类 A 中实现init方法,并在方法中通过指定初始化器实现初始化(initWithNIB)
B:
init() {
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
A:
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
}
return self;
}
- (instancetype)init{
return [self initWithNibName:nil bundle:nil];
}
指定初始化器纵向实现(委托父类的指定初始化器),便捷初始化器横向实现(调用本身的指定初始化器)
如果便捷初始化器接收参数和父类指定初始化器一样,需要在前面加上override
3、必要初始化器
当我们重写类的时候经常提示要添加代码:
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
这个叫必要初始化器,这种情况一般会出现在继承了遵守NSCoding protocol的类,比如UIView系列的类、UIViewController系列的类。
为什么一定要添加:
这是NSCoding protocol定义的,遵守了NSCoding protoaol的所有类必须继承。只是有的情况会隐式继承,而有的情况下需要显示实现。
什么情况下要显示添加:
当我们在子类定义了指定初始化器(包括自定义和重写父类指定初始化器),那么必须显示实现required init?(coder aDecoder: NSCoder),而其他情况下则会隐式继承,我们可以不用理会。
什么情况下会调用:
当我们使用storyboard实现界面的时候,程序会调用这个初始化器。
注意要去掉fatalError,fatalError的意思是无条件停止执行并打印。
总结:
如果代码实现界面,那么我们只要根据编译器提示添加必要初始化器后,就不用理会,我们创建界面的工作可以在自定义的初始化器里实现。
补充:let vc = UIViewController()方式初始化类
UIViewController类视乎只有两个初始化器,一个是必要初始化器init?(coder aDecoder: NSCoder),一个是指定初始化器init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?),那么为什么我们可以用let vc = UIViewController()这种方式初始化类呢?原因可能是这个初始化方式是来自uikit,也就是调用了Object-c下的UIViewController初始化方法,是object-c bridge过来的。
4、初始化过程
Swift 的初始化分两阶段,第一阶段是类的分配内存地址 并且给类的属性分配内存地址
位于super.init 之前。如果没有父类,就应该是所有的属性都有值之后算第二阶段。
第一阶段
1、指定或便捷初始化器在类中被调用
2、为这个类的新实例分配内存。内存还没有被初始化
3、这个类的指定初始化器确保所有由此类引入的存储属性都有一个值。现在这些存储属性的内存被初始化了
4、指定初始化器上交父类的初始化器为其存储属性执行相同的任务
5、这个调用父类初始化器的过程将沿着初始化器链一直向上进行,直到到达初始化器链的最顶部
6、一旦达了初始化器链的最顶部,在链顶部的类确保所有的存储属性都有一个值,此实例的内存被认为完全初始化了,此时第一阶段完成。
第二阶段就是super.init 之后,self已经具体实现完成,可以调用方法,并且可以进行定制,super.init 是为了完成到父类实现init的委托。这个是具体的实现。
安全检查
1、指定初始化器必须保证在向上委托给父类初始化器之前,其所在类引入的所有属性都要初始化完成。
2、指定初始化器必须先向上委托父类初始化器,然后才能为继承的属性设置新值。如果不这样做,指定初始化器赋予的新值将被父类中的初始化器所覆盖。
3、便捷初始化器必须先委托同类中的其它初始化器,然后再为任意属性赋新值(包括同类里定义的属性)
4、初始化器在第一阶段初始化完成之前,不能调用任何实例方法、不能读取任何实例属性的值,也不能引用 self 作为值。
违背检查规则的都无法通过编译。
拓展:
在OC中的 init方法内
- (instancetype)init
{
NSLog(@"%@",self); //这个时候self已经被分配地址,他得属性不能通过点符号点出,可以指向,说明地址也是被分配了。
self = [super init];//具体实现 后面才可以调用方法
if (self) {
}
return self;
}
可空初始化器
struct Animal {
let species: String
init?(species: String) {
if species.isEmpty { return nil }
self.species = species
}
}
16、Swift反射
所谓反射就是可以动态获取类型、成员信息,在运行时可以调用方法、属性等行为的特性。 在使用OC
开发时很少强调其反射概念,因为OC的Runtime
要比其他语言中的反射强大的多。不过在Swift
中并不提倡使用Runtime
,而是像其他语言一样使用反射(Reflect
),即使目前Swift
中的反射功能还比较弱,只能访问获取类型、成员信息。
Swift
的反射机制是基于一个叫Mirror
的结构体来实现的。你为具体的实例创建一个Mirror
对象,然后就可以通过它查询这个实例
使用案例
func propertyList() -> [String] {
//获取‘类’的属性列表
let mirror = Mirror(reflecting: self)
if mirror.children.count == 0 {
return []
}
var arr = [String]()
for item in mirror.children {
let name = item.label!
arr.append(name)
}
return arr
}
Mirror结构体常用属性:
subjectType
:对象类型children
:反射对象的属性集合displayStyle
:反射对象展示类型
下面来简单介绍下Mirror的使用:
//定义一个类来进行测试
class Person {
var name: String?
var age: Int = 0
}
//创建一个对象并初始化
let p = Person()
p.name = "小强"
p.age = 13
//1. 创建对象的反射,获取对象类型
let mirror: Mirror = Mirror(reflecting:p)
print("获取对象类型\(mirror.subjectType)")
// 打印出:获取对象类型Person
//2. 获取对象属性名以及对应的值
for p in mirror.children {
let propertyNameString = p.label! //属性名使用!,因为label是optional类型
let value = p.value //属性的值
print("\(propertyNameString)的值为\(value)")
}
/* 打印:
name的值为Optional("小强")
age的值为13
*/
//3. 获取指定索引下的属性类型
let children = mirror.children
let p0 = children.startIndex.advancedBy(0) //获取name属性的位置索引
let p0Mirror = Mirror(reflecting: children[p0].value) //name的反射
print("获取属性name的类型为\(p0Mirror.subjectType)")
//打印:获取属性name的类型为Optional
//4. 遍历获取对象所有动态的属性类型
for p in mirror.children {
let propertyNameString = p.label!
let value = p.value
let vMirror = Mirror(reflecting: value) //通过值来创建属性的反射
print("属性\(propertyNameString)类型为\(vMirror.subjectType)")
}
/* 打印:
属性name类型为Optional
属性age类型为Int
*/
17、Swift字面量类型和字面量协议
1、字面量类型(Literal Type)
在介绍字面量类型前,我们先认识下字面量的概念。
所谓字面量,是指一段能表示特定类型的值(如数值、布尔值、字符串)的源码表达式(it is the source code representation of a fixed value)
let num: Int = 10
let flag: Bool = true
let str: String = "hello"
那什么是字面量类型呢?
字面量类型就是支持通过字面量进行实例初始化的数据类型,如例子中的Int
、Bool
、String
类型。
在Swift中,其的字面量类型有:
- 所有的数值类型: Int、Double、Float以及其的相关类型(如UInt、Int16、Int32等)
- 布尔值类型:Bool
- 字符串类型:String
- 组合类型:Array、Dictionary、Set
- 空类型:Nil
2、字面量协议(Literal Protocol)
Swift是如何让上述的数据类型具有字面量初始化的能力呢?
答案是:实现指定的字面量协议。
所以,如果我们希望自定义的数据类型也能通过字面量进行初始化,只要实现对应的字面量协议即可。
Swift中的字面量协议主要有以下几个:
- ExpressibleByNilLiteral// nil字面量协议
- ExpressibleByIntegerLiteral// 整数字面量协议
- ExpressibleByFloatLiteral// 浮点数字面量协议
- ExpressibleByBooleanLiteral// 布尔值字面量协议
- ExpressibleByStringLiteral// 字符串字面量协议
- ExpressibleByArrayLiteral// 数组字面量协议
- ExpressibleByDictionaryLiteral// 字典字面量协议
其中, ExpressibleByStringLiteral
字符串字面量协议相对复杂一点,该协议还依赖于以下2个协议(也就是说,实现ExpressibleByStringLiteral
时,还需要实现下面2个协议):
- ExpressibleByUnicodeScalarLiteral
- ExpressibleByExtendedGraphemeClusterLiteral
3、字面量协议例子(Literal Protocol Example)
下面将会通过具体例子为大家演示如何通过实现上述的字面量协议。
1、定义Moeny
类型,实现通过整数字面量、浮点数字面量、字符串字面量、布尔值字面量初始化Money
实例:
//: Playground - noun: a place where people can play
import UIKit
import Foundation
struct Money {
var value: Double
init(value: Double) {
self.value = value
}
}
// 实现CustomStringConvertible协议,提供description方法
extension Money: CustomStringConvertible {
public var description: String {
return "\(value)"
}
}
// 实现ExpressibleByIntegerLiteral字面量协议
extension Money: ExpressibleByIntegerLiteral {
typealias IntegerLiteralType = Int
public init(integerLiteral value: IntegerLiteralType) {
self.init(value: Double(value))
}
}
// 实现ExpressibleByFloatLiteral字面量协议
extension Money: ExpressibleByFloatLiteral {
public init(floatLiteral value: FloatLiteralType) {
self.init(value: value)
}
}
// 实现ExpressibleByStringLiteral字面量协议
extension Money: ExpressibleByStringLiteral {
public init(stringLiteral value: StringLiteralType) {
if let doubleValue = Double(value) {
self.init(value: doubleValue)
} else {
self.init(value: 0)
}
}
// 实现ExpressibleByExtendedGraphemeClusterLiteral字面量协议
public init(extendedGraphemeClusterLiteral value: StringLiteralType) {
if let doubleValue = Double(value) {
self.init(value: doubleValue)
} else {
self.init(value: 0)
}
}
// 实现ExpressibleByUnicodeScalarLiteral字面量协议
public init(unicodeScalarLiteral value: StringLiteralType) {
if let doubleValue = Double(value) {
self.init(value: doubleValue)
} else {
self.init(value: 0)
}
}
}
// 实现ExpressibleByBooleanLiteral字面量协议
extension Money: ExpressibleByBooleanLiteral {
public init(booleanLiteral value: BooleanLiteralType) {
let doubleValue: Double = value ? 1.0 : 0.0
self.init(value: doubleValue)
}
}
// 通过整数字面量初始化
let intMoney: Money = 10
// 通过浮点数字面量初始化
let floatMoney: Money = 10.1
// 通过字符串字面量初始化
let strMoney: Money = "10.2"
// 通过布尔值初始化
let boolMoney: Money = true
2、定义Book
类型,实现通过字典字面量、数组字面量、nil字面量初始化Book
实例:
//: Playground - noun: a place where people can play
import Foundation
struct Book {
public var id: Int
public var name: String
init(id: Int, name: String = "unnamed") {
self.id = id
self.name = name
}
}
// 实现CustomStringConvertible协议,提供description方法
extension Book: CustomStringConvertible {
public var description: String {
return "id:\(id)\nname:《\(name)》"
}
}
// 实现ExpressibleByDictionaryLiteral字面量协议
extension Book: ExpressibleByDictionaryLiteral {
typealias Key = String
typealias Value = Any
public init(dictionaryLiteral elements: (Key, Value)...) {
var dictionary = [Key: Value](minimumCapacity: elements.count)
for (k, v) in elements {
dictionary[k] = v
}
let id = (dictionary["id"] as? Int) ?? 0
let name = (dictionary["name"] as? String) ?? "unnamed"
self.init(id: id, name: name)
}
}
// 实现ExpressibleByArrayLiteral字面量协议
extension Book: ExpressibleByArrayLiteral {
typealias ArrayLiteralElement = Any
public init(arrayLiteral elements: ArrayLiteralElement...) {
var id: Int = 0
if let eId = elements.first as? Int {
id = eId
}
var name = "unnamed"
if let eName = elements[1] as? String {
name = eName
}
self.init(id: id, name: name)
}
}
// 实现ExpressibleByNilLiteral字面量协议
extension Book: ExpressibleByNilLiteral {
public init(nilLiteral: ()) {
self.init()
}
}
// 通过字典字面量初始化
let dictBook: Book = ["id": 100, "name": "Love is Magic"]
print("\(dictBook)\n")
// 通过数组字面量初始化
let arrayBook: Book = [101, "World is word"]
print("\(arrayBook)\n")
// 通过nil字面量初始化
let nilBook: Book = nil
print("\(nilBook)\n")
4、关于 ‘not expressible by any literalenum’ Error
当你使用自定义数据类型定义枚举时,可能会遇到以下类似错误:
raw type ‘XX_TYPE’ is not expressible by any literal enum XX_ENUM: XX_TYPE
这是说你的自定义数据类型没有实现字面量协议。然而需要注意的是,enum目前支持的字面量协议是有限制的,其目前只支持以下几个字面量协议:
- ExpressibleByIntegerLiteral
- ExpressibleByFloatLiteral
- ExpressibleByStringLiteral
也就是说,若你的自定义数据类型实现的字面量协议没有包含上面中的一个,就会得到此种错误。具体示例如下:
//: Playground - noun: a place where people can play
import Foundation
struct StockType {
var number: Int
}
// 实现CustomStringConvertible协议,提供description方法
extension StockType: CustomStringConvertible {
public var description: String {
return "Stock Number:\(number)"
}
}
// 实现Equatable协议,提供==方法
extension StockType: Equatable {
public static func ==(lhs: StockType, rhs: StockType) -> Bool {
return lhs.number == rhs.number
}
}
// 实现ExpressibleByDictionaryLiteral字面量协议
extension StockType: ExpressibleByDictionaryLiteral {
typealias Key = String
typealias Value = Any
public init(dictionaryLiteral elements: (Key, Value)...) {
var dictionary = [Key: Value](minimumCapacity: elements.count)
for (k, v) in elements {
dictionary[k] = v
}
let number = (dictionary["number"] as? Int) ?? 0
self.init(number: number)
}
}
// 实现ExpressibleByIntegerLiteral字面量协议
extension StockType: ExpressibleByIntegerLiteral {
public init(integerLiteral value: IntegerLiteralType) {
self.init(number: value)
}
}
/*
若StockType没有实现 ExpressibleByIntegerLiteral、ExpressibleByFloatLiteral、ExpressibleByStringLiteral中的一个,会报错误:error: raw type 'StockType' is not expressible by any literal
*/
// 你可以尝试去掉ExpressibleByIntegerLiteral的实现,看看编译器报的错误
enum Stock: StockType {
case apple = 1001
case google = 1002
}
let appleStock = Stock.apple.rawValue
print("\(appleStock)")
上述例子中,定义了StockType
数据类型和Stock
枚举类型。若StockType
去掉ExpressibleByIntegerLiteral
字面量的协议的实现,将会获得上述的编译错误。
Swift 4 的新特性
Swift 5 的新特性