属性关键字的理解以及在MRC和ARC下的写法的不同,对其要有深刻的理解和认识。
assign和weak的区别?
weak在对象被释放的时候回自动销毁赋值为nil,而assign(会造成野指针),只是对应的引用计数为0,并没有把对象指向nil,所以会造成野指针的问题。
assign主要用来修饰基本的数据类型,分配在栈上,有系统去自动管理分配和释放工作。assign也是可以用来修饰对象的,但是有一个严重的问题:就是在对象释放的时候,assign修饰的对象并不会安全释放,会造成野指针的问题。
category的原理
在load之前也就是在runtime初始化的时候回进行合并。
方法定义在分类里面,怎么去执行
方法合并,程序运行的时候动态的将类的方法合并??
在底层生成对应的struct _category_t结构体变量不一样
分类添加的方法列表对应的方法有指定的方法指针地址,原来的数据结构不变,原来的类的方法列表放到最后面,会优先加载对应的分类的方法(这就是优先调用分类的方法)
MethodExchange的原理?什么时候去交换方法,交换的原理是什么?
实际是交换对应的方法的地址,但是外部的函数名还是没有变化,这就是为什么在自定义的函数里面再次去调取同样的函数名不会造成循环。改变了对应的函数地址。
如果是写在+load方法里面会在编译的时候就交换了对应的方法指针,+initialize是在类第一次用到的时候去交换。
谁管理autoreleasePool?
系统。
多线程
子线程创建和runloop的关系。
iOS性能的优化
-
对象的创建
- 当使用到的对象涉及到CALayer的时候,会到主线程去操作,如果不涉及到UI相应事件的话,最好用CALayer
-
对象的调整
- 涉及到frame ,bounds, center等的属性调整的时候都会消耗CPU去计算
-
对象的销毁
对象的销毁最好放到后台去销毁
iOS设备定位服务
首先用户需要同意允许定位到对应的服务信息;
会通过蜂窝网络,Wi-Fi,GPS,蓝牙来判断大致的位置信息
NSRunloop的mode
创建的线程是自带的不允许自己创建,创建并保活你的runloop
NSDefaultRunLoopMode UITrackingRunLoopMode UITInitializationRunLoopMode GSEventReceiveRunLoopMode
NSRunLoopCommonModes(NSDefaultRunLoopMode|UITrackingRunLoopMode)
GCD源码
https://opensource.apple.com/tarballs/libdispatch/
Foundation框架可以参考GNUStep,学习下思路。
下载地址http://www.gnustep.org/resources/downloads.php
runtime本身是开源的
https://opensource.apple.com/source/objc4/
开发总结和学习
Interview
clang
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc [对应的文件路径]
clang -emit-llvm -S [对应的文件名字]
bt 函数调用栈调试
p 打印地址信息
p/x 打印地址
x 查看对应的地址信息里面保存的值
C++代码只是参考
step oc的单步调试
stepi(si) 汇编的单步调试
next oc的单步调试
nexti(ni) 汇编的单步调试
gnusetp反写了oc代码,可以跟踪oc代码的实现(cocoa框架的代码)
NSObject 需要多大的内存
默认是16个字节,不够也会返回16个,字节对齐。
class_getInstanceSize 得到的是对应的这个类需要的内存的大小
malloc_size 是操作系统的分配的内存大小(在这个里面需要遵循对应的字节对齐和操作系统对应的一些规范)
NSObject 实际分配多大的内存
sizeOf编译的时候就决定了对应的大小
isa和superClass调用流程
13315615-1255b59d53589f51.pngOC三种对象形式: 实例对象(instance),类对象(class),元类对象(meta_class)
实例对象:存着这个对象的一些信息
类对象:存储着isa、superClass属性、对象方法、协议、成员变量…...
元类对象:isa、superClass、类方法…...
对象的isa指向哪里?
interface对象的isa指向类对象;
class对象的isa指向meta-class对象;
meta-class的isa指向根类的isa(oc里面是NSObject)
对象的类对象里面存放着什么?
存储着isa、superClass属性、对象方法、协议、成员变量…...
KVO原理内部机制
添加不同的对象去监听对应的值得变化;
发送消息的时候的isa指针是不一样的
动态生成的一个NSKVONotifying_xxx的类,这个类是对应的xxx类的子类,(???在这里带着两个问题去思考,就是新的类的isa指针指向哪里,还有就是对应的添加的观察者set方法的内部实现原理)
//例子 ARC
- (void)setAge:(int)age {
_age = age;
}
//创建后的setter方法
//添加了KVO的观察者的内部实现是利用runtime动态的创建了一个此类的子类NSKVONotifying_xxx
- (void)setAge:(int)age {
_NSSetIntValueAndNotify();
}
//调用的是Foundation框架的这个c函数
//在这个方法里面会调用
//伪代码
void _NSSetIntValueAndNotify{
[self willChangeValueForKey:key];
[super setAge:age];
[self didChangeValueForKey:key]; //然后再在didChangeValueForKey里面观察者去执行对应的监听
}
- (void)didChangeValueForKey:(NSString *)key {
[observer observeValueForKeyPath:@"age" ofObject:self change:change context:context];
}
-
利用runtime的动态特性,动态的生成一个子类,并且将原来的isa指针指向这个子类对象;
-
当修改instance的属性时,会调用Foundation的_NSSetxxxValueAndNotify函数
-
willChangeValueForKey
-
父类原来的setter
-
didChangeValueForKey
内部会触发监听器(Observer)的监听方法observerValueForKeyPath:Object:change
-
手动触发KVO
调用willChangeValueForKey和didChangeValueForKey
KVC
kvc设置会不会触发KVO呢???
会,首先寻找setter方法,允许访问实例变量也会触发对应的KVO。
@property (nonatomic ,assign) int age;
//设置会首先查询 _age , _isAge, age ,isAge
//取值 getAge ,age ,isAge, _age
Category
oc是消息发送机制,类对象里面查询对应的方法;但是category里面的方法存储在哪里呢
都会存储到对应的类方法里面,isa指针,在里面会对方法重新进行排序
-
编译之后的底层结构是结构体;里面存储着分类的对象方法,类方法属性,协议
-
在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象,元类对象中)是怎么合并的呢??分类中有相同的方法怎么去加载执行
分类是有顺序的,根据加载的顺序。分类是先执行的,会先执行分类的方法
Category和Extension的区别
-
Class Extension在编译的时候,它的数据已经包含在类信息中
-
Category是在运行时,才会将数据合并到类信息中
分类中有load方法吗?load方法什么时候调用?load方法能继承吗?
有load方法; 类加载进内存的时候会被调用;load方法没有被覆盖 为什么分类的load方法会被调用 普通的调用都是通过isa指针找到类去实现对应的方法,如果没有则通过superClass去调用,而load方法则不一样,它是直接取出来method的地址,直接调用。
-
load方法的调用是从父类开始的。
-
分类则是根据编译顺序加载的。load都会调用。
initialize
-
类第一次接收到消息的时候会被调用;
-
会先调用父类的initialize(消息发送机制);
-
被加载过之后就不会再去加载了;(Objc)msgSend也是根据isa指针去寻找superClass)
load和initialize的区别
load是直接找到函数指针去执行,initialize消息发送机制(如果分类实现对应的initizlize实现则调用父类的initialize)父类的initizlize会被会被多次
-
调用方式,load根据函数地址直接调用,initizlize是通过objc_msgSend调用的
-
调用时刻: load是runtime加载类,分类的时候调用(只会调用一次);initizlize是第一次被调用的时候出现,每个类只会被调用一次(父类的initizlize可能会被调用多次)
-
load 先调用自己的load,调用子类的load,会先调用父类的load; 分类的load方法是先编译先调用
-
initizlize先初始化父类,再初始化子类
-
Category能否添加成员变量?怎么添加?
struct object_IMPL {
Class isa;
int _age;
}
分类可以添加属性,只生成了对应的函数的声明;
不能直接添加成员变量,但是可以间接添加;
关联对象runtime ,全局变量的问题;
//时刻记住对应的调用图和调用顺序,还有对应的各个结构
block面试题
block本质上是一个对象,也有isa指针
封装了函数调用的地址和调用环境的OC对象
捕获变量
能够访问外部的变量;
默认的是(auto)值传递
auto int a = 10; //自动变量
static int height = 20; //地址传递
上述两个在block的时候auto是值传递,static是获取到对应的地址
局部变量会被捕获,全局变量不会被捕获
oc里面self是隐式参数,属于局部静态参数(捕获地址)
屏幕快照 2018-10-21 上午9.04.56.png
问题 函数调用栈???
copy
栈copy到堆上,堆上的代码由程序员自己释放,在ARC下编译器自动帮助转换成了NSMallocBlock,MRC下需要自己手动释放;
对象类型的auto变量
auto 自动捕获进block代码块
如果block是在栈上的,不管外面的是strong还是weak引用都不会产生引用;
如果block是在堆上的根据外部的修饰符
__block修饰符,用来修饰auto,不能用来修饰static,编译器会将__block包装成一个对象
__block的内存管理
-
捕获变量NSObject *obj在内部会释放copy和dispose对象;
-
__weak修饰
runtime
消息转发机制
isa(64位下为共用体union)
位运算:& || !读取规则
掩码:一般是用来做位与运算的
结构体:位域的运算 New
在Arm64位下用共用体其中33位用来存储isa指针(优化过的内存)
struct method_t {
SEL name;
const char *types;
IMP imp;
}
不同类中同一个SEL是相同的;imp就是函数的指针;
消息机制 给方法调用者发送消息
三个阶段
-
消息发送 源码汇编
屏幕快照 2018-10-24 上午10.51.54.png
-
动态方法解析 resolveInstanceMethod | resolveClassMethod
-
消息转发
forwardingTargetForSelector返回一个对象,给这个对象发送消息重复进入上述的流程
方法签名:返回值类型、参数类型
@dynamic 自己手动去生成对应的setter和getter的实现,也不会自动生成实例变量
@synthesize age = _age 属性age生成 _age实例变量,并生成对应的setter和getter
-(Class)class { return objc_getClass(self); } ????
消息接受者仍然是子类对象,只是查找方法的时候失去父类开始寻找的。
runtime API
class_coptIvarlist
oc是一门动态性比较强的编程语言;runtime是一套c语言的API; 是在程序运行的时候才去执行的代码。
平时项目中怎么使用runtime
-
利用关联对象给分类添加属性
-
遍历类的所有成员变量(修改UitextFIeld的站位文字的颜色,字典转模型,自动归档)
-
交换方法(交换系统的方法)
-
防止程序崩溃(消息发送机制)
监听所有按钮的事件 hook UIControl的sendActionTargetEvent
method_exchangeImplementation //类簇 NSString NSArray NSDictionary 真实类型是其他类型
runloop
source0,source1,timer,port,observer在每一种模式下,每一个runloop至少有一种mode
runloop在实际开发中的应用
控制线程的生命周期(AFNetworking);线程保活
解决NSTimer在滑动时停止工作的问题;//runloop不同的Mode模式下运行对应的diamante
监控应用卡顿;//加载大量的图片,导致滑动卡顿现象
性能优化;//
NSRunloop的run方法是无法停止的,它专门用于开启一个永不销毁的线程。 (生成与销毁)
注意:当发生坏内存访问出现的错误
线程的封装
自定义一个线程方法:封装的时候考虑到是否包含暴露对应的接口信息
子线程全新的runloop需要添加对应的source0,source1或者tiemr,observer,port
屏幕快照 2018-10-30 上午11.24.26.pngGCD等多线程技术
数据安全问题怎么去防止???
加锁:
sync和sync决定着在哪一个线程执行;决定这是否开启新的线程。
???OSSpinLock自旋锁 忙等状态一直耗费CPU资源
- 目前不安全,优先级反转问题;
线程的调度:时间片段轮转算法(线程、进程)
OSUnfairLock
Pthread_mutex互斥锁
-
NSLock
-
NSRerusiveLock
-
NSCondition
-
NSConditionLock : NSCondition
Dispatch_queue
Dispatch_semaphore
@synchronized([self class]) {每个类对象都是一个,对象可能有多个,所以在加锁的时候需要注意一下}
在子线程中,串行队列,顺序执行代码相当于有了依赖。
ios性能问题加锁
屏幕快照 2018-10-31 上午11.24.06.pngqueue决定着怎么样去执行
屏幕快照 2018-10-30 下午12.05.34.png
队列的特点:FIFO
- sync往当前串行队列中添加任务才会造成死锁
PerformSelector:withObject:afterDelay:
在runloop中,用到了定时器,添加到runloop中去,beforWaiting
atomic/nonatomic
atomic:原子性操作setter和getter,但是不是线程安全的可以在其它的线程中释放掉该资源
IO安全操作
多读单写操作
pthread_rwlock 会进入休眠
dispatch_async_barrier 异步栅栏
CADisplayLink和NSTimer循环引用和不准时的问题
定时器的内存管理:
-
利用Block的弱引用;
-
创建一个中间的弱指针引用,当ViewController销毁的时候引用链会断裂;(消息转发机制)
消息转发机制: NSProxy(转么用来消息转发)和NSObject
-
消息发送
-
动态解析
-
消息转发
NSObject和NSProxy做代理的区别
NSProxy主要用于消息转发
定时器的准确性
NSTimer和CADisplayLink依赖runloop,runloop本身可能会有耗时的操作
GCD依靠内核去执行时间,不依赖runloop,所以不存在线程阻塞的问题,导致定时器运行不准确;
程序空间
-
代码段
-
数据段
-
堆
-
栈
-
内核区
Tagged Pointer
从64bit开始引入的技术NSNumber,NSDate,NSString等小对象的存储。
耗电操作
-
CPU
-
网络请求
-
定位
-
图像
优化:
-
尽可能降低CPU,GPU
-
少用定时器
-
优化I/O操作
-
尽量不要频繁写入小数据,最好批量一次性写入
-
读写大量重要数据时,考虑用dispatch_io,提供了基于GCD的异步操作文件I/O的API,用dispatch_io系统会优化磁盘访问
-
数据量比较大的,建议用数据库(SQLite,CoreData)
-
-
网络优化
-
减少,压缩网络数据
-
缓存数据(多次请求相同的)
-
试用断点续传
-
-
网络不可用时,不要尝试执行网络请求
-
让用户可以取消长时间运行或者速度很慢的网络操作,设置合适的超时时间
-
定位
- 尽量使用少的定位,不要多次去尝试定位信息;
APP启动优化
冷启动
-
dyld dynamic link editor,apple动态链接库,mac-omac-o,装载APP的可执行文件,同时会递归加载所有依赖的动态库,当dyld把可执行文件、动态库装载完毕后会同志runtime进行下一步的处理
优化:合并一些动态库,尽量不要使用C++虚函数,objc的+load方法,可以使用+ininitialize和dispatch_once
-
runtime: 调用map_iamges进行可执行文件进行解析
-
main
热启动
安装包瘦身
屏幕快照 2018-11-01 下午3.41.35.png 屏幕快照 2018-11-01 下午3.45.01.png多线程
pthread
NSThread
GCD
设计模式与架构
多做项目:多接触
MVP
网友评论