整理了些iOS相关的基础问题,每个问题可能会再写些扩展,需要具体了解可以看题目下方的链接
如有错漏,欢迎指出,谢谢
func swap(_ arr: inout [T], a: Int, b: Int) {
(arr[a], arr[b]) = (arr[b], arr[a])
}
// Any 的定义
typealias Any = protocol<>
// AnyObject 定义
@objc protocol AnyObject {
}
OC 对象基于运行时,方法或属性使用动态派发,在运行调用时再决定实际调用的具体实现,而Swift为了追求性能,如无特殊需要,是不会在运行时再来决定这些,即Swift类型的成员/方法在编译时已经决定。
OC中基本所有类继承自NSObject,Swift类如要供OC调用,必须也继承自NSObject
@objc
并不意味着这个方法或属性会采用OC的方式变成动态派发,Swift仍有可能将其优化为静态调用@objc
可修改Swift接口暴露到OC后的名字加了@objc标识的方法、属性无法保证都会被运行时调用,因为Swift会做静态优化。要想完全被动态调用,必须使用dynamic修饰。使用dynamic修饰将会隐式的加上@objc标识。
swift中的函数是静态调用,静态调用的方式会更快,但是静态调用的时候就不能从字符串查找到对于的方法地址,这样 与OC交互的时候,OC动态查找方法就会找不到,这个时候就可以通过使用 dynamic 标记来告诉编译器,这个方法要被动态调用的
OC与Swift区别
当然,Swift和OC可以混编,从OC过渡Swift比较简单,有很多地方是相通的
Swift优势:
1.语法易读,文件结构更简单
2.更安全,强类型语言(默认类型安全)
3.代码更少,省去大量冗余代码
4.速度更快,运算性能更高
Swift 协议(protocol)详解
Protocol小结
定义某种约定,来表示共性,而不是类型,相比OC,不仅用做代理,也可用作对接口的抽象,代码的复用
协议内定义属性/方法/构造器
协议是一种类型
用于委托模式
protocol-extension
协议的继承
&
关键字同时遵循多个协议检查一致性
is
检查某个实例是否符合协议as?
返回协议类型的可选值as!
强制转换为协议类型可选协议
optional
@objc
标记关联类型
依赖倒置原则:
高层模块不依赖于低层模块,二者依赖于抽象
抽象不依赖于细节,细节依赖于抽象
Swift - 基础之extension
Swift 浅谈Struct与Class
理解Swift中struct和class在不同情况下性能的差异
缺点:
选择值类型:
选择引用类型:
模型较小,无需继承、无需OC使用,建议使用Struct
Swift 中的嵌套类型
Swift Copy-On-Write 写时复制
只有当这个值需要改变时才进行复制行为
在结构体内部存储了一个指向实际数据的引用reference,在不进行修改操作的普通传递过程中,都是将内部的reference的引用计数+1,在进行修改时,对内部的reference做一次copy操作,再在这个复制出来的数据进行真正的修改,防止和之前的reference产生意外的数据共享
isKnownUniquelyReferenced
检查类的实例是不是唯一的引用,来决定setter时是否需要复制)1.Runloop
2.事件传递与响应
响应链大概有以下几个步骤:
设备将touch到的UITouch和UIEvent对象打包, 放到当前活动的Application的事件队列中
单例的UIApplication会从事件队列中取出触摸事件并传递给单例UIWindow
UIWindow使用hitTest:withEvent:方法查找touch操作的所在的视图view
RunLoop这边我大概讲一下:
主线程的RunLoop被唤醒
通知Observer,处理Timer和Source 0
Springboard接受touch event之后转给App进程
RunLoop处理Source 1,Source1 就会触发回调,并调用_UIApplicationHandleEventQueue() 进行应用内部的分发。
RunLoop处理完毕进入睡眠,此前会释放旧的autorelease pool并新建一个autorelease pool
非逃逸/逃逸闭包
闭包捕获语义第一弹:一网打尽!
__block
逃逸闭包:
一个接受闭包为参数的函数,逃逸闭包可能会在函数返回之后才被调用,即闭包逃离了函数的作用域(例如:网络请求在请求结束后才调用闭包,并不一定是在函数作用域内执行)
非逃逸闭包:
一个接受闭包为参数的函数,闭包在这个函数结束前内被调用
闭包会强引用它捕获的所有对象,比如在闭包中访问了当前控制器的属性、函数,这样闭包会持有当前对象,容易导致循环引用
非逃逸闭包不会产生循环引用,它会在函数作用域内释放,编译器可保证在函数结束时闭包会释放它捕获的所有对象,非逃逸闭包它的上下文的内存可保存在栈上而不是堆上
自动闭包:简化参数传递,延迟执行时间
@autoclosure
的区别:前者传入参数时,会马上被执行,然后将执行结果作为参数传递给函数,而后者不会立马执行,而是由调用的函数内来决定它具体执行时间弱引用:不会被ARC计算,引用计数不会增加
unowned
:
[unonwed self]
weak
:
weak
,并在使用之前验证这个引用的有效性;使用unowned
引用不会去验证引用对象的有效性,weak
引用添加了附加层,间接得把unowned
引用包裹到了一个可选容器里面,在指向的对象析构之后变成空的情况下,处理更清晰,而这附加的机制需要正确处理可选值
OC 和 Swift 的弱引用源码分析
Swift 4 弱引用实现
Swift对象有两个引用计数:强引用计数和弱引用计数,当强引用计数为0而弱引用计数不为0时,对象会销毁,但内存不会被立即释放,内存中会保留弱引用指向的僵尸对象,在加载弱引用时,运行时会对引用对象进行检查,如果是僵尸对象,则会弱引用计数进行递减操作,一旦弱引用计数为0,对象内存将会被释放。
如果对象的弱引用数一直不为零,那么对象占用的剩余内存就不会完全释放。这些死而不僵的对象还占用很多空间的话,累积起来也是对内存造成浪费
SideTable机制:与OC不同的是,系统不再把它作为全局对象使用
map: 每个元素根据闭包中的方法进行转换,然后按转换后的元素输出
Optional map/flatMap
Sequence.map/flatMap/compactMap
filter: 过滤,筛选出满足闭包条件的元素
reduce: 组合计算
try 出现异常处理异常
try? 不处理异常,返回一个可选值类型,出现异常返回nil
try! 不让异常继续传播,一旦出现异常程序停止,类似NSAssert()
类不想被继承,函数、属性不想被重写(只能修饰类)
class 和 static 都可表示类方法,前者子类可重写,后者不能重写,static自带final class
性质
public enum Optional {
case none
case some(Wrapped)
}
泛型枚举
1.声明时添加?,告诉编译器是可选值(表示一个变量可能有值,也可能没有值为nil),自动初始化为nil
2.对变量操作前加?,判断如果变量为nil,则不响应后面的方法
1.声明时添加!,告诉编译器是可选值,并且之后对变量操作时,都隐式在操作前添加!
2.对变量操作前加!,表示默认为非nil,直接解包处理
设置默认值,判断变量是否为nil,如果不为nil,则对该变量解包,否则用??后面的默认值
lazy: 延迟初始化,当变量在用到的时候才加载(全局变量不用lazy也是懒加载)
inout:
其参数或者返回值是闭包的函数,如sort、map、filter
对结构体、枚举,mutating用于表示某个实例方法可以改变自身实例或者实例中的属性的函数
对协议,用于那些会改变遵循该协议的类型的实例的函数
不是
ArraySlice是Sequence的子类,ArraySlice就不是
作用:提供一种延时调用的方式,defer内的代码块会在当前作用域结束之后执行,代码块会被压入栈中,待函数结束时弹出栈运行。
其目的就是进行资源清理和避免重复的返回前需要执行的代码
注意:前提是必须执行到defer才会触发,多个defer,按栈的后进先出顺序执行
try catch结构:相当于finally
清理、回收资源,例如:加解锁
lock.lock()
defer {
lock.unlock()
}
调super方法:override一些方法时,需要在super方法前写,比如autolayout的约束写动画,重写updateContaints方法,可以用defer将super方法调用写在前面
completion闭包调用:有些函数分支较多,遗漏调用completion
例如:
protocol Copyable {
func copy() -> Self
}
class A: Copyable {
var num = 1
required init() { } // 保证当前类和其子类都能响应这个init方法
func copy() -> Self {
// type(of: self)获取当前对象的类型
let copy = type(of: self).init()
copy.num = num
return copy
}
}
理解 Swift 中的元类型:.Type 与 .self
let intMetatype: Int.Type = Int.self
.Type是类型,.self是元类型的值typealias AnyClass = AnyObject.Type
任意类型的元类型的别名获得元类型后可以访问静态变量和静态方法,例子:
func register(AnyClass?, forCellReuseIdentifier: String)
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
Swift选项集合(OptionSet)
OptionSet 选项集合
Swift使用struct
来遵循OptionSet
协议,引入选项集合
一个类型为整型的原始值(rawValue)+ 一个初始化构造器(struct有默认的,不需要写)
struct Sports: OptionSet {
let rawValue: Int
static let running = Sports(rawValue: 1 << 0)
static let cycling = Sports(rawValue: 1 << 1)
static let swimming = Sports(rawValue: 1 << 2)
}
let sports = [.running, .swimming]
Swift 中的 Sequence(一)
extension Set where Iterator.Element == String {
func test() { }
}
["", ""].test()
使用继承可能的问题:
swift定义了一些协议,可通过使用赋值运算符,来用文字值初始化类型,采用相应的协议并提供公共初始化允许特定类型的文本初始化
ExpressibleByArrayLiteral
数组形式初始化ExpressibleByDictionaryLiteral
字典形式初始化ExpressibleByNilLiteral
由nil值初始化ExpressibleByIntegerLiteral
整数值初始化ExpressibleByFloatLiteral
浮点数初始化ExpressibleByBooleanLiteral
布尔值初始化ExpressibleByUnicodeScalarLiteral
ExpressibleByExtendedGraphemeClusterLiteral
ExpressibleByStringLiteral
以上三种由字符串初始化,上面两种包含Unicode自负和特殊字符例子:
struct TestFloat {
var value: Float
}
//一般情况下,初始化
var test = TestFloat(value: 4.5)
// 遵循ExpressibleByFloatLiteral协议
extension TestFloat: ExpressibleByFloatLiteral {
typealias FloatLiteralType = Float
init(floatLiteral value: TestFloat.FloatLiteralType) {
self.init(value: value)
}
}
//
var testt: TestFloat = 4.5
iOS多线程全套
iOS 多线程:『pthread、NSThread』详尽总结
iOS GCD
iOS Swift GCD 开发教程
iOS 多线程:『NSOperation、NSOperationQueue』详尽总结
并发&并行:
前者指多个任务交替占用CPU,后者指多个CPU同时执行多个任务
同步&异步:
跨平台、C语言编写,需要自己管理线程的生命周期,难度大
比pthread简单,可直接操作线程对象,但也需要自己管理线程的生命周期
performSelector
指执行任务的等待队列,即用来存放任务的队列(FIFO)
区别 | 串行队列 | 并发队列 | 主队列 |
---|---|---|---|
同步 | 当前线程执行,不开启新线程,串行执行任务 | 当前线程执行,不开启新线程,串行执行任务 | 主线程调用:死锁卡住不执行;其他线程调用:不开启新线程,串行执行任务 |
异步 | 开启新线程,串行执行任务 | 可开启多个线程,并发执行任务(无序执行,多条线程) | 不开启新线程,串行执行任务(任务在同一线程) |
异步执行一组任务 -> barrier任务 -> 异步执行另一组任务
// OC
dispatch_barrier_async
// Swift
let item = DispatchWorkItem(qos: .default, flags: .barrier) {
}
// OC
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
});
dispatch_group_enter、dispatch_group_leave
// Swift
DispatchQueue.global().async(group: group, execute: <#T##DispatchWorkItem#>)
group.enter()
group.leave()
group.notify(queue: queue) {
}
group enter/leave
并不是在指定时间之后才开始执行处理,而是在指定时间之后才将任务添加到队列中
// OC
dispatch_after
// Swift
asyncAfter
按照指定的次数将指定任务追加到指定队列中, 添加的任务并发异步执行,这些任务全部执行完毕后再继续往下执行
// OC
dispatch_apply
// Swift
DispatchQueue.concurrentPerform(iterations: 100) { (index) in
}
持有计数的信号,计数为0时等待,不可通过,计数>=1时,计数减1且不等待,可通过
OC:
dispatch_semaphore_create
: 创建一个Semaphore并初始化信号的总量dispatch_semaphore_signal
: 发送一个信号,让信号总量+1dispatch_semaphore_wait
: 当信号总量为0时,就会一直等待(阻塞所在线程),否则就可以正常执行, 并使总信号量-1Swift:
DispatchSemaphore(value: 1)
初始化信号量的总量wait()
使信号量减1,如果信号量大于0则返回.success
,否则返回timeout
signal()
使信号量+1,返回当前信号量应用:
1.保持线程同步,将异步执行任务转换成同步执行任务
func semaphoreSync() {
print("current thread: \(Thread.current)")
print("semaphore begin")
let se = DispatchSemaphore(value: 0)
var num = 0
DispatchQueue.global().async {
Thread.sleep(forTimeInterval: 2)
print("----> \(Thread.current)")
num = 100
se.signal()
}
se.wait()
print("---> num: \(num)")
}
2.保证线程安全,为线程加锁
例子:多个窗口卖票,票源总数固定,
wait相当于加锁,signal相当于解锁
优点:
Operation(操作):
OperationQueue(操作队列):
maxConcurrentOperationCount
最大并发操作数控制并发、串行步骤:
之后,系统自动将操作队列中的操作取出,在新线程中执行操作
在没使用OperationQueue,在主线程中单独使用子类NSInvocationOperation、NSBlockOperation或自定义继承Operation子类执行一个操作的情况下,操作是在当前线程执行,没有开启新线程
BlockOperation与InvocationOperation类似,但还多提供了一个方法addExecutionBlock
来添加额外的操作,额外操作是在不同线程中异步执行的
一般情况下,如果BlockOperation封装多个操作,是否开启新线程,取决于操作个数,开启的线程数由系统决定
OperationQueue 有两种队列:主队列、自定义队列(包含串行、并发功能)
主队列:凡是添加到主队列的操作,都会放到主线程中执行(不包括addExecutionBlock
添加的额外操作,可能在其他线程执行)
自定义队列:在自定队列中会自动放到子线程中执行,包含串行、并发功能
maxConcurrentOperationCount
:控制的不是并发线程的数量,是一个队列中同时能并发执行的最大操作数,开启线程数量由系统决定
1, 队列为并发队列,操作并发执行,其值为min(自己设定的值,系统设定默认最大值)
【Swift】iOS 线程锁
iOS-线程安全
线程安全:当一段代码被多个线程执行,执行后的结果和多个线程依次执行后的结果一致,那么这段代码就是线程安全
互斥锁: 当新线程访问,发现有线程正在执行锁定代码,新线程进入休眠,避免占用CPU资源,锁的持有者的任务完成,会检测是否存在等待执行的线程,如有,唤醒执行任务
自旋锁:新thread会用死循环的方式一直等待锁定的代码执行完成,消耗性能
public protocol NSLocking {
public func lock()
public func unlock()
}
遵循NSLocking协议,包括NSLock, NSCondition, NSConditionLock, NSRecursiveLock
最常用的锁,lock & unlock, 注意需要在同一线程上调用
确保线程仅在condition符合情况时上锁,并执行相应代码,然后分配新的状态
手动控制线程wait和signal
可以多次给相同线程上锁并不会造成死锁
持有计数的信号,计数为0时等待,不可通过,计数>=1时,计数减1且不等待,可通过
OC:
dispatch_semaphore_create
: 创建一个Semaphore并初始化信号的总量dispatch_semaphore_signal
: 发送一个信号,让信号总量+1dispatch_semaphore_wait
: 当信号总量为0时,就会一直等待(阻塞所在线程),否则就可以正常执行, 并使总信号量-1Swift:
DispatchSemaphore(value: 1)
初始化信号量的总量wait()
使信号量减1,如果信号量大于0则返回.success
,否则返回timeout
signal()
使信号量+1,返回当前信号量会对访问的变量加互斥锁
与OC的synchroned关键字类似,对某一个对象加互斥锁
os_unfair_lock
: iOS10新方法方法:
使用步骤:
底层实现:
runtime机制动态创建被监听类的派生类,重写setter方法,在调用原setter方法之前和之后通知观察者值的改变,并将原被监听类的isa指针指向这个派生类
详细:iOS 消息发送与转发详解
objc_msgSend(id self, SEL cmd, …)
当上述没有找到方法实现,程序在异常抛出前,runtime会有3次拯救的机会
resolveInstanceMethod (实例方法) / resolveClassMethod(类方法)
, 在该方法内利用class_addMethod
绑定,返回YES
forwardingTargetForSelector
替换消息的接受者为其他对象,即将A类的某个方法,转发到B类的实现中去,如果return nil/self则进入第三完整转发,forwardInvocation / methodSignatureForSelector
iOS 多线程:『RunLoop』详尽总结
深入理解RunLoop
解密-神秘的 RunLoop
我认为的 Runloop 最佳实践
RunLoop在循环中用来处理程序运行过程中出现的各种事件,从而保持程序的持续运行
在没有事件处理时,会使线程进入睡眠模式,从而节省CPU资源,提高性能
UIApplicationMain
自动开启了主线程的RunLoop,内部无限循环RunLoop是线程中的一个循环,RunLoop会在循环中不断检测,通过Input sources(输入源)和Timer sources(定时源)两种来源等待接收事件,然后对接收到的事件通知的线程进行处理,并在没有事件的时候让线程休息
Core Foundation框架(括号为Swift写法):
基于时间的触发器
基本上说得就是NSTimer(CADisplayLink也是加到RunLoop),它受RunLoop的Mode影响
CCD的定时器不受RunLoop的Mode影响
按官方文档分类:
按函数调用栈分类:
Source0: event事件,只含回调,需要先调用CFRunLoopSourceSignal(source),将这个Source标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop。
Source1: 包含了一个mach_port和一个回调,被用于通过内核和其他线程互相发送消息,能主动唤醒RunLoop线程
可监听的状态变化有:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即将进入Loop:1
kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理Timer:2
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理Source:4
kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠:32
kCFRunLoopAfterWaiting = (1UL << 6), // 即将从休眠中唤醒:64
kCFRunLoopExit = (1UL << 7), // 即将从Loop中退出:128
kCFRunLoopAllActivities = 0x0FFFFFFFU // 监听全部状态改变
};
注:进入RunLoop前,会判断模式是否为空,为空直接退出
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
上述代码会自动将定时器加入到RunLoop的默认模式下,相当于一下两句代码:
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
比如,图片轮播器,拖拽时模式从默认到Tracking,此时定时器不响应,停止轮播
场景:用户再拖拽时不显示图片,拖拽完成时显示图片
当调用 NSObject 的 performSelecter:afterDelay:
后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。
当调用 performSelector:onThread:
时,实际上其会创建一个 Timer 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。
开启一个常驻线程,让一个子线程不进入消亡状态,等待其他线程发来消息,处理其他事件
1.创建常驻线程 thread
2.对常驻线程运行一下代码
// 添加下边两句代码,就可以开启RunLoop,之后self.thread就变成了常驻线程,可随时添加任务,并交于RunLoop处理
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
3.需要常驻线程执行任务时,将任务放到该线程
在休眠前(kCFRunLoopBeforeWaiting)进行释放,处理事件前创建释放池,中间创建的对象会放入释放池
特别注意:
在启动RunLoop之前建议用 @autoreleasepool {…}包裹
意义:创建一个大释放池,释放{}期间创建的临时对象
自动释放池的前世今生 ---- 深入解析 autoreleasepool
黑幕背后的Autorelease
提供了一种可以向一个对象延迟发送 release 消息的机制,由若干个AutoreleasePoolPage以双向链表的形式组合而成
class AutoreleasePoolPage {
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
};
AutoreleasePoolPage
的大小都是4096
字节,这4096字节中,有56bit用于存储page的成员变量,剩下的用来存储加入到自动释放池中的对象哨兵对象是nil的别名
#define POOL_SENTINEL nil
每个自动释放池初始化调用时,都会把一个哨兵对象push到自动释放池的栈顶,并返回这个哨兵对象的地址
objc_autoreleasePoolPush的返回值正是这个哨兵对象的地址,被objc_autoreleasePoolPop(哨兵对象)作为入参,于是:
自动释放池就是若干个AutoreleasePoolPage以双向链表的形式实现
objc_autoreleasePoolPush
,往AutoreleasePoolPage中的next位置插入一个哨兵对象(POOL_SENTINEL),并返回它的内存地址
添加autorelease对象
objc_autoreleasePoolPop
,将之前返回的哨兵对象传入pop函数,根据这个对象地址找到哨兵对象所在的page,然后对晚于哨兵对象插入的所有autorelease对象都发送依次release消息,并向回移动next指针(可以跨越若干page,直到哨兵所在的page)
在没有手加Autorelease Pool的情况下,Autorelease对象是在当前的runloop休眠时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop
UIResponder
史上最详细的iOS之事件的传递和响应机制-原理篇
继承了UIResponder的对象,能接受处理、传递事件
由一系列响应者对象构成的链条,能清楚地呈现每个响应者之间的联系,可让一个事件多个对象处理
即事件传递机制
keyWindow
,再在视图层次结构中找一个合适的视图来处理事件userInteractionEnable=NO
, isHidden=NO
, alpha<=0.01
hitTest:withEvent:
方法来查找第一响应者hitTest:withEvent:
方法,以此类推UIApplication
找到第一响应者后,应用程序会先调用第一响应者的处理事件,如果不能处理则调用nextResponder
将事件传递给下一个响应者,其顺序:Subview -> Superview -> UIViewController -> UIWindow -> UIApplication
注:
UIViewController没有hitTest:withEvent:方法,所以控制器不参与查找响应视图的过程。但是控制器在响应者链中,如果控制器的View不处理事件,会交给控制器来处理。控制器不处理的话,再交给View的下一级响应者处理。
hitTest
方式查找响应视图UIApplication
向其派发消息,如果响应者链中存在能处理事件的手势,则手势响应事件,并执行touchesCancelled
将响应者链打断UIControl
也通过hitTest
查找第一响应者UIControl
,则Application
直接派发事件,并不再向响应者链派发消息UIControl
不能处理事件,再交给手势处理或响应者链传递通过响应者链,循环查找nextResponder是否为UIViewController
- (UIViewController *)parentController { UIResponder *responder = [self nextResponder]; while (responder) { if ([responder isKindOfClass:[UIViewController class]]) { return (UIViewController *)responder; } responder = [responder nextResponder]; } return nil; }
pointInside:withEvent
用来判断一个点是否在视图中,而这个方法是通过bounds来判断的,如果要扩大响应范围,可重写该方法,将判断bounds的范围扩大不规则点击区域判断也可重写该方法
多个对象实现touches
并调用super
方法
property = ivar + getter + setter 成员变量+存取方法
前者深拷贝,赋值时,会对新变量的重新生成一份新的内存空间,后者浅拷贝,只是复制对象的指针
可以,但是当OC对象的引用计数为0时,对象销毁,编译器不会置为nil,产生野指针
iOS 底层解析weak的实现原理(包含weak对象的初始化,引用,释放的分析)
Runtime对注册的类会进行内存布局,有个SideTable结构体是负责管理类的引用计数表和weak表,weak修饰的对象地址作为key存放到全局的weak引用表中,value是所有指向这个weak指针的地址集合,调用release会导致引用计数器减一,当引用计数器为0时调用dealloc,在执行dealloc时将会在这个weak的hash表中搜索,找到这个key的记录,将记录中所有附有weak修饰符的变量地址,设置为nil,并从weak表中删除记录。
主要用于管理对象的引用计数和weak表
struct SideTable {
spinlock_t slock; // 保证原子操作的自旋锁
RefcountMap refcnts; // 引用计数的 hash 表
weak_table_t weak_table; // weak 引用全局 hash 表
}
weak 表,全局弱引用表,使用不定类型对象的地址作为key,用weak_entry_t
类型结构体对象作为value,weak_entry_t
负责维护和存储指向一个对象的所有弱引用hash表
// weak 表
struct weak_table_t {
weak entry_t *weak_entries; // 保存了所有指向指定对象的weak指针
size_t num_entries; // 存储空间
uintptr_t mask; // 参与判断引用计数辅助量
uintptr_t max_hash_displacement; // 最大偏移量
}
1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
2、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
Block
函数指针+捕获上下文变量
配置在栈上的Block,如果其所属的栈作用域结束,该Block就会被废弃,对于超出Block作用域仍需使用Block的情况,Block提供了将Block从栈上复制到堆上的方法来解决这种问题
ARC有效时,以下情况栈上的Block会自动复制到堆上:
其他情况向方法的参数中传递block时,需手动调用copy
默认情况
对block外的变量引用,默认将其复制到数据结构中,存储在block的结构体内部,此时,block只能访问不能修改变量
__block修饰外部变量
block复制其引用地址来实现访问,可修改__blcok修饰的外部变量的值
原理:将栈上用
__block
修饰的自动变量封装成一个结构体,让其在堆上创建,以方便从栈上或堆上访问或修改同一份数据
因为对象obj在Block被copy到堆上的时候自动retain了一次。因为Block不知道obj什么时候被释放,为了不在Block使用obj前被释放,Block retain了obj一次,在Block被释放的时候,obj被release一次。
retain cycle问题的根源在于Block和obj可能会互相强引用,互相retain对方,这样就导致了retain cycle,最后这个Block和obj就变成了孤岛,谁也释放不了谁。
(那张表isa/superclass 表)
nil:空实例对象(给对象赋空值)
Nil:空类对象(Class class = Nil)
NULL:指向C类型的空指针
NSNull:类,用于空对象的占位符(用于替代集合中的空对象,还有判断对象是否为空对象)
在UIViewController对象的view属性被访问到且为空的时候调用
用来自定义view,只要实现了这个方法,其他通过xib或storyboard创建的view都不会被加载,不能调用super方法
UIView的setNeedsLayout,layoutIfNeeded等方法介绍
1.如果要立即刷新,先调用setNeedsLayout,标记为需要布局,再调用layoutIfNeeded,实现布局
2.视图第一次显示之前默认标记需要刷新
3. layoutIfNeeded不一定会调用layoutSubviews,但setNeedsLayout一定会调用layoutSubviews
以上两个方法没有递归,对subviews不负责,只负责自己
以上1,2推荐,3,4不提倡
Inactive/Active的切换:
一般:前后台应用切换,Inactive会在Active和Background之间短暂出现
其他:Active和Inactive在前台运行时切换,比如来电拒接、拉下通知栏、系统弹出Alert
将所有依赖库放到一个名为Pods的项目中,然后让主项目依赖Pods项目,使源码工作从主项目移到了Pods项目中,Pods项目最终会编译一个libPods.a 的文件,主项目只需要依赖这个.a文件即可
使用CocoaPods前引用第三方:
1.复制依赖库的源码
2.添加依赖框架/动态库
3.设置参数
4.管理更新
深入理解Objective C的ARC机制
auto reference count 自动引用计数,编译器会自动插入对应的代码,再结合Objective C的runtime,实现自动引用计数
内部实现:
- (id)retain {
return ((id)self)->rootRetain();
}
inline id objc_object::rootRetain()
{
if (isTaggedPointer()) return (id)this;
return sidetable_retain();
}
其本质是调用sidetable_retain
id objc_object::sidetable_retain()
{
//获取table
SideTable& table = SideTables()[this];
//加锁
table.lock();
//获取引用计数
size_t& refcntStorage = table.refcnts[this];
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
//增加引用计数
refcntStorage += SIDE_TABLE_RC_ONE;
}
//解锁
table.unlock();
return (id)this;
}
SideTable结构
struct SideTable {
spinlock_t slock; // 自旋锁
RefcountMap refcnts; // 引用计数表,以地址作为key,引用计数的值作为value
weak_table_t weak_table; //weak 表
//省略其他实现...
};
release的实现
SideTable& table = SideTables()[this];
bool do_dealloc = false;
table.lock();
//找到对应地址的
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end()) { //找不到的话,执行dellloc
do_dealloc = true;
table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
} else if (it->second < SIDE_TABLE_DEALLOCATING) {//引用计数小于阈值,dealloc
do_dealloc = true;
it->second |= SIDE_TABLE_DEALLOCATING;
} else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
//引用计数减去1
it->second -= SIDE_TABLE_RC_ONE;
}
table.unlock();
if (do_dealloc && performDealloc) {
//执行dealloc
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return do_dealloc;
//autorelease方法
- (id)autorelease {
return ((id)self)->rootAutorelease();
}
//rootAutorelease 方法
inline id objc_object::rootAutorelease()
{
if (isTaggedPointer()) return (id)this;
//检查是否可以优化
if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;
//放到auto release pool中。
return rootAutorelease2();
}
// rootAutorelease2
id objc_object::rootAutorelease2()
{
assert(!isTaggedPointer());
return AutoreleasePoolPage::autorelease((id)this);
}
autorelease方法会把对象存储到AutoreleasePoolPage的链表里。等到auto release pool被释放的时候,把链表内存储的对象删除。所以,AutoreleasePoolPage就是自动释放池的内部实现。
CFMessagePort
mach port
get请求是幂等性的,即同一个URL的多个请求返回同样的结果,而post不是(因为get是幂等的,在网络不好的隧道中会尝试请求,如果用get来请求增删改数据,会有重复操作的风险)