基础知识
1.class , enum ,struct 的区别
- struct不能继承,class可以继承。
- struct是ValueType(值类型),而class是ReferenceType(引用类型)
- class 的对象方法调用,是通过对象的前八个字节找到自身的类对象,之后偏移具体的字节直接调用类对象的方法(大概率在全局区)。如果有多肽,父类的方法也会存在类对象中,而不是像OC那样,通过runtime 查找
- 结构体调用方法的话,所有由同一个结构体生成的对象,调用同一个方法的时候,调用地址都是一样的。
属性
1.存储属性,类似成员变量,存储在实例的内存中,结构体,类可以定义存储属性
2.计算属性,本质上是方法,不占用实例内存,枚举,结构体,类都可以定义计算属性
如何区分:
要区分存储属性和计算属性,主要看属性的定义中是否包含getter和setter。如果包含getter和setter,那么它是一个计算属性。如果没有getter和setter,那么它是一个存储属性。当你访问计算属性时,实际上是调用了getter方法来计算属性的值,而存储属性则直接返回存储的值。
- 带lazy 关键字的都是存储属性
- 计算属性无需要在初始化的时候赋值,不需要遵从Swift的二段式初始化流程
- 带属性观察器(didSet,willSet)的一定是存储属性
枚举
enum中只能包含计算属性、类型属性,不能包含存储属性
为什么struct中可以放存储属性,而enum不可以?
主要是因为struct中可以包含存储属性是因为其大小就是存储属性的大小。而对enum来说就是不一样的,enum枚举的大小是取决于case的个数的,如果没有超过255,enum的大小就是1字节(8位)
2.不通过继承,代码复用(共享)的方式有哪些
扩展, 全局函数
3.值类型和引用类型
值类型赋值给var、let或者给函数传参,是直接将所有内容拷贝一份,类似于对文件进行copy、paste操作,产生了全新的文件副本。属于深拷贝(deep copy) 引用类型复制相当于把指针复制一次,属于浅拷贝。
在Swift标准库中,为了提升性能,String、Array、Dictionary、Set采取了Copy On Write的技术
- 比如仅当有“写”操作时,才会真正执行拷贝操作
- 对于标准库值类型的赋值操作,Swift能确保最佳性能,所以没有必要为了保证最佳新能来避免赋值
- Swift中参数传递是值类型传递,它会对值类型进行copy操作,当传递一个值类型变量时(变量赋值,函数传参),它传递的是一份新的copy值,两个变量指向不同的内存区域。如果频繁操作的变量占内存较大,会产生性能问题。
Copy On Write是一种优化值类型copy的机制,对String、Int、Float等非集合数据类型,赋值直接拷贝,对于Array等集合类型数据,只有传递的内容值改变后才进行拷贝操作。
Copy On Write的实现:set函数中判断是否存在多个引用,只有存在多个引用的情况下才会进行拷贝操作。另外,自定义结构体是不支持Copy On Write的。
4.元组(tuples)
元组(tuples)可以把多个值组合成一个复合值。元组内的值可以是任意类型,并不要求是相同类型
5.可选类型
可选类型默认值为nil。 可以使用可选项来判断一个枚举是否包含这个值
if Season(rawValue: 2) != nil {
print("right");
}else{
print("wrong");
}
可选类型的安全解包拆包方式一般是
func getHeight(_ height: Float?) -> Float? {
if height != nil {
return height! / 100
}
return nil
}
func getHeight(_ height: Float?) -> Float? {
guard let opt = height eles{
return nil;
}
return opt / 100
try! 和 try?
let string = "{"name":"hehe"}"
let data = string.data(using: .utf8)
//方法一:推荐使用 try?,如果解析成功,就有值;解析失败,返回nil
let json = try? JSONSerialization.jsonObject(with: data!, options: [])
//方法二:不建议使用 try!,如果解析失败,会崩溃
let json = try! JSONSerialization.jsonObject(with: data!, options: [])
//方法三:do catch 手动捕获异常
do {
let json = try JSONSerialization.jsonObject(with:data!, options: [])
} catch {
//输出错误信息
print(error)
}
空合并运算符 ??
a ?? b 这里a必须是可选项,b可以是可选项或者不是可选项,但是a 和 b 的存储类型必须相同
- 如果 a 不为 nil, 就返回 a
- 如果 a 为nil, 就返回b
- 如果b不是可选项,返回a 时会自动解包
let a: Int? = nil
let b: Int? = 2
if let c = a ?? b {
print(c)
}
// 这里的if 判断类似 if a != nil || b != nil
闭包
闭包和OC的block很像,是封装了函数和其调用环境的对象。
逃逸闭包
如果函数参数的闭包不在该函数体中调用,那么该参数必须声明为逃逸闭包。
class AVCViewVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.test {
print("我来了")
}
}
func test(callback:()->()) {
print("1")
callback() // 闭包在函数体中调用了
print("2")
}
func escapeTest(callback: @escaping ()->()) {
print("1")
test(callback: callback) // 逃逸闭包,因为闭包作为text的参数传递了
print("2")
}
}
inout关键字
这个关键字会C++的& 为变量起别名很像,会把变量的地址传递到调用的函数。测试代码如下:
func spaceInout(data : inout Int) {
data = data + 10;
}
var daddd = 0x77777;
spaceInout(data: &daddd);
0x102a1f624 <+1404>: stur x8, [x29, #-0x98]
0x102a1f628 <+1408>: ldur x0, [x29, #-0xc0]
0x102a1f62c <+1412>: bl 0x102a1efb4 ; SwiftLearning.spaceInout(data: inout Swift.Int) -> () at funcSwTTest.swift:27
从汇编可以看出,通过把x29(sp寄存器,函数调用栈基地址寄存器)通过偏移计算得到局部变量daddd的地址,之后保存在x0寄存器(函数参数)再跳转。
如果inout 传入的是计算属性,从汇编可以看出,首先调用的是计算属性的getter方法,获取到值之后再传入带inout形参的函数,修改完成之后再调用setter方法
-> 0x1005736f0 <+228>: bl 0x100572fcc ; SwiftLearning.Student.grade.getter : Swift.Int at shuXing.swift:80
0x1005736f4 <+232>: sub x8, x29, #0x38 ; =0x38
0x1005736f8 <+236>: stur x0, [x29, #-0x38]
0x1005736fc <+240>: mov x0, x8
0x100573700 <+244>: bl 0x100571dac ; SwiftLearning.changeNum(data: inout Swift.Int) -> () at shuXing.swift:11
0x100573704 <+248>: ldur x0, [x29, #-0x38]
0x100573708 <+252>: ldur x20, [x29, #-0xf8]
0x10057370c <+256>: bl 0x100573050 ; SwiftLearning.Student.grade.setter : Swift.Int at shuXing.swift:77
let
let 定义的变量在初始化方法完成之前就拥有值。
lazy 属性
延时存储属性(用到的时候才会赋值调用),不是线程安全的,只能用var修饰。 在第一次访问lazy修饰的属性时候汇编代码执行顺序如下: getter->swift_
继承
枚举和结构体不支持继承,只有类支持继承
swift不像OC那样,有一个所有类的基类(NSObject)。
子类可以重写父类的方法,属性,下标。但是必需添加override关键字。
子类可以把父类的计算属性和存储属性重写成计算属性但是不能重写从存储属性。
子类重写后的属性权限不能小于父类
- 如果父类属性的只读,那么子类重写后可以是只读,也可以是可读可写
- 如果分类属性是可读可写,那么子类也只能是可读可写
被class修饰的属性可以被子类重写。
被static修饰的属性不能被子类重写。
被final修饰的属性,方法,下标禁止重写。被final修饰的类禁止继承
初始化器
指定初始化器 designated
- Swift有了超级严格的初始化方法。一方面,Swift强化了 designated初始化方法的地位。Swift中不加修饰的 init 方法都需要在方法中保证所有非Optional 的实例变量被赋值初始化,而在子类中也强制(显式或者隐式的)调用 super 版本的 designated 初始化,所以无论走何种路径,被初始化的对象总是可以完成完整的初始化的
- 类,结构体,枚举都有指定初始化器init。当一个类的所有存储属性都有默认值并且没有定义任何初始化器时,Swift 会自动生成一个无参数的默认初始化器。这个默认初始化器可以让你创建类的实例,而无需提供任何初始值。而对于结构体,如果没有初始化器,系统就会根据对应的存储属性生成默认初始化器
- 便捷初始化器必需最终调用他在其类中的其中一个指定初始化器
- 子类指定初始化器必须调用父类指定初始化。(但是测试发现,如果父类没有需要初始化的程序变量,或者没有自己手动实现指定初始化器,就不需要调用super),子类属性都初始化完毕(有值)才能调用super 初始化
- 注意在init里我们可以对 let 的实例常量进行赋值,这是初始化方法的重要特点。在Swift中let声明的值是不变量,无法被写入赋值,这对于构建线程安全的API十分有用。而因为 Swift 的 init 只可能被调用一次,因此在 init 中我们可以为不变量进行赋值,而不会引起任何线程安全的问题。
- 比如一个UIView 写了一个其他的初始化方法A,这样外部就不能调用init(frame:) 这个父类初始化方法了,因为如果调用的话,会出现初始方法A里面赋值的变量没有值,违反了Swift的非可选变量都必须要有值的原则。所有frame 是一个计算属性!!以下是GPT的解答:
UIView的frame属性是一个计算属性,而不是一个存储属性。计算属性是通过计算而得到的值,而不是通过直接存储值来获得的。在UIView中,frame属性的值是通过计算得到的,它是基于视图的bounds和center属性计算得出的。当你设置一个视图的frame时,实际上是修改了它的bounds和center属性。因此,frame属性本身并不存储任何值,它只是一个方便的计算属性。
便捷初始化器 convenience
与designated 初始化方法对应的是在 init 前加上 convenience 关键字的初始化方法。这类方法是 Swift 初始化方法中的“二等公民”,只作为补充和提供使用上的方便。所有的 convenience 初始化方法都必须调用同一个类中的 designated 初始化完成设置,另外 convenience 的初始化方法是不能被子类重写的,也不能从子类中以 super 的方式被调用
require 关键字
对于某些我们希望子类中一定实现的 designated 初始化方法,我们可以通过添加 required 关键字进行限制,强制子类对这个方法重写实现。这样的一个最大的好处是可以保证依赖于某个 designated 初始化方法的 convenience 一直可以被使用。一个现成的例子就是上面的 init(bigNum: Bool),如果我们希望这个初始化方法对于子类一定可用,那么应当将 init(num: Int) 声明为“必须”,这样我们在子类中调用 init(bigNum: Bool) 时就始终能够找到一条完全初始化的路径了
为什么实现协议中的初始化方法一定要加required?
在实现协议中的初始化方法时,添加required关键字是为了强制要求其遵循者(实现类)也必须实现该初始化方法,且必须以相同的方法声明来实现。
如果协议中定义了一个初始化方法,那么在实现这个初始化方法时,实现类必须使用required关键字修饰这个方法。这是因为协议并不能保证遵循者的子类都能继承所需的初始化方法,而使用required关键字可以确保子类重写实现时也必须提供该初始化方法。
对于结构体来说,实现协议中的初始化方法时不需要加上required关键字。这是因为结构体是值类型,当它被传递或复制时,其整个值都会被复制,而不是像引用类型那样只是复制其引用。因此,结构体的初始化方法在实现时不需要使用required关键字来满足协议的初始化要求,因为它们始终是可用的。
对于类来说,不能用extension 去实现一个指定初始化方法,必须要在本地内实现,结构体则可以(为什么这么设计?)
总结:
-
两段式初始化
第1阶段: 初始化所有存储属性
外层调用指定\便捷初始化器
分配内存给实例,但未初始化
指定初始化器确保当前类定义的存储属性都初始化
指定初始化器调用父类的初始化器,不断向上调用,形成初始化器链 -
第2阶段: 设置新的存储属性值 从顶部初始化器往下,链中的每一个指定初始化器都有机会进一步定制实例
初始化器现在能够使用self(访问、修改它的属性,调用它的实例方法等等
最终,链中任何便捷初始化器都有机会定制实例以及使用self -
安全检查:
指定初始化器必须保证在调用父类初始化器之前,其所在类定义的所有存储属性都要初始化完成
指定初始化器必须先调用父类初始化器,然后才能为继承的属性设置新值
便捷初始化器必须先调用同类中的其它初始化器,然后再为任意属性设置新值初始化器在第1阶段初始化完成之前,不能调用任何实例方法、不能读取任何实例属性的值,也不能引用seLf
直到第1阶段结束,实例太算完全合法 -
自动继承
1.如果子类没有自定义任何指定初始化器,它会自动继承父类所有的指定初始化器
2.如果子类提供了父类所有指定初始化器的实现 (要么通过方式1继承,要么重写 )子类自动继承所有的父类便捷初始化器
3.就算子类添加了更多的便捷初始化器,这些规则仍然适用 4.子类以便捷初始化器的形式重写父类的指定初始化器,也可以作为满足规则2的一部分
可选链
只要经过可选链包装出来的,都是可选类型
@inlinable关键字
这个关键词是可内联的声明,它来源于C语言中的inline。C中一般用于函数前,做内联函数,它的目的是防止当某一函数多次调用造成函数栈溢出的情况。因为声明为内联函数,会在编译时将该段函数调用用具体实现代替,这么做可以省去函数调用的时间。
访问控制
open
允许在定义实体的模块,其他模块中访问,允许其他模块进行继承,重写(open只能用在类,类成员上)
public
允许在定义实体的模块,其他模块中访问,但是不允许其他模块进行继承,重写
internal
只允许在定义实体的模块中访问,不能在其他模块中访问(默认)
fileprivate
只允许在定义实体的源文件中访问
private
只允许在定义实体的封闭声明中访问
如果Swift没写访问控制,默认是internal
总的来说,有三个概念 1.模块 2.文件 3.定义实体的封闭声明
协议
1、枚举,结构体,类可以遵守协议
2、协议不能有默认参数值
3、默认情况下,协议中定义的内容必须全部都实现,也有办法只实现部分内容
4、协议中的属性实现,既可以用存储属性实现,也可以用计算属性实现
5、协议中定义属性必须要var 关键字
6、为了保证通用,协议中必须用static 定义类型方法,类型属性,类型下标(因为如果用class 那么结构体就不能遵循这个协议了)
where Self 是一种协议扩展中的特殊语法,它表示只有遵守该协议的类或者对象才能够使用该协议扩展中的方法或者属性。使用 where Self 关键字可以实现在协议扩展中对类或对象的类型进行限制,从而在使用该协议扩展中的方法或者属性时可以保证类型的一致性。
7、在协议中使用static关键字定义方法和属性,那么这些方法和属性只能在遵循该协议的类中使用,而无法在协议外部访问。如果想要在协议外部访问这些方法和属性,可以使用class关键字来代替static,这样就可以通过类名直接访问这些方法和属性了。
8、协议中的属性和方法都是类型,可以可以同名。如果一个类型遵守了两个协议A,B,A,B中有相同的属性,那么在使用的时候,需要显示指定用的是哪个属性。例子如下
protocol MyProtocol1 {
static var myStaticProperty: String { get }
}
protocol MyProtocol2 {
static var myStaticProperty: Int { get }
}
struct MyStruct: MyProtocol1, MyProtocol2 {
static var myStaticProperty: String = "Hello"
static var myStaticProperty: Int = 42
}
print(MyStruct.myStaticProperty) // 编译错误:Ambiguous reference to member 'myStaticProperty'
print(MyStruct.myStaticProperty as String) // 输出:Hello
print(MyStruct.myStaticProperty as Int) // 输出:42
Any,AnyObject
Any 代表任意类型(枚举,结构体,类,函数)
AnyObject 代表任意类类型(如果协议上写上AnyObject,代表只有类能遵守这个协议)
X.self X.Type X.AnyClass(X表示类,注意不是对象)
1.X.self 是一个元类型(metaData)的指针,metaData 存放着类型相关信息,就是对象的前八个字节(和对象的isa 指针有什么区别)?
2.X.Type X.self 的类型就是X.Type.也就是说,X.Type 是元类型的类型
3.AnyClass 在Swift中定义为AnyObject.Type
Self 和 self
在 Swift 中,self 表示实例本身,是一个关键字。Self(大写)表示当前类型本身,在协议中使用。当一个类型实现一个协议时,Self 代表该类型本身。
举个例子,下面的协议声明了一个 static 方法 make(),该方法返回一个遵循协议的类型的新实例:
protocol SomeProtocol {
static func make() -> Self
}
这里的 Self 表示遵循该协议的类型本身,而不是实例。因此,如果一个类型 SomeType 实现了该协议,则 SomeType.make() 将返回一个 SomeType 类型的新实例。
(backtick)反引号
public static let `default` = Session()
在Swift在反引号的作用是使用保留字作为标识符。
associatedtype
关联类型
用于给协议中用到的类型定义个占位符,协议中可以拥有多个关联类型。
主要是协议不能像类,结构体那样实现泛型,所以才有这个associatedtype。实现协议类,结构题,可以用typealias
类型约束
func eqaul<cls0:A,cls1:B>(_ p1:cls0, _ p2:cls1) -> Bool where cls0.Element == cls1.Element, cls1.Element : Hashable {
return false
}
deinit 方法
当类的实例对象被释放内存时,就会调用实例对象的deinit方法
deinit不接受任何参数,不能写小括号,不能自行调用
父类的deinit能被子类继承
子类的deinit实现执行完毕后会调用父类的deinit
模式匹配
if case 语句等价于只有一个case 的 switch 语句,下面4个语句等价
if age >= 0 && age <=9 {
}
if case 0...9 = age {
}
guard case 0...9 = age else { return }
switch age {
case 0...9: print("")
default:break
}
for case 语句
let ages: [Int?] = [2,3,nil,5]
for case nil in ages {
print("有nil 值")
break
}
let points = [(1,0),(2,1),(3,0)]
for case let (x,0) in points {
print(x)
}
指针应用
1.在 Swift 中,指针都使用一个特殊的类型来表示,那就是 UnsafePointer<T>。遵循了 Cocoa 的一贯不可变原则,UnsafePointer<T> 也是不可变的。当然对应地,它还有一个可变变体,UnsafeMutablePointer<T>。绝大部分时间里,C 中的指针都会被以这两种类型引入到 Swift 中:C 中 const 修饰的指针对应 UnsafePointer (最常见的应该就是 C 字符串的 const char * 了),而其他可变的指针则对应 UnsafeMutablePointer。除此之外,Swift 中存在表示一组连续数据指针的 UnsafeBufferPointer<T>,表示非完整结构的不透明指针 COpaquePointer 等等。另外你可能已经注意到了,能够确定指向内容的指针类型都是泛型的 struct,我们可以通过这个泛型来对指针指向的类型进行约束以提供一定安全性。
对于一个 UnsafePointer<T> 类型,我们可以通过 pointee 属性对其进行取值,如果这个指针是可变的 UnsafeMutablePointer<T> 类型,我们还可以通过 pointee 对它进行赋值。比如我们想要写一个利用指针直接操作内存的计数器的话,可以这么做
func incrementor(ptr: UnsafeMutablePointer<Int>) {
ptr.pointee += 1
}
var a = 10
incrementor(&a)
// a == 11
2.如果需要像C一样,临时使用某个变量的地址,可以用### withUnsafePointer / withUnsafeMutablePointer,在闭包中生一个指针,之后传递到glGenFramebuffers,而且这个闭包是同步执行,即立即执行
let _ = withUnsafeMutablePointer(to: &self.frameBuffer) { ptr in
glGenFramebuffers(1, ptr)
print("---00-")
}
UnsafePointer<T> 是一个指定类型的指针 而 UnsafeRawPointer 是一个未指定数据类型的指针有点像C 语言的 (void *)
Decodable & Encoder 协议
Decodable 是 Swift 4 中新增的一个协议,用于将 JSON 数据解析为 Swift 类型。通过实现 Decodable 协议,我们可以方便地将 JSON 数据转换为我们所需要的类型,无需手动解析 JSON。这极大地简化了从网络获取数据并处理数据的过程。但是,这里有个缺陷就是,如果JSON的某些字段在结构体或者类中不存在的话(或者没有设置为可选类型),会导致解释报错
Decodable 是 Codable 协议的一个组成部分,它可以将 JSON 或 PropertyList 格式的数据反序列化为对象。如果一个类型要支持 Decodable 协议,就需要实现 init(from:) 方法。通常情况下,我们可以使用编译器自动生成的 init(from:) 方法来实现 Decodable 协议。
Encoder 是 Codable 协议的一个组成部分,它负责将 Encodable 类型的对象转换为 JSON 或 PropertyList 格式。Encoder 协议提供了很多可选的方法和属性,用于定制 JSON 或 PropertyList 的编码方式。常用的 Encoder 实现包括 JSONEncoder 和 PropertyListEncoder。
Encodable是Swift标准库中的一个协议,用于将一个类型编码为另一种格式,例如JSON或者二进制数据
结构体在遵守 Decodable 协议时,会自动为其成员变量生成一个默认的逐一成员变量初始化器,这个初始化器会自动从 JSON 中解码出对应的值并赋给结构体成员变量,因此不需要手动实现 init(from:) 方法。
这个默认的逐一成员变量初始化器是编译器自动生成的,并在需要时自动调用,因此我们在结构体中遵守 Decodable 协议是不需要实现 init(from:) 方法,编译器会为我们自动生成。相反,对于类来说,没有默认的逐一成员变量初始化器,所以必须手动实现 init(from:) 方法来为成员变量赋值
class 和 static 关键字
class关键字用于定义可以被子类重写的类方法或类属性,也可以在子类中被重写。static关键字用于定义不能被重写的类方法或类属性,即静态方法或静态属性,子类不能重写。
因此,通常情况下,如果你想要在子类中重写一个类方法或类属性,应该使用 class 关键字来定义它。如果你定义一个不允许子类重写的方法或属性,应该使用 static 关键字。如果你不确定是否需要子类重写方法或属性,可以使用 final class 关键字来定义一个不允许被重写的类。
extension 关键字
在Swift 中,extension关键字是一个语法糖,在编译的时候,会把extension的内容整合到类中,并没有新生成类
在 Swift 中,extension 是一种语法糖,它可以为一个已有的类型(class、struct、enum 或 protocol)添加新的成员,包括计算属性、方法、下标等,甚至还可以实现协议,但它并没有创建一个新的类型。在底层,Swift 编译器会将 extension 中的成员添加到原类型的定义中,以达到扩展原类型的目的。
extension 会将新添加的成员编译到一个独立的模块中,并在编译时将其合并到原类型的定义中。这样,在使用扩展中新增的成员时,就不需要在客户端代码中显式导入扩展所在的模块。
Swift 中的 extension 还可以用于对协议进行扩展,这种扩展方式被称为协议扩展。协议扩展可以为协议添加新的方法、属性等成员,也可以提供协议的默认实现,这使得可以在不修改协议定义的情况下,为多个类型添加相同的实现,避免了重复代码的出现。
在底层,Swift 编译器会将协议扩展的成员直接添加到实现该协议的类型中,因此,在使用协议扩展的成员时,也不需要在客户端代码中显式导入扩展所在的模块。
extension 不能添加存储属性
如何为协议添加默认实现?
可以使用扩展(extension)为协议添加默认实现。通过在扩展中实现协议的方法,可以为该协议的所有实现类型提供默认行为。
以下是一个示例协议和默认实现的扩展:
protocol MyProtocol {
func myMethod()
}
extension MyProtocol {
func myMethod() {
print("Default implementation of myMethod()")
}
}
这样任何继承了这个协议的类型,都有一个默认实现,注意,这些实现是可选的,如果你重写了这些实现,那么就会覆盖默认的实现
defer
defer 关键字的作用是用于定义一段在当前作用域退出之前必须被执行的代码,不管是由于 return、break、continue 抑或是抛出异常等方式导致的当前作用域的退出。
defer 语句可以被放置在函数、方法、初始化器或者任意代码块的任意位置,并且可以有多个,它们的执行顺序是逆序的,即最后定义的 defer 语句最先执行。
defer 关键字通常用来在函数或方法的结束前进行资源清理,例如打开文件或者网络请求之类的操作,因为无论代码块是以何种方式退出,defer 语句都能保证这些资源得到释放。
mutating func next() -> Int? {
if count == 0 {
return nil
} else {
// 这里defer 的作用是先返回,后-1
defer { count -= 1 }
return count
}
}
@_exported import
@_exported import 是 Swift 中的一个语法特性,用于将某个模块中的所有公开接口(public)暴露给当前模块。它的中文含义为“导出式导入”。
interview www.jianshu.com/p/a70841d17…
通用代码,方便cv
单例的书写
public class fileManage{
public static let shared:fileManage = {
print("初始化单例~~");
return fileManage();
}()
// public static let shared = fileManage();
//屏蔽系统的init方法
private init(){}
}
懒加载书写
struct lazyManager {
lazy var aInt = { () -> Int in
return 6
}()
lazy var array:Array<Int> = {() -> Array in
return Array<Int>.init()
}()
}
selector书写注意点
主要要是带参数,参数必须要是可省略的带_,否者编译报错,还必须带@objc
@objc func btnCli(_ btn:UIButton) {
}
butt.addTarget(self, action:#selector(ViewController.btnCli(_:)), for: .touchUpInside)
打印
通过转换成OC的NSData,打印台能够打印出Data的具体字节内容,没转化直接打印Data,打印的是Data大小
let OCData = signedHex as NSData
print("signedHex === \(OCData)")
as? 和 as
as?
//如果没法转换成string 就赋值 "" 相当于 OC的:双目运算符
//注意这个cast["msg"] 取出的类型是Any?
let info = cast["msg"] as? String ?? ""
//如果是String? 只需要
let msg00:String? = "123"
let msg01 = msg00 ?? ""
注意如果as? 转换失败,那么就变成 nil
比如:
let abc : Int? = "string" as? Int //abc的结果为nil
在编译时执行的 as
因为 as 是在编译期执行的,而在编译期, 4 还只是个字面量,并没有给它赋值为 Int 类型,所以它会转型成功。4 as Double 与下面的代码是等价的。
let x = 4 as Double
let x : Double = 4
属性观察器
- 父类在自己的初始化器中赋值不会触发属性观察器,但是在子类的初始化中赋值就会触发属性观察器
- 子类可以把父类的计算属性和存储属性重写成计算属性,但是不能重写成存储属性
生命周期方法
静态属性(static let),它在类加载的时候就会被初始化并赋值。在 Swift 中,静态属性是属于类而不是实例的属性,它们在首次访问时被懒加载(lazy loaded),也就是说,它们会在第一次被访问时才进行初始化。
Swift和OC混编注意点
1、OC调用Swift的类时候,Swift的类必须权限必须大于或者等于public,否者不会在桥接文件xxx-Swift.h中生成
2、 Swift 函数可以带默认参数,但是当这个函数导出给OC用的时候,默认参数是必须加上,所以在OC调用的时候,不能省略默认参数。(查看桥接文件发现)
//Swift 函数
@objc public func requestSearchProducts(pageIndex:Int,pageSize:Int,content:String , catalogId: Int = -1, completion: @escaping (Any?,Error?)->());
//桥接过去之后变成
- (void)requestSearchProductsWithPageIndex:(NSInteger)pageIndex pageSize:(NSInteger)pageSize content:(NSString * _Nonnull)content catalogId:(NSInteger)catalogId completion:(void (^ _Nonnull)(id _Nullable, NSError * _Nullable))completion;
而且只有一个函数。没有生成两个~~
3、Swift 的Int? 类型不能转换成OC,如果一个类或者方法,有Int?类型,可以用NSNumber? 代替用于桥接
4、元祖
元祖类型不能桥接出来让OC使用
// 1. 通过索引的方式访问元祖
http404Error.0
http404Error.1
// 2. 通过分解赋值成单独的常量或变量
let (statusCode, statusMessage) = http404Error
print("(statusCode) ---- (statusMessage)")
// 2.1 可以通过 _ 忽略不想要的值, 如元祖中的 "Not Found" 不需要使用到的情况下可以这样做
let (statusCode2, _) = http404Error
print(statusCode2)
let httpError = (404,"not found")
@objc func aMethod01() -> (Int,String) {
return httpError
}
//编译报错Method cannot be marked @objc because its result type cannot be represented in Objective-C
5、在OC项目中集成包含Swift的三方库必须在主工程创建一个空的Swift文件,要不编译不通过
6、枚举
枚举的关联值和原始值是不一样的,但是原始值不会占用枚举变量的内存(没有存储在枚举变量的内存里面),如果你要获得这个枚举变量的,可以通过调用rawValue 方法
enum Season: Int {
case Spring = 1, summer, autumn, winter
}
let fi = Season.Spring
let sec = Season.summer
print("sec:\(sec)") // sec:summer
print("sec:\(sec.rawValue)") // sec:2
enum Password {
case number(Int,Int)
case other
}
let pwd1 = Password.number(10, 10)
print("MemoryLayout:\(MemoryLayout<Password>.size)") //MemoryLayout:17
print("MemoryLayout:\(MemoryLayout<Password>.stride)") // MemoryLayout:24
print("MemoryLayout:\(MemoryLayout<Password>.alignment)") // MemoryLayout:8
// .size 是实际内存需要的大小,.stride 是实际分配的内存大小,.alignment 是内存对齐的大小
枚举类型要是想提供出给OC用必须遵守Int协议
@objc public enum OEDeviceInt:Int{
public typealias RawValue = Int
case agb
}
7、Swift调用OC
需要在xxx--Bridging-Header.h中import需要让Swift调用的类
但是如果是framework,比如Swift中调用OC的库(pod库),就需要用到.modulemap文件。不但可以导出OC给Swift使用还能导出C++/C
Swift 中最简单最优雅的引用 oc 和 c 方式是利用.modulemap
常见的互换
@objc public private(set) static var clientId: String? ==> 转换成OC代码如下:
@property (nonatomic, class, readonly, copy) NSString * _Nullable clientId;
8、OC 通过反射 调用Swift 的类
通过添加@objc(OEMScenesMainVC)来确定导出文件的类型名称为OEMScenesMainVC 之后在OC可以通过#import <xxx/xxx-Swift.h>文件之后利用OC反射
Class targetControllerClass = NSClassFromString(@"StringClassName");
id targetClass = [[targetControllerClass alloc] init];
9 递归调用
//swift类调OC类,OC类中再调swift会导致编译失败,所以此类仅暴露给Swift使用,不能在此类中调用Swift代码