Runloop结构包含_currentMode,_commonModes,_commonModeItems,_currentMode,_pthread model结构包含source0、source1、timer、observer
source0是非基于Port的。只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
Source1除了包含回调指针外还包含一个mach port,Source1可以监听系统端口和通过内核和其他线程通信,接收、分发系统事件,它能够主动唤醒RunLoop(由操作系统内核进行管理,例如CFMessagePort消息)。
runloop是在获取时创建的以一个全局字典存储,key为线程,value为runloop, 当字段为创建,先创建字典,将主线程的runloop创建并添加到字典中,以线程为key,未获取到runloop时,创建runloop并存放到字典中。获取到就直接返回该runloop。 线程销毁时runloop随着被销毁
添加model我们没有办法直接创建一个CFRunLoopMode对象,但是我们可以调用CFRunLoopAddCommonMode传入一个字符串向RunLoop中添加Mode,传入的字符串即为Mode的名字,Mode对象应该是此时在RunLoop内部创建的。下面来看一下源码。
- modeName不能重复,modeName是mode的唯一标识符
- RunLoop的_commonModes数组存放所有被标记为common的mode的名称
- 添加commonMode会把commonModeItems数组中的所有source同步到新添加的mode中
- CFRunLoopMode对象在CFRunLoopAddItemsToCommonMode函数中调用CFRunLoopFindMode时被创建
添加source
- 如果modeName传入kCFRunLoopCommonModes,则该source会被保存到RunLoop的_commonModeItems中
- 如果modeName传入kCFRunLoopCommonModes,则该source会被添加到所有commonMode中
- 如果modeName传入的不是kCFRunLoopCommonModes,则会先查找该Mode,如果没有,会创建一个,加入相应的source0或者source1
- 同一个source在一个mode中只能被添加一次
添加Timer和observer
添加observer和timer的内部逻辑和添加source大体类似。
区别在于observer和timer只能被添加到一个RunLoop的一个或者多个mode中,比如一个timer被添加到主线程的RunLoop中,则不能再把该timer添加到子线程的RunLoop,而source没有这个限制,不管是哪个RunLoop,只要mode中没有,就可以添加。
RunLoop运行
通过CFRunLoopRunSpecific的内部逻辑,我们可以得出:
- 先指定唯一的model 如果指定了一个不存在的mode或者mode中不包含任何modeItem,那么RunLoop也不会运行
- 在进入run loop之前通知observer,状态为kCFRunLoopEntry
- beforTimer->beforSource->如果有需要处理的block就处理,有source1就处理source1,没有任务处理就进入beforWaiting, 等待被唤醒,会有dowhile循环等待系统端口主动唤醒,通过mach_msg 是runloop休眠或者被唤醒
- afterWaiting被唤醒,执行block任务,如果被Timer唤醒就行Timer事件、被通过GCD添加到主线程的block,就执行相应的block、被source1唤醒就执行source1事件
- 时间执行完毕根据情况决定继续循环还是退出循环kCFRunLoopRunTimedOut、kCFRunLoopRunStopped、kCFRunLoopRunFinished退出循环
- 在退出run loop之后通知observer,状态为kCFRunLoopExit
push
如果没有hotPage,就新建一个page,边界对象放到page;有hotPage并且没有满,就将边界对象放到page中; 如果有hotPage但是满了,就新建一个子page,边界对象存放到page,并且上一个page的child指向新page,新page的parent指向上一个page
pop
以当前hotPage开始release page中的对象,释放完毕,查找parent Page, 并销毁当前Page,直到遇到了边界对象
autoRelease
会携带 要autoRelease的对象,如果没有hotPage,就新建一个page,边界对象放到page;有hotPage并且没有满,就将边界对象放到page中; 如果有hotPage但是满了,就新建一个子page,边界对象存放到page,并且上一个page的child指向新page,新page的parent指向上一个page
page->add(obj)其实就是将autorelease对象添加到Page中的next指针所指向的位置,并将next指针指向这个对象的下一个位置,然后将该对象的位置返回。
总结
- Page的双向链表对线程是私有的,同一个线程的多个自动释放池共用一个链表,\
- runloop的enter时机 push新的, beforeWaiting pop旧的,push新的, exit pop 自动释放池, \
- 子线程不会自动开启runloop,即使autoRelease操作触发noPage的场景,新建Page, 但是没有pop操作,只能等待「子线程结束时」系统自动清理(销毁线程关联的所有
AutoreleasePoolPage,并对其中的对象发送release)所以有autoRelease对象时,要手动创建@autoReleasePool{},不然对象会无法释放,出现内存泄露\ - 有大量局部变量创建时,可以使用@autoReleasePool,减少内存峰值
Person *object = [Person alloc];
id __weak objc = object;
总结:
1.weak 关键字的作用是弱引用,所引用对象的计数器不会加1,并在引用对象被释放的时候自动被设置为 nil。
2.原理是底层维护了一张weak_table_t结构的hash表,key是所指对象的地址(object),value是weak指针的地址(objc)数组, 当weak指针指向一个对象时,会先检查该weak指针是否之前已经指向了一个旧对象,如果有,则旧对象对应的weak指针数组将移除该weak指针; 如果没有指向旧对象,以新对象为key 从weak_table表中取出weak指针数组weak_entry,如果没有该数组创建新的,并添加在weak_table中,如果有就直接将weak指针放入该weak指针数组中; 当对象销毁时,会检查该对象是否有弱引用表,有的话就找出该对象对应的weak指针数组,将每个weak指针置为nil
- KVC&VKO
通过Key名直接访问对象的属性,或者给对象的属性赋值。而不需要调用明确的存取方法。这样就可以在运行时动态地访问和修改对象的属性。而不是在编译时确定
重复remove会崩溃:不能remove未add的observer
add同一个key两次会接收到两次通知
不实现observeValueForKeyPath:ofObject:change:context :当发送通知的时候会崩溃
监听者和被监听者的生命周期不同
监听者提前为nil,当被监听的key改变是,会崩溃
2022-11-23 18:36:31.946022+0800 OCProject[77291:4283648] *** -[Student retain]: message sent to deallocated instance 0x6000030dc530\
KVO崩溃防护
KVO 日常使用造成崩溃的原因通常有以下几个:
KVO 添加次数和移除次数不匹配:
- 移除了未注册的观察者,导致崩溃。
- 重复移除多次,移除次数多于添加次数,导致崩溃。
- 重复添加多次,虽然不会崩溃,但是发生改变时,也同时会被观察多次。
被观察者提前被释放,被观察者在 dealloc 时仍然注册着 KVO,导致崩溃。 例如:被观察者是局部变量的情况(iOS 10 及之前会崩溃)。
添加了观察者,但未实现 `observeValueForKeyPath:ofObject:change:context:` 方法,导致崩溃。
添加或者移除时 `keypath == nil`,导致崩溃。
- 首先为 NSObject 建立一个分类,利用 Method Swizzling,实现自定义的
BMP_addObserver:forKeyPath:options:context:、BMP_removeObserver:forKeyPath:、BMP_removeObserver:forKeyPath:context:、BMPKVO_dealloc方法,用来替换系统原生的添加移除观察者方法的实现。
- 然后在观察者和被观察者之间建立一个
KVODelegate 对象,两者之间通过KVODelegate 对象建立联系。然后在添加和移除操作时,将 KVO 的相关信息observer添加到KVODelegate 对象中对应 的关系哈希表中,对应原有的添加观察者。 关系哈希表的数据结构:{keypath : [observer对象1, observer 对象2, ... ]} - 在添加和移除操作的时候,利用
KVODelegate 对象做转发,把真正的观察者变为KVODelegate 对象,而当被观察者的特定属性发生了改变,再由KVODelegate 对象分发到原有的观察者上。
那么,是如何避免 KVO 崩溃的呢?
- 添加观察者时:通过关系哈希表判断是否重复添加,只添加一次。
- 移除观察者时:通过关系哈希表是否已经进行过移除操作,避免多次移除。
- 观察键值改变时:同样通过关系哈希表判断,将改变操作分发到原有的观察者上。
- key 不是对象的属性,造成崩溃。
- keyPath 不正确,造成崩溃。
- key 为 nil,造成崩溃。
- value 为 nil,为非对象设值,造成崩溃。 切换valueForKey:和setValue:ForKey:方法 重写setValue:forUndefinedKey:、 valueForUndefinedKey:、 setNilValueForKey
setValue:forKey
valueForKey:
KVO
动态生成NSKVONotifying_Person 子类,set方法;willChangeValueForKey方法、原来的setter方法实现、didChangeValueForKey方法,该子类强持有观察者,找到该观察者,发送通知
任务:同步(堵塞当前线程)、异步(具备开启新线程的能力)
队列:串行(整个执行任务)、并发(可以并发执行任务)
任务和队列 组合使用、队列嵌套使用、注意死锁、线程安全和线程同步
dispatch_barrier_async、 dispatch_after、 dispatch_once、 dispatch_apply、dispatch_group、dispatch_semaphore、dispatch_source
NSInvocationOperation、NSBlockOperation、自定义继承自 NSOperation 的子类
操作addDependency、removeDependency、queuePriority、cancel、isFinished、isCancelled、isExecuting、isReady
waitUntilFinished、setCompletionBlock
主队列、自定义队列
cancelAllOperations、isSuspended、setSuspended:(BOOL)b
waitUntilAllOperationsAreFinished
addOperationWithBlock、addOperations、currentQueue、mainQueue
串行并发队列(maxConcurrentOperationCount)、线程同步、
init方法创建要手动start、detachNewThreadSelector创建线程会自动启用、performSelectorInBackground:withObject 隐式创建并开启
performSelectorOnMainThread
performSelector:onThread: withObject: waitUntilDone
performSelector:withObject:(当前线程)
互斥锁、自旋锁
信号量
NSlock(是最基础的互斥锁,遵循 “一次加锁必须对应一次解锁” 的规则。如果同一线程在未解锁的情况下再次调用 lock 方法,会导致死锁。)
@synchronized、 pthread_mutex、
NSRecursiveLock(用于解决 “同一线程需要多次加锁” 的场景(如递归函数、嵌套调用)。它会记录 “加锁次数”,只有当 “解锁次数等于加锁次数” 时,锁才会真正被释放,其他线程才能获取。)、
递归锁更消耗性能、能不用递归锁就不用,必须用的时候确保加解锁次数严格匹配,且解锁线程与加锁线程一致。
NSCondition、 NSCoditionLock、 pthread_rwlock \
成员变量、实例变量、属性、set方法
关键字:@synthesize @dynamic
@interface ViewController : UIViewController
{
UIButton *aButton;
double money;
id data;
}
@property(nonatomic,strong)UIButton *bButton;
@end
1.实例变量本质上就是成员变量,只是实例是针对类而言,实例是值类的声明 2.除去基本数据类型int,float,double等,其他类型的变量都叫实例变量。 3.成员变量用于类和子类内部,@protect修饰 ,不能用" . "调用, self->_priName = @"私有变量"; 4.在 @interface 括号里面的统称为”成员变量”,实例变量是成员变量中的一种
属性: 上面这段代码中bButton就是属性 属性创建过程中自动产生了set 和get方法
@synthesize iOS 6 之后 LLVM 编译器引入property autosynthesis,即属性自动合成。换句话说,就是编译器会为每个 @property 添加 @synthesize ,如以下形式:@synthesize propertyName = _propertyName; 1、当重写属性setter和getter方法或者readonly修饰的属性getter方法时,系统不会自动生成_propertyName变量了,需要手动生成成员变量 @synthesize propertyName = _propertyName; 或者手动注册变量 2、protocol中声明了property,需要在实现协议的类中 实现其set和get方法:1)@synthesize propertyName = _propertyName;2)手动注册变量,并手动实现set和get方法 2、@synthesize 声明的属性=变量。意思是,1、声明和实现属性的setter,getter方法,并作用于这个变量。2、指定与属性对应的实例变量,例如我可以这样写@synthesize age = myAge;,那这样子的话我们去调用的时候self.age其实是操作的实例变量myAge,而不是_age了。
@implementation Sark
@synthesize address = _address;
@synthesize name = _name;
//@dynamic name;
- (void)sear {
self.name = @"afa";
_name = @"mamdafd";
}
- (NSString *)address {
_address = @"asdf";
return _address;
}
- (void)setName:(NSString *)name {
_name = name;
}
- (NSString *)name {
_name = @"adsfasdfs";
return _name;
}
@end
@dynamic @dynamic :不自动生成getter/setter方法,由自己实现setter和getter方法 或 在运行时动态创建绑定,谨慎使用 ,当未实现setter和getter方法时可以编译成功,但是运行会奔溃
##2、@private,@protected,@public,@package 1、@private @private变量只能在类内部调用,在类外无法访问 继承该类的子类也无法访问
2、@protected 在类外面不能调用potected变量 在类里或者继承该类的子类里可以使用该变量
3、@public 该类实例内,还是在类外面调用都可以 继承的子类的实例内或者在外面调用改实例都可以获取该变量
** @package** 只能在本框架内使用 对于framework内部,相当于@public 对于framework外部,相当于@private 这个特性,很适合用于开发第三方的静态类库,因为多数人并不希望让别人知道自己属性的值。
set
strong(默认):强引用,延长对象生命周期。
setter 会保留新值、释放旧值:
- (void)setName:(NSString *)name {
if (_name != name) {
[name retain]; // 保留新值(ARC 下自动完成,MRC 需手动写)
[_name release]; // 释放旧值(MRC 需手动写)
_name = name;
}
}
weak:弱引用,不延长对象生命周期,对象释放后自动置空。
setter 会注册弱引用关系
- (void)setDelegate:(id<Delegate>)delegate {
// 底层调用 objc_storeWeak,将 _delegate 与新值关联
objc_storeWeak(&_delegate, delegate);
}
copy:拷贝新值,确保实例变量持有独立副本(避免外部修改影响内部)。
setter 会对参数进行拷贝(深拷贝或浅拷贝,取决于类型):
- (void)setName:(NSString *)name {
if (_name != name) {
_name = [name copy]; // 拷贝新值(NSString 是不可变的,实际是浅拷贝优化)
}
}
// 对可变类型(如 NSMutableArray),copy 会生成不可变副本,
atomic(默认):setter 和 getter 会加锁,保证赋值和取值的原子性(线程安全的最小保证)。
- (void)setName:(NSString *)name {
@synchronized(self) { // 加锁,同一时间只有一个线程执行
if (_name != name) {
_name = [name copy];
}
}
}
触摸事件首先将会由第一响应者响应,触发其 open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) 等方法,根据触摸的方式不同(如拖动,双指),具体的方法和过程也不一样。若第一响应者在这个方法中不处理这个事件,则会传递给响应链中的下一个响应者触发该方法处理,若下一个也不处理,则以此类推传递下去。若到最后还没有人响应,则会被丢弃(比如一个误触)。 我们可以创建一个 UIView 的子类,并加入一些打印函数,来观察响应链具体的工作流程。
取消superView touchBegin方法
1.添加手势; 2.重写touchBegin方法,不调用super touchBegin 方法
响应链及手势识别 手势的优先级比touch高,当view 的手势响应事件后,会向view发送touchCancle,取消view的touch响应 手势底层依赖于touch,对touch的高级封装
block 是个对象类型,具有函数和对象的特性,可以被传递参数和赋值,有NSGlobalBlock、NSStackBlock、NSMallocBlock三种类型、__weak修饰self避免循环引用、__block修饰外部局部变量,会生成一个结构体,该结构体内部都一个指针指向结构体本身,可以指针引用的方式修改变量
启动优化
时间耗时:进程创建到viewDidAppear
首页优化: viewDidLoad到接口返回
juejin.cn/post/694899…
数据结构相关