swift学习资料2022
- String 与 NSString 的关系与区别
- Swift的String是struct,而NSString类是NSObject
- 什么时候用NSString:
- NSString特有操作和动态特性
- NSString有containsString,string只能用rangeOfString
- String与Range配合比较麻烦
- 什么情况下使用String:
- String更符合字符串“不变”特性,性能提升
- String实现了像CollectionType的接口。比如for...in的枚举遍历所有字
- Swift字符串的Range截取
-
-
let range1:ClosedRange = 1 ... 4 let range2:CountableClosedRange = 1 ... 4 let range3:Range = 1 ..< 4 let range4:CountableRange = 1 ..< 4 let array = ["a", "b", "c", "d", "e", "f"] print(array[range1]) print(array[range2]) print(array[range3]) print(array[range4])
-
- throws 和 rethrows 的用法与作用
- rethrows:如果一个函数A(例如map)的其中一个参数是一个函数类型B(map的闭包),且B有可能接受throw或不throw的函数,那么A就可以声明为rethrows,这时如果接受的B是不需要的throw的,就不用写do catch来处理错误,如果接受的是throw的才需要。
- open与public的区别?
- public:可以别任何人访问,但是不可以被其他module复写和继承。
- open:可以被任何人访问,可以被继承和复写。
- swift中 closure 与OC中block的区别
- closure是匿名函数、block是一个结构体对象
- block内部会对值类型做一份复制,并不指向之前的值的内存地址,而对于对象类型则会指向对象当前的内存地址,所以修改number时,block里的number数值不变,而修改字符串时,block里的字符串则改变了;closure则默认给外部访问的变量加上了__block修饰词的block。
- swift中,如何阻止方法,属性,下标被子类改写?
- aaaaaa final 关键字声明类、属性、方法和下标。
final 声明的类不能被继承,final 声明的属性、方法和下标不能被重写。
如果只是限制一个方法或属性被重写,只需要在该方法或者属性前加一个 final.
如果需要限制整个类无法被继承, 那么可以在类名之前加一个final。
- aaaaaa final 关键字声明类、属性、方法和下标。
- associatedtype 的作用
- 关联类型: 为协议中的某个类型提供了一个别名,其代表的真实类型在实现者中定义
- 泛型(generic)可以使我们在程序代码中定义一些可变的部分,在运行的时候指定。
使用泛型可以最大限度地重用代码、保护类型的安全以及提高性能。 -
//协议,使用关联类型 protocol TableViewCell { associatedtype T func updateCell(_ data: T) } //遵守TableViewCell class MyTableViewCell: UITableViewCell, TableViewCell { typealias T = Model func updateCell(_ data: Model) { // do something ... } }
- try? 和 try!是什么意思
- try?不处理错误,抛出异常函数时, 如果函数抛出异常, 则返回 nil, 否则返回函数返回值的可选值,
- try!保证不会出现错误 强制解,抛出异常的时候崩溃, 否则则返会函数返回值
- map、filter、reduce 的作用?
- [1, 2, 3].map{"\($0)"}// 数字数组转换为字符串数组,["1", "2", "3"]
- [1, 2, 3].filter{$0 % 2 == 0} // 筛选偶数,2
- [1, 2, 3].reduce(""){$0 + "\($1)"}// 转换为字符串并拼接,"123"
- defer、guard的作用?
- defer:主要的作用是延迟执行以及在return之前执行。
- 如何自定义下标获取
- 实现 subscript 即可,索引除了数字之外, 其他类型也是可以的
-
extension AnyList { subscript(index: Int) -> T{ return self.list[index] } subscript(indexString: String) -> T?{ guard let index = Int(indexString) else { return nil } return self.list[index] } }
- 这段复杂的代码按字母顺序对名称数组进行排序。尽可能简化闭包。
-
类型推断系统会自动判断闭包中参数的类型和返回类型,就可以去掉类型: animals.sort { (one, two) -> Bool in return one < two } 可以用$I符号替换参数名: animals.sort { return $0 < $1 } 在单语句闭包中,可以省略返回关键字。最后一条语句的值将成为闭包的返回值: animals.sort { $0 < $1 } 后,由于Swift知道数组的元素符合equatable,因此可以简单地编写: animals.sort(by: <)
-
- 什么是可选的,可选可以解决哪些问题?
- 使用可选类型(optionals)来处理值可能缺失的情况。在objective-c中,只有在使用nil特殊值的引用类型中才可以表示值缺失。值类型(如int或float)不具有此功能。
Swift将缺乏值概念扩展到引用类型和值类型。可选变量可以包含值或零,表示是否缺少值。
- 使用可选类型(optionals)来处理值可能缺失的情况。在objective-c中,只有在使用nil特殊值的引用类型中才可以表示值缺失。值类型(如int或float)不具有此功能。
- 构体和类之间的主要区别。
- 类支持继承;结构不支持。
类是引用类型;结构体是值类型。
- 类支持继承;结构不支持。
- 什么是泛型(通用类型)?它们解决了什么问题?
- 提高代码的复用性。
- 在swift中,可以在函数和数据类型中使用泛型,例如在类、结构体或枚举中。
泛型解决了代码重复的问题。当有一个方法接受一种类型的参数时,通常会复制它以适应不同类型的参数。
例如,在下面的代码中,第二个函数是第一个函数的“克隆”,但它接受字符串而不是整数。 -
func areIntEqual(_ x: Int, _ y: Int) -> Bool { return x == y } func areStringsEqual(_ x: String, _ y: String) -> Bool { return x == y } areStringsEqual("ray", "ray") // true areIntEqual(1, 1) // true 通过采用泛型,可以将这两个函数合并为一个函数,同时保持类型安全。下面是通用实现: func areTheyEqual<T: Equatable>(_ x: T, _ y: T) -> Bool { return x == y } areTheyEqual("ray", "ray") areTheyEqual(1, 1) 由于在本例中测试的是相等性,所以将参数限制为遵守 Equatable 协议的任何类型。此代码实现了预期的结果,并防止传递不同类型的参数。
- nil 和 .none有什么区别?
- 没有区别,none == nil
- Swift和OC的区别?
- swift是静态语言,有类型推断,OC是动态语言。
- swift面向协议编程,OC面向对象编程
- swift注重值类型,OC注重引用类型。
- swift支持泛型,OC只支持轻量泛型
- swift支持静态派发(效率高)、动态派发(函数表派发、消息派发)方式,OC支持动态派发(消息派发)方式。
- swift支持函数式编程
- swift的协议不仅可以被类实现,也可以被struct和enum实现
- swift有元组类型、支持运算符重载
- swift支持命名空间
- swift支持默认参数
- swift比oc代码更加简洁
- swift语法简单易读、代码更少,更加清晰、易于维护
- 更加安全,optional的使用更加考验 程序员 对代码安全的掌控
- 泛型、结构体、枚举都很强大
- 函数为一等公民,便捷的函数式编程
- 有命名空间 基于module
- 类型判断
- swift的派发机制
- 三种派发机制:直接派发, 函数表派发 和 消息机制派发
- 直接派发 :最快指令集更少, 编译器有很大的优化空间, 例如函数内联等,但缺乏动态性,没继承,静态调用。
- 函数表派发:使用了一个数组存储类声明和函数指针. 称为虚函数表。查表比直接派发慢. 从字节码角度看, 多两次读和一次跳转的损耗. 另一个慢在于编译器无法优化.这种基于数组, 缺陷在于无法拓展. 子类会在最后插入新的函数, 没有位置可以让 extension 安全地插入函数.
- 消息机制派发:是oc的消息转发机制,是最动态的方式. 效率最低,但是缓存后跟函数表派发一样快
- Swift 的派发机制:使用 dynamic 修饰的时候会通过 Objective-C 的运行时进行消息机制派发.总结起来有这么几点:
- 值类型、协议和类的extension总是会使用直接派发
- NSObject 声明作用域里的函数都会使用函数表进行派发.
- 协议里声明的, 并且带有默认实现的函数会使用函数表进行派发.
- NSObject 的 extension 会使用消息机制进行派发
- 指定派发方式
- 直接派发:final、static、@inline
- 消息派发:dynamic、@objc
- 三种派发机制:直接派发, 函数表派发 和 消息机制派发
- Struct和Class区别
- Struct不支持继承、Class支持继承
- Struct是值类型,Class是引用类型
- Struct无法修改自身属性值,函数需要添加mutating关键字
- Struct初始化方法是基于属性的
- Struct不需要deinit方法,因为值类型不关心引用计数,Class需要deinit方法。
- swift中mutating的作用?
- swift中协议是可以被Struct和enum实现的,mutating关键字是为了能在被修饰的函数中修改struct或enum的变量值。对Class完全透明。
- associatedtype 的作用?
- 简单来说就是 protocol 使用的泛型
-
protocol ListProtcol { associatedtype Element func push(_ element:Element) func pop(_ element:Element) -> Element? }
- 实现协议的时候, 可以使用 typealias 指定为特定的类型, 也可以自动推断, 如
-
class IntList: ListProtcol { typealias Element = Int // 使用 typealias 指定为 Int var list = [Element]() func push(_ element: Element) { self.list.append(element) } func pop(_ element: Element) -> Element? { return self.list.popLast() } } class DoubleList: ListProtcol { var list = [Double]() func push(_ element: Double) {// 自动推断 self.list.append(element) } func pop(_ element: Double) -> Double? { return self.list.popLast() } }
- 什么时候使用 final
- final 用于限制继承和重写. 如果只是需要在某一个属性前加一个 final。 如果需要限制整个类无法被继承, 那么可以在类名之前加一个final
- 定义静态方法时关键字 static 和 class 有什么区别
- static 定义的方法不可以被子类继承, class 则可以
-
class AnotherClass { static func staticMethod(){} class func classMethod(){} } class ChildOfAnotherClass: AnotherClass { override class func classMethod(){} //override static func staticMethod(){}// error }
- Self 的使用场景
- Self 通常在协议中使用, 用来表示实现者或者实现者的子类类型. 例如, 定义一个复制的协议
-
protocol CopyProtocol { func copy() -> Self }
- 如果是结构体去实现, 要将Self 换为具体的类型
-
struct SomeStruct: CopyProtocol { let value: Int func copySelf() -> SomeStruct { return SomeStruct(value: self.value) } }
- 如果是类去实现, 则有点复杂, 需要有一个 required 初始化方法
-
class SomeCopyableClass: CopyProtocol { func copySelf() -> Self { return type(of: self).init() } required init(){} }
- 一个类型表示选项,可以同时表示有几个选项选中(类似 UIViewAnimationOptions ),用什么类型表示
- OptionSet, 一般使用 struct 实现. 由于 OptionSet 要求有一个不可失败的init(rawValue:) 构造器, 而 枚举无法做到这一点(枚举的原始值构造器是可失败的, 而且有些组合值, 是没办法用一个枚举值表示的)
- 选项
struct SomeOption: OptionSet { let rawValue: Int static let option1 = SomeOption(rawValue: 1 << 0) static let option2 = SomeOption(rawValue:1 << 1) static let option3 = SomeOption(rawValue:1 << 2) } let options: SomeOption = [.option1, .option2]
- inout 的作用
- 输入输出参数, 如:
-
func swap( a: inout Int, b: inout Int) { let temp = a a = b b = temp } var a = 1 var b = 2 print(a, b)// 1 2 swap(a: &a, b: &b) print(a, b)// 2 1
- Error 如果要兼容 NSError 需要做什么操作
- 其实直接转换就可以, 例如
SomeError.someError as NSError
但是这样没有错误码, 描述等等, 如果想和 NSError 一样有这些东西, 只需要实现LocalizedError
和CustomNSError
协议, 有些方法有默认实现, 可以略过, 如: -
enum SomeError: Error, LocalizedError, CustomNSError { case error1, error2 public var errorDescription: String? { switch self { case .error1: return "error description error1" case .error2: return "error description error2" } } var errorCode: Int { switch self { case .error1: return 1 case .error2: return 2 } } public static var errorDomain: String { return "error domain SomeError" } public var errorUserInfo: [String : Any] { switch self { case .error1: return ["info": "error1"] case .error2: return ["info": "error2"] } } } print(SomeError.error1 as NSError) // Error Domain=error domain SomeError Code=1 "error description error1" UserInfo={info=error1}
- 其实直接转换就可以, 例如
- 什么是高阶函数
- 一个函数如果可以以某一个函数作为参数, 或者是返回值, 那么这个函数就称之为高阶函数, 如 map, reduce, filter
- 如何解决引用循环
- 转换为值类型, 只有类会存在引用循环, 所以如果能不用类, 是可以解引用循环的
- delegate 使用 weak 属性
- 闭包中, 对有可能发生循环引用的对象, 使用 weak 或者 unowned, 修饰
- 下面的代码会不会崩溃,说出原因
-
var mutableArray = [1,2,3] for _ in mutableArray { mutableArray.removeLast() }
- 不会,removeLast如果是空数组会崩溃,在for等同于一个空判断的迭代器,只要有值才执行
-
- 给集合中元素是字符串的类型增加一个扩展方法,应该怎么声明
-
extension Array where Element == String { var isStringElement:Bool { return true } } ["1", "2"].isStringElement //[1, 2].isStringElement// error
-
- 一个 Sequence 的索引是不是一定从 0 开始?
- sequence是相同类型集合的迭代器,迭代器不是数组,索引是可以继续迭代还是重新开始,所以它不一定是从0开始,跟序号索引不强绑定,它是next,不是从0开始
- 数组都实现了哪些协议
- MutableCollection, 实现了可修改的数组
- ExpressibleByArrayLiteral, 实现了数组可以从[1, 2, 3] 这种字面值初始化的能力
- 如何让自定义对象支持字面量初始化
ExpressibleBy____Literal
ExpressibleByArrayLiteral
可以由数组形式初始化ExpressibleByDictionaryLiteral
可以由字典形式初始化ExpressibleByNilLiteral
可以由nil 值初始化ExpressibleByIntegerLiteral
可以由整数值初始化ExpressibleByFloatLiteral
可以由浮点数初始化ExpressibleByBooleanLiteral
可以由布尔值初始化- ExpressibleByUnicodeScalarLiteral
- ExpressibleByExtendedGraphemeClusterLiteral
- ExpressibleByStringLiteral
-
Bool : ExpressibleByBooleanLiteral Int : ExpressibleByIntegerLiteral Float、Double : ExpressibleByIntegerLiteral、ExpressibleByFloatLiteral Dictionary : ExpressibleByDictionaryLiteral String : ExpressibleByStringLiteral Array、Set : ExpressibleByArrayLiteral Optinal : ExpressibleByNilLiteral
class MyUrl{ var url:URL init(string: String){ self.url = URL(string: string) } } extension myUrl:ExpressibleByStringLiteral { init(stringLiteral value: String) { self.url = URL(string: value) } } let url:myUrl = "https://www.baidu.com" print(url.url) // "https://www.baidu.com"
var num: Int = true //错误 extension Int : ExpressibleByBooleanLiteral { public init(booleanLiteral value: Bool) { self = value ? 1 : 0 } } var num: Int = true //正确 print(num) // 输出:1
- 为什么数组索引越界会崩溃,而字典用下标取值时 key 没有对应值的话返回的是 nil 不会崩溃
- 数组是一段连续的地址,越界也是可以访问到那一片内存的,但是不合法的访问就是野指针
- 字典不一样,它存储的是指针,不是真真正的,找不到就返回nil
- 一个函数的参数类型只要是数字(Int、Float)都可以,要怎么表示
-
Int、Float 都有一个协议 func myMethod(_ value: T) where T: Numeric { print(value + 1) } 或者 ExpressibleByIntegerLiteral 协议也行
-
- 不通过继承,代码复用(共享)的方式有哪些
- Extensions
- Protocols
- 如何自定义模式匹配
-
infix operator =~ func =~ (str: String, pattern: String) -> Bool { } infix、 prefix、 postfix 用于自定义表达式的声明, 分别表示 中缀、前缀、后缀
Swift 中没有内置的正则表达式,有一个相似的功能,就是匹配模式
-
- swift语法糖?!的本质
?
和!
其实分别是Swift语言中对一种可选类型( Optional) 操作的语法糖,Optional
其实是个enum
,里面有None
和Some
两种类型。其实所谓的nil就是Optional.None
, 非nil就是Optional.Some
, 然后会通过Some(T)
包装(wrap)原始值,这也是为什么在使用Optional
的时候要拆包(从enum
里取出来原始值)的原因-
var name: String? // 上面这个Optional的声明,是”我声明了一个Optional类型值, 它可能包含一个String值,也可能什么都不包含”, 也就是说实际上我们声明的是Optional类型,而不是声明了一个String类型 (这其实理解起来挺蛋疼的...)
- 什么是函数式编程
- 编程程序的一种代码规范,主要思想是把运算过程尽量写成一系列嵌套的函数调用
- 它使代码更像自然语言,告诉程序员要干什么,而不是怎么干,把怎么干的细节拆分到各个函数中。例如1+1=,那么就变成一加一
- 特点:
- 代码简洁,开发快速
- 接近自然语言,易于理解
- 更方便的代码管理。函数式编程不依赖、也不会改变外界的状态,只要给定输入参数,返回的结果必定相同
- 易于"并发编程"。函数式编程不需要考虑"死锁"(deadlock),因为它不修改变量,所以根本不存在"锁"线程的问题
-