Swift面试篇

·  阅读 72

1.Swift与Objective-C的区别

SwiftOC
语言特性静态语言动态语言静态语言由编译器做类型推断,一旦类型确定就无法改变
动态语言在运行时可以改变其结构
命名空间同一个命名空间中的所有 Symbol不允许重名
方法调用直接调用,函数表调用,消息转发消息转发直接派发(静态派发)编译后就确定了方法的调用地址
函数表派发(动态派发)运行时通过一个函数表查找需要执行的方法
消息派发(动态派发)运行时查找找类及父类对象的方法列表

添加finalstatic@inline@nonobjc以及值类型Extensions的函数直接派发
添加dynamic@objc、的函数消息派发
初始声明函数使用函数表派发
泛型/元组/高阶函数轻量,如集合类型泛型可以使我们在程序代码中定义一些可变的部分,在运行的时候指定
元组是多个值组合成一个复合值,且值可以是任意类型
高阶函数的参数或者返回值为函数
语言效率性能更高,速度更快Swift静态编译以及Option类型相对安全
语法简洁、面向协议、高度抽象的泛型性能更高
值类型在栈空间单指针处理性能更快
文件特性.swift 单文件.h/.m包含头文件Swift中通过OpenPublicInternalFilePrivatePirvate来实现接口层面和实现成面问题
编程特性函数式编程/响应式编程/面向协议编程面向对象编程函数式编程是一种思考问题的方式
响应式编程是一种面向数据流和变化传播的编程范式,可以简化异步编程,提供更优雅的数据绑定
面向协议编程协议定义提供实现的入口遵循协议的类型需要对其进行实现;协议扩展为入口提供默实现,根据入口提供额外实现
面向对象编程使用封装和继承,将一系列相关的内容放到一起

2.Struct与Class的区别

StructClass
支持继承-
类型值类型引用类型值类型存放在栈区,赋值为深拷贝。采用copy on write策略,优化效率,操作仅仅是单个指针的移动,减少了堆上内存分配和回收次数
引用类型存放在堆区,赋值是浅拷贝,操作牵涉到合并,位移,重链接
析构方法(deinit)值类型不关系引用计数
初始化方法默认构造器指定构造器、便利构造器结构体初始化的时候必须要给属性赋值,来决定其在内存中的布局
Class初始化的时候可以暂时不用赋值
属性声明属性的时候不需要赋值声明属性的时候必须赋值或者包装成Optional-
其他方法修改属性的时候需要用**@mutating修饰
Struct 只能用
Static** 修饰
required关键字只支持Class
支持引用相等比较(===于!==),结构体不支持
-

3.Swift中常量与Objective-C中常量的区别

  • OC中常量定义如下
const int number = 0;
复制代码

ObjC中const表明的常量类型和数值是在编译时确定的;

  • Swift中常量定义如下
let number = 0
复制代码

Swift 中 let 只是表明常量(只能赋值一次),其类型和值既可以是静态的,也可以是一个动态的计算方法,它们在 runtime 运行时确定

4.!、?、??、is、as、as!、as?的定义

  • **!**强制解包,如果值为nil,会报错crash掉
  • **?**用来声明可选值,如果变量未初始化则自动初始化nil;在操作可选值时,如果可选值时nil则不响应后续的操作;使用as?进行向下转型操作
  • ?? 用来判断左侧可选值非空(not nil)时返回左侧值可选值,左侧可选值为空(nil)则返回右侧的值
  • is判断某个对象是否为某个特定类的对象
  • as从子类对象转换为父类对象,向上转型使用;消除二义性,数值类型转换;switch语句中进行模式匹配
  • **as!**父类对象强制转为子类对象,如果失败会crash,属于向下转型
  • **as?**父类对象转为子类对象,转型不成功会返回一个nil对象,成功返回可选类型(optional),需要拆包

5.Swift中final、class、static的用法

  • static修饰存储属性、计算属性、类型方法,且修饰的方法不能被继承
  • class修饰计算属性、类方法,修饰的方法可以继承
  • final修饰的类、方法、变量是不能被继承或重写的,且通过它可以显示的指定函数的派发机制。

6.Swift中单例

class Tools {
  static let share = Tools()
  private init() {
  }
}
let tools = Tools.shared
复制代码

7.Swift中延迟加载(Lazy)

    lazy var iOSResumeDescription: String = {
        return "I am an iOS developer"
    }()
复制代码

懒加载存储属性只会在首次使用时才会计算初始值属性,且懒加载属性必须声明(var)为变量,因为常量属性(let)初始化之前会有值

8.Swift中的访问控制权限

  • open:实例可被同一模块内所有实体访问,模块外可导入该模块即可访问,模块外可被继承和重写。
  • public:实例可被同一模块内所有实体访问,模块外可导入该模块即可访问,模块外不能被继承和重写。
  • internal:实例可被同一模块内所有实体访问,模块外无法访问。大部分实体默认是Internal级别。
  • fileprivate:限制实例只能在它定义的文件内部(源文件)访问。
  • private: 限制实体只能在它定义的作用域内及同一文件extension中访问。

9.Swift枚举中关联值与原始值的区别

  • 关联值将枚举的成员值跟其他类型的变量关联存储在一起
enum Date {
  case digit(year: Int, month: Int, day: Int)
  case string(String)
}
复制代码
  • 原始值枚举成员可以使用相同类型的默认值预先关联
enum Grade: String {
  case perfect = "A"
  case great = "B"
  case good = "C"
  case bad = "D"
}
复制代码

10.Swift存储属性与计算属性的区别

  • 存储属性即成员变量,存储在实例对象的内存中
  • 计算属性本质就是方法(函数),不占用实例对象的内存 枚举不可以定义存储属性
struct Circle {
    // 存储属性
    var radius: Double
    // 计算属性
    var diameter: Double {
        set {
            radius = newValue / 2
        }
        get {
            return radius * 2
        }
    }
}
复制代码

11.运算符重载

即类、结构体、枚举可以为现有的运算符提供自定义的实现

struct Point {
  var x: Int
  var y: Int
    
  // 重载运算符   
  static func + (p1: Point, p2: Point) -> Point   {       
    return Point(x: p1.x + p2.x, y: p1.y + p2.y)   
  }
}
var p1 = Point(x: 10, y: 10)
var p2 = Point(x: 20, y: 20)
var p3 = p1 + p2
复制代码

12.关键字 guard 和 defer 的用法

  • guard是基于一个表达式的布尔值去判断一段代码是否该被执行,只有在条件不满足的时候才会执行这段代码
guard let name = self.text else { 
  return 
}
复制代码
  • defer的用法是,这条语句并不会马上执行,而是被推入栈中,直到函数结束时才再次被调用
defer {
   //函数结束才调用
}
复制代码

13.Swift 中的下标是什么

subscript可以给任意类型(枚举、结构体、类)增加下标功能,语法类似于实例方法、计算属性,本质就是方法(函数)

class Point {
    var x = 0.0, y = 0.0
    subscript(index: Int) -> Double {
        set {
            if index == 0 {
                x = newValue
            } else if index == 1 {
                y = newValue }
        }
        get {
            if index == 0 {
                return x
            } else if index == 1 {
                return y
            }
            return 0
        }
    }
}

var p = Point()
// 下标赋值
p[0] = 11.1
p[1] = 22.2
// 下标访问
print(p.x) // 11.1
print(p.y) // 22.2
复制代码

14.Swift 和OC中初始化方法 (init)的区别

swift初始化方法需要保证所有的非optional的成员变量都完成初始化, 同时新增了conveniencerequired convenience必须通过一个指定初始化器来完成初始化 required是强制子类重写父类中所修饰的初始化方法

15.什么是属性观察

属性观察是指在当前类型内对特性属性进行监测,并作出响应

  • willSet会传递新值,默认叫newValue
  • didSet会传递旧值,默认叫oldValue
var title: String {
    willSet {
        print("willSet", newValue)

    }
    didSet {
        print("didSet", oldValue, title)
    }
}
复制代码

16.如何将Swift 中的协议(protocol)中的部分方法设计为可选(optional)

  • 协议和方法前面添加 @objc,然后在方法前面添加 optional,实际上是将协议转为了OC的方式
@objc protocol someProtocol {
  @objc  optional func test()
}
复制代码
  • 使用扩展(extension),来规定可选方法,在 swift 中,协议扩展可以定义部分方法的默认实现
protocol someProtocol {
    func test()
}

extension someProtocol{
    func test() {
        print("test")
    }
}
复制代码

17.函数重载的定义,以及 swift 支不支持函数重载

函数重载是指: 函数名称相同,函数的参数个数不同, 或者参数类型不同,或参数标签不同, 返回值类型与函数重载无关 swift 支持函数重载

18.Swift 中闭包的结构,以及尾随闭包、逃逸闭包和自动闭包的定义

  • 闭包结构
{
    (参数列表) -> 返回值类型 in 函数体代码
}
复制代码
  • 尾随闭包,是一个书写在函数圆括号之后的闭包表达式,函数支持将其作为最后一个参数调用
// fn 就是一个尾随闭包参数
func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
    print(fn(v1, v2))
}

// 调用
exec(v1: 10, v2: 20) {
    $0 + $1
}
复制代码
  • 逃逸闭包在函数结束后调用,闭包调用逃离了函数的作用域,需要通过**@escaping**声明
// 定义一个数组用于存储闭包类型
var completionHandlers: [() -> Void] = []

//  在方法中将闭包当做实际参数,存储到外部变量中
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

someFunctionWithEscapingClosure(_:) 函数接受一个闭包作为参数,该闭包被添加到一个函数外定义的数组中如果你不将这个参数标记为 @escaping,就会得到一个编译错误
复制代码
  • 自动闭包不接受任何参数,延迟求值,只有在被调用时才会返回被包装在其中的表达式的值,需要通过**@autoclosure**声明
func goodMooning(morning:Bool ,who:@autoclosure() -> String){
    if morning {
        print("Good morning, (who())")
    }
}
复制代码

18.可选链的定义

可选链是一个调用和查询可选属性、方法和下标的过程,它可能为 nil 如果可选项包含值,属性、方法或者下标的调用成功 如果可选项是 nil ,属性、方法或者下标的调用会返回 nil 。 多个查询可以链接在一起,如果链中任何一个节点是 nil ,那么整个链就会得体地失败

19.OC和Swift中的扩展(Extension)区别

  • OC中有分类和扩展
  • Swift中只有扩展(更类似OC中的分类)
  • Swif Extension中不可以直接添加属性,编译会报错
  • 类、结构体、枚举类型或者协议类型都可以添加扩展

20.交换数组中的两个元素

func swap<T>(nums: inout [T],  p: Int , q : Int)  {
 (nums[p],  nums[q]) = (nums[q],  nums[p]);
}

func swap(a: inout Int,  b: inout Int)  {
    (a, b) = (b , a)
}

func swap<T, U>(x: T,  y: U) -> (T, U)  {
   return (y, x)
}
复制代码

21.实现一个 min 函数,返回两个元素中较小的元素

func min<T : Comparable>(_ a : T , b : T) -> T {
    return a < b ? a : b
}
复制代码

22.map、filter、reduce、flatmap 的作用

  • map映射,将一个元素根据某个函数 映射 成另一个元素(可以是同类型,也可以是不同类型)
  • flatmap可以将元素映射成可选类型
  • filter 过滤,将一个元素传入闭包中,如果返回的是false , 就过滤掉
  • reduce先映射后融合,将数组中的所有元素映射融合在一起

23.利用Swift 的 Currying(柯里化) 特性,输入任一整数,输出输入的整数+2

柯里化(Currying) 将原来接受两个参数的函数变成新的接受一个参数的函数的过程,新函数返回一个以原有第二个参数为参数的函数

func addOne(count: Int) -> Int {
    return count + 2
}

func add(count: Int, addition: Int) -> Int {
    return count + addition
}

func add(_ addition: Int) -> (Int) -> Int {
    return {
        count in 
        return count + addition
    }
}
复制代码

24.为什么数组索引越界会Crash,而字典用下标取值时 key 没有对应值的话返回的是 nil 不会Crash

  • 数组是一段连续内存地址,越界访问也能访问到内存,但这段内存不一定可用,所以会引起Crash
  • 字典的key并没有对应确定的内存地址,所以是安全的

25.associatedtype(关联类型) 的作用

  • 给协议中用到的类型定义一个占位名称
  • 协议中可以拥有多个关联类型

26.声明一个只有一个参数没有返回值闭包的别名

typealias SomeClosuerType = (String) -> (Void)
let someClosuer: SomeClosuerType = { (name: String) in
    print("hello,", name)
}
someClosuer("world")
复制代码

27.深拷贝与浅拷贝的区别

深拷贝:拷贝指针,指向一块新开的内存地址 浅拷贝:仅拷贝指针,指向同一块内存地址

  • 对于非容器类不可变对象,copy是浅拷贝,mutableCopy是深拷贝
  • 对于非容器类可变对象,copy和mutableCopy都是深拷贝,但copy出的对象是不可变
  • 对于容器类可变/不可变对象容器本身均适用上两条,但容器内对象均为浅拷贝,只拷贝了指针,如想实现深度拷贝,需要使用NSCoding协议
    NSArray *arr = [NSArray arrayWithObjects:people, nil];
    //归档
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:arr];
    //解档
    NSArray *arr1 = [NSKeyedUnarchiver unarchiveObjectWithData:data];
复制代码

28.Swift构造器

  • 指定构造器:类最主要的构造器,一个类必须有一个指定构造器,保证所有的非optional的成员变量都完成初始化。如有父类,必须调用父类的一个指定构造器。
  • 便利构造器:将对象用一些默认值进行初始化来让它们适合某种使用场景,便利构造器必须调用类自身的另一构造器,最终一定会调用一个指定构造器
分类:
iOS
标签:
收藏成功!
已添加到「」, 点击更改