Objective C/C/C++
使用的编译器前端是Clang,Swift是swift,后端都是LLVM.前端
二、3为编译器前端处理ios
静态库:连接时,静态库会被完整地复制到可执行文件中,被屡次使用就有多份冗余拷贝c++
系统动态库:连接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存git
一个NSObject对象占用多少内存?web
系统分配了16个字节给NSObject对象(经过malloc_size函数得到) 但NSObject对象内部只使用了8个字节的空间(64bit环境下,能够经过class_getInstanceSize函数得到)
复制代码
对象的isa指针指向哪里?面试
instance对象的isa指向class对象 class对象的isa指向meta-class对象 meta-class对象的isa指向基类的meta-class对象
复制代码
OC的类信息存放在哪里?objective-c
对象方法、属性、成员变量、协议信息,存放在class对象中 类方法,存放在meta-class对象中 成员变量的具体值,存放在instance对象
复制代码
class_rw_t 与 class_ro_t 区别算法
class_rw_t结构体内有一个指向class_ro_t结构体的指针.
class_ro_t存放的是编译期间就肯定的;而class_rw_t是在runtime时才肯定,它会先将class_ro_t的内容拷贝过去,而后再将当前类的分类的这些属性、方法等拷贝到其中。因此能够说class_rw_t是class_ro_t的超集
固然实际访问类的方法、属性等也都是访问的class_rw_t中的内容
属性(property)存放在class_rw_t中,实例变量(ivar)存放在class_ro_t中。
复制代码
msg_send 消息转发swift
分为三个阶段
一、动态方法解析
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(run)) {
return NO;//返回 NO, 才会执行第二步
}
return [super resolveInstanceMethod:sel];
}
二、快速转发
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(run)) {
// return [Dog new]; //替换其余消息接受者
return nil; //返回nil 则会走到第3阶段,彻底消息转发机制(慢速转发)
}
return [super forwardingTargetForSelector:aSelector];
}
三、彻底消息转发
3.1方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(run)) {
Dog *dog = [Dog new];
return [dog methodSignatureForSelector:aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
3.2 消息转发
- (void)forwardInvocation:(NSInvocation *)anInvocation {
Dog *dog = [Dog new];
if ([dog respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:dog];
} else {
[super forwardInvocation:anInvocation];
}
}
复制代码
iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?)后端
利用RuntimeAPI动态生成一个子类,而且让instance对象的isa指向这个全新的子类 当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数 willChangeValueForKey: 父类原来的setter didChangeValueForKey: 内部会触发监听器(Oberser)的监听方法( observeValueForKeyPath:ofObject:change:context:)
复制代码
IMP、SEL、Method的区别
SEL是方法编号,也是方法名
IMP是函数实现指针,找IMP就是找函数实现的过程
Method就是具体的实现
SEL和IMP的关系就能够解释为:
SEL就至关于书本的⽬录标题
IMP就是书本的⻚码
Method就是具体页码对应的内容
SEL是在dyld加载镜像到内存时,经过_read_image方法加载到内存的表中了
复制代码
AssociationsManager类 管理了着一个锁还有一个全局的哈希键值对表.
// 初始化一个AssociationsManager实例对象的时候,会获取一个分配了内存的锁,而且调用它的assocations()方法来懒加载获取哈希表
AssociationsHashMap
ObjectAssociationMap
ObjectAssociation
复制代码
@synchronized
使用@synchronized虽然解决了多线程的问题,可是并不完美。由于只有在single未建立时,咱们加锁才是有必要的。若是single已经建立.这时候锁不只没有好处,并且还会影响到程序执行的性能(多个线程执行@synchronized中的代码时,只有一个线程执行,其余线程须要等待)。
@synchronized是一把支持多线程递归的互斥锁
objc_sync_enter跟objc_sync_exit
复制代码
dispatch_once
当onceToken= 0时,线程执行dispatch_once的block中代码 当onceToken= -1时,线程跳过dispatch_once的block中代码不执行 当onceToken为其余值时,线程被阻塞,等待onceToken值改变
复制代码
发送通知所在的线程就是接收通知所在的线程
两张表 Named Table NotificationName做为表的key, nameless table 没有传入NotificationName wildcard
一、传入了NotificationName,则会以NotificationName为key去查找对应的Value,若找到value,则取出对应的value;若未找到对应的value,则新建一个table,而后将这个table以NotificationName为key添加到Named Table中。
二、若是没有传入NotificationName,直接根据对应的 object 为key去找对应的链表而已。
三、若是既没有传入NotificationName也没有传入object,则这个观察者会添加到wildcard链表中。
复制代码
使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 不管给我传入是一个可变对象仍是不可对象,我自己持有的就是一个不可变的副本.
若是咱们使用 strong ,那么这个属性就有可能指向一个可变对象,若是这个可变对象在外部被修改了,那么会影响该属性.
NSMutableString *string = [[NSMutableString alloc] initWithString:@"0"];
NSString *copyString = [string mutableCopy];
NSString *strongString = string;
[string appendString:@"1"];
NSLog(@"copyString = %@", copyString);
NSLog(@"strongString = %@", strongString);
复制代码
不可变对象 copy 指针拷贝 浅拷贝 仅此一种状况
copy | mutableCopy | |
---|---|---|
不可变对象 | 浅拷贝 (指针拷贝) | 深拷贝 |
可变对象 | 深拷贝 | 深拷贝 |
weak表实际上是一个hash表,key 是所指对象的地址,value 是 weak 指针的地址数组,
sideTable是一个结构体,内部主要有引用计数表和弱引用表两个成员,内存存储的其实都是对象的地址、引用计数和weak变量的地址,而不是对象自己的数据
alloc
方法封装init
只是一种工厂设计方案,为了方便子类重写:自定义实现,提供一些初始化就伴随的东西new
封装了 alloc 和init
一、_objc_rootDealloc
if (fastpath(isa.nonpointer && //无指针指向
!isa.weakly_referenced && //无弱引用
!isa.has_assoc && //无关联对象
!isa.has_cxx_dtor && //无cxx析构函数
!isa.has_sidetable_rc)) //无散列表引用计数
{
assert(!sidetable_present());
free(this); //直接释放
}
else {
object_dispose((id)this);//则作其余操做
}
复制代码
二、dispose
objc_destructInstance
是否有c++析构函数
是否存在关联对象
obj->clearDeallocating();
free
复制代码
一、转成 cgImage
二、建立上下文对象 contextRef (位图信息bitmapInfo)
三、将cgimage 画在 上下文上
四、获得新的 cgImage
五、uiimage *newImage = CGBitMapContextCreateImage( cgImage);
block的原理是什么 本质是什么
本质是一个 oc 对象, 内部也有一个 isa 指针
内部封装了 block 执行逻辑的函数
复制代码
结构体对象
int age = 20;
void (^block) (void) = ^ {
NSLog(@"age is %d", age);
};
复制代码
变量自动捕获👇
struct __main_block_impl_0 {
struct __block_impl impl; //impl 结构体见👇
struct __main_block_desc_0* Desc;
int age;// 自动变量捕获
}
复制代码
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr; //指向 block 内部实现的函数地址 (见👇)
}
复制代码
// 封装了 block 执行逻辑的函数
static void __main_block_func_0 () {
//TODO
}
复制代码
auto 值传递
static 指针传递
全局变量 不捕获 直接访问
局部变量须要捕获是由于须要跨函数
访问
继承自NSBlock类型
globleBlock 没有访问 auto 变量 (访问 static 和 全局变量仍让是 globelBlock)
stackBlock 访问了 auto 变量 (MRC 下能打印出来, ARC下会自动调动 copy ---> mallocBlock)
stackBlock ----> mallocBlock 调用了 copy (栈 --- > 堆上)
复制代码
__block 可解决 block 内部没法修改 auto 变量的问题
复制代码
编译器会将__block变量包装成一个对象 __Block_byref_xxx_0 结构体
复制代码
基本数据类型 int age = 0;
编译器会将 age 包装成 __Block_byref_age_0
结构体
1.__main_block_impl_0 结构体内持有 __Block_byref_age_0
2.__Block_byref_age_0 内部持有 __forwarding 指针指向本身
复制代码
当 block 被 copy 到堆上时,会调用block内部的 copy 函数,copy 函数会调用 __Block_object_assign
一个运行着的程序就是一个进程或者一个任务。每一个进程至少有一个线程,线程就是程序的执行流。建立好一个进程的同时,一个线程便同时开始运行,也就是主线程。每一个进程有本身独立的虚拟内存空间,线程之间共用进程的内存空间。有些线程执行的任务是一条直线,起点到终点;在 iOS 中,圆型的线程就是经过run loop不停的循环实现的。
1.每一个线程包括主线程都有与之对应的 runloop 对象,线程和 runloop 对象是一一对应的;
2.Runloop 保存在一个全局字典中,线程为key, runloop为value CFDictionaryGetValue
3.主线程会默认开启 runloop , 子线程默认不会开启,须要手动开启
4.runloop 在第一次获取时建立,在线程结束时销毁
复制代码
首先由Source1捕捉系统事件,而后包装成eventqueue,传递给Source0处理触摸事件
复制代码
Entry :
beforeTimers :
beforeSources
beforeWaiting
afterWaiting
exit
复制代码
通知 observers:RunLoop 要开始进入 loop 了,(kCFRunLoopEntry)
开启一个 do while 来保活线程,通知 observers :
通知 Observers:RunLoop 的线程将进入休眠(sleep)状态 (kCFRunLoopBeforeWaiting)
进入休眠后,会等待 mach_port的消息,以再次唤醒;只有在下面四个事件
唤醒时,通知 Observer: Runloop 的线程刚刚被唤醒了 (kCFRunLoopAfterWaiting)
被唤醒后,开始处理消息
若是是 Timer 时间到的话,就触发 Timer 的回调;
若是是 dispatch 的话,就执行 block;
若是是 source1 事件的话,就处理这个事件。
消息执行完后,就执行到loop里的block
根据当前runloop的状态来判断是否须要走下一个 loop。当被外部强制中止或loop 超时,就不继续下一个loop了,不然继续走下一个loop
mode做用是用来隔离, 将不一样组的Source0、Source一、timer、Observer 隔离开来,互不影响
主要有
defaultMode : app的默认 mode,一般主线程在这个mode下运行
UITrackingMode : 界面追踪 mode, 用于scrollview追踪触摸滑动,保证界面滑动时不受其余 Mode 影响
复制代码
控制线程生命周期(线程保活)
检测应用卡顿
性能优化
用户态和内核态之间的相互切换
mach_msg()
用户态 ----> 内核态 (等待消息)
内核态 ---->用户态 (处理消息)
内核态:
等待消息
没有消息就让线程休眠
有消息就唤醒线程
复制代码
viewDidLoad
和viewWillAppear
在同一个RunLoop循环中
UIApplicationMain 启动了 runloop
@autoreleasePool = __AtAutoreleasePool __autoreleasePool
__AtAutoreleasePool 结构体
复制代码
AutoreleasePool 是 oc 的一种内存回收机制,正常状况下变量在超出做用域的时候 release,可是若是将变量加入到 pool 中,那么release 将延迟执行
AutoreleasePool 并无单独的结构,而是由若干个 AutoreleasePoolPage 以**双向链表**形式组成
1. PAGE_MAX_SIZE :4KB,虚拟内存每一个扇区的大小,内存对齐
2. 内部 thread ,page 当前所在的线程,AutoreleasePool是按线程一一对应的
3. 自己的成员变量占用56字节,剩下的内存存储了调用 autorelease 的变量的对象的地址,同时将一个哨兵插入page中
4. pool_boundry 哨兵标记,哨兵其实就是一个空地址,用来区分每个page 的边界
5. 当一个Page被占满后,会新建一个page,并插入哨兵标记
复制代码
单个自动释放池的执行过程就是objc_autoreleasePoolPush()
—> [object autorelease]
—> objc_autoreleasePoolPop(void *)
具体实现以下:
void *objc_autoreleasePoolPush(void) {
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt) {
AutoreleasePoolPage::pop(ctxt);
}
复制代码
内部其实是对 AutoreleasePoolPage 的调用
每当自动释放池调用 objc_autoreleasePoolPush 时,都会把边界对象放进栈顶,而后返回边界对象,用于释放。
AutoreleasePoolPage::push();
调用👇
static inline void *push() {
return autoreleaseFast(POOL_BOUNDARY);
}
复制代码
autoreleaseFast
👇
static inline id *autoreleaseFast(id obj) {
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
复制代码
👆上述方法分三种状况选择不一样的代码执行:
- 有 hotPage 而且当前 page 不满,调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中
- 有 hotPage 而且当前 page 已满,调用 autoreleaseFullPage 初始化一个新的页,调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中
- 无 hotPage,调用 autoreleaseNoPage 建立一个 hotPage,调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中
最后的都会调用 page->add(obj) 将对象添加到自动释放池中。 hotPage 能够理解为当前正在使用的 AutoreleasePoolPage。
复制代码
是以栈的形式存在,而且内部对象经过进栈、出栈对应着 objc_autoreleasePoolPush 和 objc_autoreleasePoolPop
当咱们对一个对象发送一条 autorelease 消息时,其实是将这个对象地址加入到 autoreleasePoolPage 的栈顶 next 指针的指向的位置
复制代码
iOS 在主线程注册了两个 observer
__第一个observer __
监听了 kCFRunloopEntry, 会调用 objc_autoreleasePool_push()
第二个 observer
监听了 kCFRunloopBeforeWaiting 会调用 objc_autoreleasePool_pop() 、objc_autoreleasePool_push()
监听了 kCFRunloopExit 事件,会调用 objc_autoreleasePool_pop()
同步、异步 Dispatch_async 和 dispatch_sync 决定了是否开启新的线程
并发、串行 concurrent 、serial 队列的类型决定了任务的执行方式
使用 sync 向当前串行队列中添加任务,会卡住当前的串行队列(产生死锁)
High-level lock
自旋锁再也不安全 等待锁的线程会处于忙等状态,一直占用着CPU的资源
可能会出现优先级反转的问题
从底层调用来看,等待 os_unfair_lock 锁的线程处于休眠状态,并不是忙等
须要销毁
pthread_cond_t
pthread_cond_wait
pthread_cond_signal
复制代码
对 pthread_mutex 默认封装
对 NSConditionLock 和 NSCondition的封装
wait
signal
gcd 串行队列
互斥递归锁
@synthronized(obj) obj 传递进去 syncData(hashmap)一个 obj 对应一把锁 (pmutext_lock)
obj对应的递归锁,而后进行加锁、解锁操做
进入SyncData的定义,是一个结构体,主要用来表示一个线程data,相似于链表结构,有next指向,且封装了recursive_mutex_t属性,能够确认@synchronized确实是一个递归互斥锁
复制代码
自旋锁 (不休眠)
预计线程等待锁的时间很短
CPU资源不紧张
互斥锁
预计等待锁的时间较长
有IO操做
CPU资源紧张
复制代码
atomic 读写加锁 可是 release 不加锁
pthread_rwlock : 读写锁
等待的锁 会进入休眠
复制代码
dispatch_barrier_async:异步栅栏函数
queue 必须是本身手动建立的并发队列
复制代码
NSProxy 没有 init 方法
若是调用 nsproxy 发送消息,他会直接调用
0x00400000
**开始访问堆区内存时,通常是先经过对象读取到对象所在的栈区的指针地址,而后经过指针地址访问堆区
复制代码
内存从高到底分配 内存是连续的
复制代码
具体内存布局👇
小对象是不会进行retain和release操做的 所以不用担忧过分释放问题
专门用来处理小对象
,例如NSNumber、NSDate、小NSString等
NSTaggedPointerString类型
,存在常量区
1.NSTaggedPointerString
:标签指针,是苹果在64位
环境下对NSString、NSNumber
等对象作的优化
。对于NSString对象来讲
字符串是由数字、英文字母组合且长度小于等于9
时,会自动成为NSTaggedPointerString
类型,存储在常量区
中文或者其余特殊符号
时,会直接成为__NSCFString
类型,存储在堆区
2.__NSCFString
:是在运行时
建立的NSString子类
,建立后引用计数会加1
,存储在堆上
3.__NSCFConstantString
:字符串常量
,是一种编译时常量
,retainCount值很大
,对其操做,不会引发引用计数变化,存储在字符串常量区
从64bit开始,iOS引入了Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象的存储
在没有使用Tagged Pointer以前, NSNumber等对象须要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值
使用Tagged Pointer以后,NSNumber指针里面存储的数据变成了:Tag + Data,也就是将数据直接存储在了指针中
当指针不够存储数据时,才会使用动态分配内存的方式来存储数据
objc_msgSend能识别Tagged Pointer,好比NSNumber的intValue方法,直接从指针提取数据,节省了之前的调用开销
如何判断一个指针是否为Tagged Pointer?
iOS平台,最高有效位是1(第64bit) 16进制转为2进制
isTaggerPointer
pointer & 1
Mac平台,最低有效位是1
复制代码
DEMO
崩溃缘由是多条线程
对同一个对象
进行释放
,致使对象过分释放
,因此才会崩溃。
优化64位地址
两个表
,分别是引用计数表
、弱引用表
Charles 是如何抓包的
微信卡顿三方matrix
建立一个 CFRunLoopObserverContext 观察者,将建立好的观察者 observer 添加到主线程runloop 的 common 模式下观察。而后建立一个持续的子线程专门用来监控主线程的runloop 状态。
CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,kCFRunLoopAllActivities,YES,0,&runLoopObserverCallBack,&context);
复制代码
一旦发现进入休眠前的 beforeSources 状态,或者唤醒后的状态 afterWaiting ,在设置的时间(阈值)内一直没有变化,便可断定为卡顿
定义:在当前屏幕缓冲区外开辟一个缓冲区进行渲染操做
离屏渲染消耗性能的缘由
一、须要建立新的缓冲区
二、离屏渲染的过程当中,须要屡次切换上下文环境,先是从当前屏幕切换到离屏;等到离屏渲染完毕后,将离屏缓冲区的渲染结果显示到屏幕上,又须要将上下文环境从离屏切换到当前屏幕
复制代码
离屏渲染产生的缘由
一、光栅化 layer.shouldRasterize = YES;
二、遮罩 layer.mask
三、圆角 可以使用 coreGraphic 绘制圆角解决
四、阴影 layer.shadowxxx 若是设置了 path 就不会触发离屏渲染
复制代码
一、CPU
**计算要显示的内容,包括如下几个方面:**👇
当 runloop 在 beforewaiting 和 exit 时通知注册的监听,而后对图层进行打包。而后将打包数据发给专门负责渲染的独立进程 render server
复制代码
二、Render server
减小在短期内大量图片的显示
CPU
进行预处理,而后再提交给GPU
处理,致使额外CPU
资源消耗)AFN
SD
在渲染阶段,控件树(widget)会转换成对应的渲染对象(RenderObject)树,在 Rendering 层进行布局和绘制
复制代码
2.Relayout Boundary
为了防止因子节点发生变化而致使的整个控件树重绘,Flutter 加入了一个机制 relayout boundry
flutter使用边界标记须要从新绘制和从新布局的节点,这样就能够避免其余节点被污染或者触发重建。
就是控件大小不会影响其余控件时,就不必从新布局整个控件树。有了这个机制后,不管子树发生什么样的变化,处理范围都只在子树上。
复制代码
3.布局的计算
在布局时 Flutter 深度优先遍历渲染对象树。数据流的传递方式是从上到下传递约束,从下到上传递大小。也就是说,父节点会将本身的约束传递给子节点,子节点根据接收到的约束来计算本身的大小,而后将本身的尺寸返回给父节点。
显式动画是指用户本身经过beginAnimations:context:和commitAnimations建立的动画。CABasicAnimation
隐式动画是指经过UIView的animateWithDuration:animations:方法建立的动画。
隐式动画是ios4以后引入sdk的,以前只有显式动画。从官方的介绍来看,二者并无什么差异,甚至苹果还推荐使用隐式动画,可是这里面有一个问题,就是使用隐式动画后,View会暂时不能接收用户的触摸、滑动等手势。这就形成了当一个列表滚动时,若是对其中的view使用了隐式动画,就会感受滚动没法主动中止下来,必须等动画结束了才能中止。
关键帧动画 CAKeyframeAnimation
20210314 字节一面
isEqual && hash
参考 www.jianshu.com/p/915356e28…
==运算符 与 isEqual
==运算符只是简单地判断是不是同一个对象, 而isEqual方法能够判断对象是否相同
复制代码
hash方法只在对象被添加至NSSet和设置为NSDictionary的key时会调用
成员变量和 setter 方法
hook 实例 例子: KVO
id instancetype
内存布局
tcp 和 UDP
mvc 和 mvvm
git rebase git merge 区别
算法:全排列