1. struct 和 class 的区别
类型区别
struct: 值类型
- 赋值或传递时是值拷贝(深拷贝),独立内存空间
- 修改副本不会影响原实例
class:应用类型
- 赋值或者传递时是引用指针的拷贝,共享同一内存空间
- 修改任一引用会影响所有指向该实例的变量
内存管理
struct:编译器自动管理,栈上,比较高效
class:内存通过ARC管理,堆上分配。可能引发循环引用
继承与多态
struct:不支持继承,但可以通过protocol实现多态
class:支持单继承,可通过override
重写方法/属性
可变性
struct:默认不可变,如需修改方法内的属性,需标记为mutaing
//在值类型的实例方法中修改实例属性值,需要在方法前加 mutaing
struct Point2 {
var x=0.0, y=0.0
mutating func moveBy(dx:Double, dy:Double) {
// x += dx
// y += dy
//或者直接为 self 赋值
self = Point2(x: x+dx, y: y+dy)
}
}
class:始终可变,无需额外关键字
初始化
struct:自动生成成员初始化器
对于struct,如果没有自定义初始化方法,会有一个默认的,为所有属性赋值的初始化方法:
//对于 struct,如果属性没有默认值,初始化的时候,会有一个默认初始化方法,可以为所有属性赋值
struct Size {
var width:Double
var height:Double
}
var size = Size(width: 100.0, height: 200.0)
如果有自定义了初始化方法,那么不能再调用默认的初始化方法了
struct Size {
var width:Double
var height:Double
init() { // 自定义了初始化方法
width = 200.0
height = 300.0
}
}
// 如果为 value type 提供了自定义初始化方法,那么就不能调用默认的初始化方法了
// 不能使用 var size = Size(width: 100.0, height: 200.0)
var size = Size()
class:需手动定义初始化器,若继承父类需处理super.init()
使用场景
优先选择struct:
- 数据简单,无需继承
- 线程安全
优先选择class:
- 需共享或者修改同一实例
- 需继承或类型检查
map, filter, reduce 作用
- map:对集合中每个元素进行转换,返回一个新的集合,可用于元素转换,替换for循环
let numbers = [1, 2, 3, 4]
let newArr = numbers.map { n in
return n * 2
}
print(newArr) // [2,4,6,8]
let newArr2 = numbers.map { $0 * 2 }
print(newArr2)
- filter: 筛选符合条件的元素,返回一个新集合。可用于过滤无效数据,搜索匹配,替代if+for循环
let strs = ["fasdf", "werw", "fvvf", "32e124faf", "mmkjbnj"]
let res1 = strs.filter { str in
return str.count > 6
}
print(res1) // ["32e124faf", "mmkjbnj"]
let res2 = strs.filter { $0.count > 6 }
print(res2)
- reduce:将集合的所有元素合并成一个值,如求和,字符串拼接,替代for循环+累加变量
let result = array.reduce(initialValue) { partialResult, element -> T in
// 累积计算
return updatedPartialResult
}
let nums = [1,2,3,4]
// 0 是初始值
let sum = nums.reduce(0) { partialResult, n in
return partialResult + n
}
print(sum)
let sum1 = nums.reduce(0, +)
print(sum1)
let strs = ["hello", "world", "shanghai"]
let sum2 = strs.reduce("") { $0 + $1 }
print(sum2)
string和NSString的区别和联系
string可通过定义时的let或var决定,NSString不可变
string值类型,NSString引用类型
string编码方式UTF-8/UTF-16,自动处理Unicode
string具有赋值时拷贝(Copy-on-Write)的特性,NSString传递指针
互相转换
let str = "hello"
let nstr = str as NSString
let nstr2 = NSString(string: "world")
let nstr3: NSString = "wwww"
let swiftStr = nstr3 as String
let swiftstr2 = String(nstr3)
swift中associatedtype 的作用
When defining a protocol, it’s sometimes useful to declare one or more associated types as part of the protocol’s definition. An associated type gives a placeholder name to a type that’s used as part of the protocol. The actual type to use for that associated type isn’t specified until the protocol is adopted. Associated types are specified with the associatedtype
keyword
associatedtype
主要作用是让protocol支持泛型
如下,定义一个协议,有一个属性和两个方法
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
func getItem(at index: Int) -> Item
}
Item
是一个类型占位符,具体类型由实现协议的结构体或类来定义
下面的结构体实现了该协议,并且指定Item的类型为Int
struct IntContainer: Container {
typealias Item = Int // 可以省略,swift会根据类型推断Item的具体类型
private var items:[Int] = []
mutating func append(_ item: Int) {
items.append(item)
}
func getItem(at index: Int) -> Int {
return items[index]
}
}
又一个实现该协议的struct,这次指定Item为String
struct StringContainer: Container {
private var items:[String] = []
mutating func append(_ item: String) {
items.append(item)
}
func getItem(at index: Int) -> String {
return items[index]
}
}
分别初始化两个结构体,可以看到可以有不同的类型
var intContainer = IntContainer()
intContainer.append(12)
intContainer.append(13)
print(intContainer.getItem(at: 0))
var stringContainer = StringContainer()
stringContainer.append("hello")
stringContainer.append("world")
print(stringContainer.getItem(at: 0) + " " + stringContainer.getItem(at: 1))
open和public的区别
特性 |
public |
open |
模块外可见性 |
✅ 其他模块可以访问 |
✅ 其他模块可以访问 |
模块外可继承 |
❌ 其他模块不能继承该类 |
✅ 其他模块可以继承该类(open class ) |
模块外可重写 |
❌ 其他模块不能重写方法/属性 |
✅ 其他模块可以重写方法/属性(open func ) |
适用场景 |
暴露接口但不允许外部修改 |
允许外部继承或重写(如框架设计) |
Optional是用什么实现的?
enum,有两个case: .some(value) .none
定义静态方法时,staitc 和 class 有什么区别?
都用于定义类型级别的方法或者属性,但是关键区别在于是否允许子类重写
特性 |
static |
class |
适用范围 |
类、结构体、枚举、协议 |
仅类(class ) |
是否允许重写 |
❌ 不可被子类重写 |
✅ 允许子类重写(需加 override ) |
语义 |
强调静态不可变性 |
强调类的可继承性 |
协议中必须用static
何时用static?
- 定义工具方法或者常量
- 需要跨类型使用
- 禁止子类修改
何时用class?
- 设计可扩展的类层次结构
- 需要子类提供特定实现
如果想禁止重写class方法,可配合final使用:
class Parent {
final class func cannotOverride() {} // 子类不能重写
}
weak和assign区别
weak:修饰对象类型,不会增加引用计数,对象释放后自动设置为nil,代理用weak,可防止循环引用。
assign:修饰基本数据类型,修饰对象时可能导致野指针。
weak修饰的属性自动置为nil 的原理
weak是Runtime维护了一个hash(哈希)表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。
weak_table:
全局管理弱引用的哈希表
weak var obj = someObj
key: 被弱引用的对象的地址,这里是指 &someObj
作用:通过地址快速定位到该对象的所有弱引用记录
value:weak_entry_t
结构体
类型:weak_entry_t,存储一个对象的所有弱引用信息
struct weak_entry_t {
DisguisedPtr<objc_object> referent; // Key 的副本(被弱引用的对象)
union {
struct {
weak_referrer_t *referrers; // 动态数组:存储所有 weak 变量的地址
uintptr_t num_refs; // 数组长度
};
struct {
weak_referrer_t inline_referrers[4]; // 内联数组(优化少量 weak 引用)
};
};
};
referrers存储的是变量obj的地址,如:[&obj],如果有多个weak修饰,那么如:[&obj1, &obj2]
内存关系图:

weak引用插入weak_table_t的过程:
当执行 weak var obj = someObject
时:
- 计算
someObject
地址的哈希值:
hash = hash_pointer(someObject)
。
- 通过
hash & mask
计算数组索引 index
。
- 检查
weak_entries[index]
:
- 若为空,初始化一个新的
weak_entry_t
,存入 referent = someObject
和 referrers = [&obj]
。
- 若已存在且
referent == someObject
,将 &obj
追加到 referrers
数组。
- 若发生哈希冲突(
referent != someObject
),线性探测下一个位置(开放寻址法)
什么时候用copy修饰符
修饰NSString
,NSArray
,NSDictionary
等不可变类型的时候
原因:使用copy修饰的时候,赋值的时候,实际保存的是值的一个不可变副本,不会被外界无意修改。
NSString
,NSArray
,NSDictionary
有对应的可变类型,如果用strong
修饰,得到的实际是可变对象,外界修改时会同步改变属性的值
不手动指定Autoreleasepool的前提下,一个AutoreleasePool对象在什么时候释放?
所有autorelease
对象都由主线程的RunLoop
创建的@autoreleasepool
来管理。
每一个Autoreleasepool
都是由一系列的AutoreleasePage
组成的,数据结构为双向链表,AutoreleasePage是节点
AutoreleasePage
类似一个栈结构

初始化的时候,调用objc_autoreleasePoolPush
,把一个POOL_SENTINEL
push到自动释放池的栈顶,并且返回这个POOL_SENTINEL
对象。
POP时,向自动释放池中的对象发送release
消息,直到第一个POOL_SENTINEL
。
Autoreleasepool和RunLoop的关系
主线程的RunLoop中注册了两个observer
- 一个监听
kCFRunLoopEntry
事件,调用push
- 第二个observer,监听
kCFRunLoopBeforeWaiting
事件,调用pop,push。监听kCFRunLoopBeforeExit
事件,调用pop。
总结就是runloop会自动创建和销毁Autoreleasepool。
RunLoop
juejin.cn/post/684490…
运行循环,在程序运行中做一些事情,如接收和处理消息,休眠等待
主要是靠内部的事件循环来实现
事件循环
- 没有消息时休眠,避免占用资源
- 有消息时唤醒线程
- 用户态到内核态到转换
RunLoop mode
多种mode起到屏蔽效果,运行在mode1模式的时候,无法处理mode2中的事件。
RunLoop和线程的关系
- 一一对应
- 如果没有Runloop,线程执行完任务就会退出,Runloop会在第一次获取它时创建
子线程创建RunLoop过程
- 获取当前线程的RunLoop
- 添加source/port
- run
为什么block可以修改使用__block
修饰的局部变量?
使用__block
修饰的局部变量,会在底层被编译成一个结构体,结构体中引用的有该变量的地址,在block中会通过地址访问该结构体,然后通过结构体更改该变量的值。
如何解决NSTimer和self的循环引用?
- 中间代理
- iOS 10+ 提供了
scheduledTimerWithTimeInterval:repeats:block:
,结合 weakSelf
__weak typeof(self) weakSelf = self;
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf timerFired]; // 必须检查 weakSelf 是否存活
}];
- gcd timer
dispatch_barrier_async
作用
主要起到线程同步的作用,只能作用在自定义的并发队列
举例:
在barrier之前有3个并发任务A,B,C。barrier中加入了任务D,后续还有任务E,F。
那么任务执行如下:
- A,B,C并发执行
- D不会立即执行,要等到A,B,C都执行完毕后,D才开始执行。
- D执行完毕后,E,F开始执行。
Objective-C的消息处理流程
www.jianshu.com/p/220841172…
iOS 事件响应链
juejin.cn/post/689451…