一、经典题
1.Swift中的常量和OC中的常量有啥区别?
OC中用 const 是用来表示常量的,而 Swift 中用 let 是用来判断是不是常量。
OC中的常量(const)是编译期决定的,Swift中的常量(let)是运行时确定的。
上面的区别更进一步说,OC中 const 表明的常量类型和数值是在 compilation time 时确定的;而 Swift 中 let 只是表明常量(只能赋值一次),其类型和值既可以是静态的,也可以是一个动态的计算方法,它们在 runtime 时确定的。
Swift中的常量可以是非特定类型的,即它们的类型可以在运行时确定。例如,你可以定义一个常量,其值可以是任何类型,只要在赋值时类型明确即可。
在Objective-C中,常量通常使用#define预处理器指令或const关键字定义。使用#define定义的常量在编译时会被替换为它们的值,而使用const定义的常量在运行时不可修改。Objective-C中的常量类型必须在编译时确定,并且不能用作函数参数。
2.指定构造器和便利构造器有什么区别?
类必须要有一个指定构造器,可以没有便利构造器
便利构造器必须调用本类另一个构造器,最终调用到本类的指定构造器
便利构造器前面需要添加convenience关键字
3.Any和AnyObject的区别?
AnyObject只能表示引用类型的任何实例,相当于Objective-C中的id类型。
Any可以表示类,结构体,枚举的任何实例。
AnyObject是Any的子集。
(延伸 oc中 id的本质是struct objc_object结构体指针,可以指向任何OC对象,id指向基础数据类型会报编译错误。)
4.In-Out(inout关键字)参数了解过吗?
默认情况下,函数参数默认是常量,试图从函数体中去改变一个函数的参数值会报编译错误。如果希望函数修改参数值,并在函数调用结束后仍然保留。这个时候就需要用到inout关键字。
注意事项:
inout关键字只能修饰变量,无法修饰常量,因为常量和字面量不能被修改。
inout参数不能有默认值,可变参数不能标记为inout。
调用函数的时候,应该在变量名前放置&符号表示该变量可以由函数修改。
var variable: Int = 1
func changeNumber(num:inout Int) {
num = 2
print(num)
}
changeNumber(num: &variable) // 2
inout关键字修饰的变量传递过程:
- 如果实参有物理内存地址,且没有设置属性观察器
答: 直接将实参的内存地址传入函数 (实参进行引用传递)
2.如果实参是计算属性 或者 设置了属性观察器
答:采取了Copy In Copy Out 的做法
- 调用该函数时,先复制实参的值,产生副本 [get]
2.将副本的内存地址传入函数 (副本进行引用传递),在函数内部可以修改副本的值
3.函数返回后,再将副本的值覆盖实参的值 [set]
var man = person(heigth: 1, width: 1)
{
willSet(newValue){
print("即将 \(newValue) ")
}
didSet (oldValue){
print("已经 \(man) \(oldValue) ")
}
}
func addValue( _ value : inout person)->person
{
value.heigth = 20
value.width = 20
//如果将属性观察关掉,崩溃:因为违反了独占原则:一个地址被别的地方引用后,不可以访问。Swift 需要对变量进行独占访问时才能修改该变量。本质上来说,当一个变量作为 inout 参数或者 mutating 方法中的 self 被修改时,不能通过不同的名称被访问的。
print("新",value,"旧",self.man)
return value
}
//执行
let k = addValue(&man)
print(k)
//打印 因为有属性观察器所以 copy。 执行方法函数结束后再进入属性观察器,再给返回值。
// 新 person(heigth: 20, width: 20) 旧 person(heigth: 1, width: 1)
// 即将 person(heigth: 20, width: 20)
// 已经 person(heigth: 20, width: 20) person(heigth: 1, width: 1)
// person(heigth: 20, width: 20)
总结:
- inout参数的本质是地址传递 (引用传递),不管什么情况都是传入一个地址。
- Swift 值类型中,属性的默认行为是不可变的。mutating关键字,用于在结构体或枚举的方法中修改属性。使用mutating修饰的方法(func)在修改属性后更新原始值,而不是返回一个新的副本。
(mutating关键字只能用于值类型,mutating关键字本质是包装了inout关键字,加上mutating关键字后参数值会变成地址传递。
类对象是指针,传递的本身就是地址值,所以 mutating关键字对类是透明的,加不加效果都一样。)
5.什么是自动闭包、逃逸闭包、非逃逸闭包?
非逃逸闭包, 永远不会离开一个函数的局部作用域的闭包就是非逃逸闭包。
func player(complete:(Bool)->()){
complete(true) //执行闭包 传入真
}
self.player { bool in
print( bool ? "yes":"no")
} // yes
当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。在形式参数前写@escaping来明确闭包是允许逃逸的。
var completionHanglers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHangler: @escaping () -> Void) {
completionHanglers.append(completionHangler)
}
completionHanglers.first?()
自动闭包:是一种自动创建的闭包,用来把作为实际参数传递给函数的表达式打包的闭包.他不接受任何实际参数,并且当它被调用时,它会返回内部打包的表达式的值.
Autoclosure 是用于延迟执行闭包的一种技术。使用 Autoclosure,我们可以将闭包作为参数传递给函数或方法,但是闭包不会立即执行。相反,它会在需要时才会被执行。
public func assert(_ condition:@autoclosure () -> Bool,_ message: @autoclosure () -> String = String(), file:StaticString = #file, line: Unit = #line)
let num = 3
assert(num>3,"num不能大于3")
var customersInLine = ["李一", "张2", "刘3", "赵四", "王五"]
override func viewDidLoad() {
super.viewDidLoad()
print(customersInLine.count)
// 打印出“5”
let customerProvider = { self.customersInLine.remove(at: 0) }//自动闭包
print(customersInLine.count)//没有执行呢还 还是打印出“5”
print("移除了 \(customerProvider())!") //移除了 李一!
print(customersInLine)
serve(customer: customersInLine.remove(at: 0))
// 不用 @autoclosure 修饰
serve2(customer: { customersInLine.remove(at: 0) } )
}
func serve(customer customerProvider: @autoclosure () -> String) {
print("移除了 \(customerProvider())!")
print(customersInLine)
}
func serve2(customer customerProvider: () -> String) {
print("移除了 \(customerProvider())!")
print(customersInLine)
}
6.什么是Optional(可选项或者叫可选类型)?
在变量类型后加问号(?)表示该变量可能有值也可能没有值
底层Optional是一个包含None和Some(Wrapped)两种类型的泛枚举类型,Optional.None即nil,Optional.Some非nil。
@frozen public enum Optional<Wrapped> : ExpressibleByNilLiteral {
/// The absence of a value.
///
/// In code, the absence of a value is typically written using the `nil`
/// literal rather than the explicit `.none` enumeration case.
case none
/// The presence of a value, stored as `Wrapped`.值的存在,存储为“Wrapped”
case some(Wrapped)
}
var optional1: String? = nil
var optional2: String? = .none
在OC中, nil是一个指向不存在对象的指针(OC非对象类型也可以设置成nil但是会有警告⚠️:指针不兼容)
在Swift中,nil不是指针,而是值缺失的一种特殊类型,任何类型的可选项都可以设置为nil而不仅仅是对象类型,Swift 是一种类型安全的语言,而 Objective-C 不是。这意味着在 Swift 中,每个类型的 nil 都是不同的,例如 Int? 的 nil 和 String? 的 nil 是不同的,它们所代表的空值的类型不同。
非可选项类型,不可设置为nil。Optional及可以包装值类型也可以包装引用类型。
7.swift的派发机制(函数的派发机制:静态派发(直接派发)、函数表派发、消息派发)?
1)swift中所有值类型:struct、enum使用直接派发。
2)swift中协议的extensions(扩展)使用直接派发,初始声明函数使用函数表派发
3)swift中class中extensions使用直接派发,初始化声明函数使用函数表派发,dynamic修饰的函数使用消息派发。
4)swift中NSObject的子类用@nonobjc或final修饰的函数使用直接派发,初始声明函数使用函数表派发,dynamic修饰的extensions使用消息派发
swift(关键字)显示指定派发方式?
1)添加final关键字的函数使用直接派发
2)添加static关键字函数使用直接派发
3)添加dynamic关键字函数使用消息派发
4)添加@objc关键字的函数使用消息派发
5)添加@inline关键字的函数告诉编译器可以使用直接派发
8.try、try?、try
“try”需要用“ do catch”捕捉异常,如果在“try”代码块中出现异常,程序会跳转到相应的“catch”代码块中执行异常处理逻辑,然后继续执行“catch”代码块后面的代码。
enum NormalError: Error {
case one
case two
case three
case four
}
func someFunction(words: String) throws -> String {
switch words {
case "one":
throw NormalError.one
case "two":
throw NormalError.one
case "three":
throw NormalError.one
case "four":
throw NormalError.one
default:
return "ok"
}
}
//如果在“try”代码块中出现异常,程序会跳转到“catch”代码块中执行异常处理逻辑。
do {
try someFunction(words: "five")
} catch {
print("An error occurred: \(error)")
}
“try?”是返回一个可选值类型,如果“try?”代码块中出现异常,返回值会是“nil”,否则返回可选值。可以在运行时判断是否有异常发生。
// 返回值是一个可选类型,如果执行正常的话就存在返回值,否则如果抛出错误的话返回值为nil
let result = try? say(words: "four")
// 可选绑定
if let res = try? doSomething(words: "four") {
}
else { print("出现错误") }
// 提前退出
guard let resu = try? say(words: "four") else { return }
“try!”类似于可选型中的强制解包,它不会对错误进行处理,如果“try!”代码块中出现异常,程序会在异常发生处崩溃。
let result = try! someFunction()
print("The result is \(result)")
print(try? divide(2,1))//divide 整数除法运算符(/)
// Optional(2.0)
print(try? divide(2,0))
// nil
print(try! divide(2,1))
// 2.0
print(try! divide(2,0))
// 崩溃
9.存储属性、计算属性和类型属性的区别?
存储属性用来进行数据的存储, 需要分配内存空间。子类(无论是let var static修饰)不能直接重写存储属性。重写(Override)通常用于子类覆盖父类的方法、计算属性或观察者。
计算属性:用来定义计算的过程, 不需要分配空间.计算属性必须使用var关键字进行定义。
类型属性:用于定义某个类所有实例共享的数据。oc和swift都可以有类型属性。
oc需要添加属性关键字class定义类型属性(更像是通过调用属性来调用一个方法,比较像计算属性,oc中无法真正存储值。)
swift类型属性:使用static、 class来定义。static(不支持重写) class(支持子类重写)。
class HealthArticlesVC: BaseViewController {
// 存储属性
var age = 12
let name = "小花"
var ppName: String = { return "abc" }()
private let _searchTextField: UITextField = UITextField() //搜索
}
//计算属性 应用场景:一般用于获取现有属性加工和计算后的结果。
var petInfo:String{
get {
return "getbackName"
}
set (newInfo) {
print("\(newInfo)")
}
}
//类型属性
struct SomeStructure {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int { return 1}
}
class SomeClass {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 27
}
class var overrideableComputedTypeProperty: Int {
return 107
}
}
10.extension 中能增加存储属性吗?
extension :可以增加计算属性,不能增加存储属性。
extension是用来给存在的类型添加新行为的并不能改变类型或者接口本身。因为 extension 不能为类或结构体增加实际的存储空间。
(swift 没有分类的概念 只有extension扩展)
11.swift 中 closure闭包 与 OC 中 block 的区别?
相同点:都是一段可以执行的代码块
1、closure 是匿名函数,block 是一个结构体对象。
2、closure 可以通过逃逸闭包来在内部修改变量,block 通过 __block 修饰符。
12.作用域关键字的区别
. private 只可以在本类而且在同一个作用域中被访问.
. fileprivate 可以在本类中进行访问.
. internal (默认值) 只能访问自己module(模块)的任何internal实体,不能访问其他模块中的internal实体.
. public 类似于final,可以被其他module被访问,不可以被重载和继承.
. open 可以被其他module被访问、被重载、被继承.
二、冷门题
1.什么是柯里化?
柯里化:把接受多个参数的函数变成接受一个单一参数(最初函数的第一个)的函数,并且返回接受余下的参数和返回结果的新函数。
func multiplyTwoNumbers(_one:Int) -> (Int) -> Int {
return{$0* one}
}
//调用
let a = multiplyTwoNumbers(2)
let b = a(3)
print(b)//输出6
2.swift 中map、flatMap、compactMap的区别
map: 正常遍历
flatMap:已弃用:4.1,重命名为:“compactMap(_:)”,消息:“对于闭包返回可选值的情况,请使用compactMap。
flatMap:可以自动解包。特殊表达式可以去nil。
//二维数组可以解包
let array = [[1,2,3], [4,5,6], [7,8,9]]
let arrayFlatMap = array.flatMap { $0 }
print(arrayFlatMap) //[1, 2, 3, 4, 5, 6, 7, 8, 9]
//这种则无法解包
var customArr2 : [Any] = ["李一", "张2", "刘3", "赵四", "王五",["吴6","范7","吕宝贝8"]]
let array2 = customArr2.flatMap { $0}
print(array2)//["李一", "张2", "刘3", "赵四", "王五", ["吴6", "范7", "吕宝贝8"]]
let fruits = ["apple", "banana", "orange", ""]
let counts = fruits.flatMap { fruit -> Int? in
let length = fruit.characters.count
guard length > 0 else {
return nil
}
return length
}
// [5,6,6]
compactMap :函数会自动过滤掉映射结果为nil或者无效的值,只保留非空的映射结果。
let possibleNumbers = ["1", "2", "three", "///4///", "5"]
//普通map遍历
let mapped: [Int?] = possibleNumbers.map { str in Int(str) }
print(mapped)
//[Optional(1), Optional(2), nil, nil, Optional(5)]
let compactMapped: [Int] = possibleNumbers.compactMap { str in Int(str) }
print(compactMapped) //[1, 2, 5]
3.什么是函数式编程?
面向对象编程:将要解决的问题抽象成一个类,通过给类定义属性和方法,让类帮助我们解决需要处理的问题(即命令式编程,给对象下一个个命令)。
函数式编程:数学意义上的函数,即映射关系(如:y = f(x),就是 y 和 x 的对应关系,可以理解为"像函数一样的编程")。它的主要思想是把运算过程尽量写成一系列嵌套的函数调用。
例:
数学表达式
(1 + 2) * 3 - 4
传统编程
var a = 1 + 2
var b = a * 3
var c = b - 4
函数式编程
var result = subtract(multiply(add(1,2), 3), 4)
函数式编程的好处:
代码简洁,开发迅速;
接近自然语言,易于理解;
更方便的代码管理;
易于"并发编程";
代码的热升级。
4.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()
}
}
使用泛型也可以
class AnyList<T>: ListProtcol {
var list = [T]()
func push(_ element: T) {
self.list.append(element)
}
func pop(_ element: T) -> T? {
return self.list.popLast()
}
}
//可以使用 where 字句限定 Element 类型, 如:
extension ListProtcol where Element == Int {
func isInt() ->Bool {
return true
}
}
5.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(){}
}
6.OC中的协议和swift中的协议 有什么区别?
Objective-C 的协议:声明方法,不能实现。
Swift 中的协议:它可以定义计算属性、方法、关联类型、静态方法和静态计算属性等。
Swift 的协议还支持泛型、默认实现、条件约束。
protocol MyProtocol {
// 计算属性
var name: String { get set }
var age: Int { get }
// 方法
func greet() -> String
// 关联类型
associatedtype Item
func processItem(item: Item)
// 静态方法
static func staticMethod() -> String
// 静态属性
static var staticProperty: String { get set }
}
extension MyProtocol {
// 默认实现
func defaultImplementation() -> String {
return "Default implementation"
}
}
7.属性默认
OC中 属性默认 关键字 nonatomic,strong ,readwrite
swift中 属性默认 关键字 nonatomic,strong