1.说一下OC的反射机制?
我们可以根据“字符串”获取一个“类”、获取一个“方法”、获取一个“协议”;也可以根据“类”、“方法”、“协议”获取相应的“字符串”,这样互相转化的过程称为oc的反射机制。(个人理解)下面是对应的OC方法,但是实质是runtime实现的
FOUNDATION_EXPORT NSString *NSStringFromClass(Class aClass);
FOUNDATION_EXPORT Class _Nullable NSClassFromString(NSString *aClassName);
FOUNDATION_EXPORT NSString *NSStringFromSelector(SEL aSelector);
FOUNDATION_EXPORT SEL NSSelectorFromString(NSString *aSelectorName);
2.@property中有哪些属性关键字?
atomic、nonatomic 、readonly、readwrite 、assign、strong、weak、copy 、retain、release、setter = 、 getter =
(ios9)nullable,nonnull,null_resettable,null_unspecified
3.@synthesize 和 @dynamic 分别有什么作用?
@synthesize 的语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法。在xcode的4.5版本以前,使用@ property 需要在.m实现中加上,需要在.m文件中写上@synthesize student = _student(这里就用student代表一个属性),以后@dynamic 告诉编译器,属性的setter与getter方法由用户自己实现,不自动生成(如,@dynamic var)。
4.KVC的底层实现?
1). 检查是否存在相应的key的set方法,如果存在,就调用set方法。
2). 如果set方法不存在,就会查找与key相同名称并且带下划线的成员变量,如果有,则直接给成员变量属性赋值。
3). 如果没有找到_key,就会查找相同名称的属性key,如果有就直接赋值。
4). 如果还没有找到,则调用valueForUndefinedKey:和setValue:forUndefinedKey:方法。
5.KVO的底层实现?
当某个类的属性对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类NSKVONotifying_Person,
isa指针指向当前类,键值观察通知依赖于NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey:;在一个被观察属性发生改变之前, willChangeValueForKey:一定会被调用,这就 会记录旧的值。而当改变发生后,didChangeValueForKey:会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。
6.ViewController生命周期?
1. initWithCoder:通过nib文件初始化时触发。
2. awakeFromNib:nib文件被加载的时候,会发生一个awakeFromNib的消息到nib文件中的每个对象。
3. loadView:开始加载视图控制器自带的view。
4. viewDidLoad:视图控制器的view被加载完成。
5. viewWillAppear:视图控制器的view将要显示在window上。
6. updateViewConstraints:视图控制器的view开始更新AutoLayout约束。
7. viewWillLayoutSubviews:视图控制器的view将要更新内容视图的位置。
8. viewDidLayoutSubviews:视图控制器的view已经更新视图的位置。
9. viewDidAppear:视图控制器的view已经展示到window上。
10. viewWillDisappear:视图控制器的view将要从window上消失。
11. viewDidDisappear:视图控制器的view已经从window上消失。
7.类变量的 @public,@protected,@private,@package 声明各有什么含义?
@public 任何地方都能访问;
@protected 该类和子类中访问,是默认的;
@private 只能在本类中访问;
@package 本包内使用,跨包不可以。
8.什么是谓词?
//定义谓词对象,谓词对象中包含了过滤条件(过滤条件比较多)
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age<%d",30];
//使用谓词条件过滤数组中的元素,过滤之后返回查询的结果
NSArray *array = [persons filteredArrayUsingPredicate:predicate];
9.在category和protocol中如何实现@property?
objc_setAssociatedObject
objc_getAssociatedObject
- (void)setNameWithSetterGetter:(NSString *)nameWithSetterGetter {
objc_setAssociatedObject(self, &nameWithSetterGetterKey, nameWithSetterGetter, OBJC_ASSOCIATION_COPY);
}
- (NSString *)nameWithSetterGetter {
return objc_getAssociatedObject(self, &nameWithSetterGetterKey);
}
9.在block内如何修改block外部的变量?
block中不允许修改外部变量的值,因为作用域发生了变化;
加上__block关键字;__block关键字的作用:如果此变量被block持有,就将变量的值拷贝到堆中,并指向堆中,即改变了变量的作用域,使得在block内也可以操作变量了;
10.如何调试BAD_ACCESS错误?
原因:每当你的应用程序尝试引用损坏的指针,一个异常就会被内核抛出
打开enable zombie objects 或者用 Address Sanitizer;
僵尸调试模式
Edit Scheme
勾选 Enable Zombie Objects选框。
预防:analyze静态分析
屏幕快照 2018-10-10 下午11.06.44.png
11.iOS 中的 nil、Nil、NULL、NSNull 僵尸对象和野指针?
nil空对象.
NSNull值为空的对象NSNull对象拥有一个有效的内存地址.
nil和Nil在使用上是没有严格限定的,也就是说凡是使用nil的地方都可以用Nil来代替,反之亦然。
只不过从编程人员的规约中我们约定俗成地将nil表示一个空对象,Nil表示一个空类
而NULL就是典型C语言的语法,它表示一个空指针
12.理解 : UDID、UUID、IDFA、IDFV?
UUID:
代码获取的方式:NSLog(@"uuid = %@",[NSUUID UUID].UUIDString);它保证对在同一时空中的所有机器都是唯一的。所以,需要作为唯一标识码的话,你可以通过保存在keychain或者NSUserDefaults中.
NSString *uuid = [NSUUID UUID].UUIDString;
UDID :
所谓UDID指的是设备的唯一设备识别符,移动广告商和游戏网络运营商往往需要通过UDID用来识别玩家用户,并对用户活动进行跟踪。UDID 在 iOS5.0 的时候已经被抛弃使用了
IDFA:
广告标示符,适用于对外:例如广告推广,换量等跨应用的用户追踪等。如果用户完全重置系统((设置程序 -> 通用 -> 还原 -> 还原位置与隐私) ,这个广告标示符会重新生成。另外如果用户明确的还原广告(设置程序-> 通用 -> 关于本机 -> 广告 -> 还原广告标示符) ,那么广告标示符也会重新生成。
#import <AdSupport/AdSupport.h>
NSString *adId = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
IDFV:
是给Vendor标识用户用的,每个设备在所属同一个Vender(应用提供商)的应用里,都有相同的值。和idfa不同的是,idfv的值是一定能取到的,所以非常适合于作为内部用户行为分析的主id,来标识用户,替代OpenUDID。
注意:如果用户将属于此Vender的所有App卸载,则idfv的值会被重置,即再重装此Vender的App,idfv的值和之前不同。
NSString *idfv = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
13.冷启动优化
- pre-main阶段 ,
执行:
1.加载应用的可执行文件
2.加载动态链接库加载器dyld(dynamic loader)
3.dyld递归加载应用所有依赖的dylib(dynamic library 动态链接库)
测时:
Xcode 中 Edit scheme -> Run -> Auguments ,
将环境变量 DYLD_PRINT_STATISTICS 设为 1
优化:
1. 移除不需要用到的动态库
2. 移除不需要用到的类
3. 合并功能类似的类和扩展
4. 尽量避免在+load方法里执行的操作,可以推迟到+initialize方法中。
- main()阶段
执行:
2.1. dyld调用main()
2.2. 调用UIApplicationMain()
2.3. 调用applicationWillFinishLaunching
2.4. 调用didFinishLaunchingWithOptions
测时:
打印时间日志
优化:
1.梳理各个三方库,找到可以延迟加载的库,做延迟加载处理,比如放到首页控制器的viewDidAppear方法里。
2.梳理业务逻辑,把可以延迟执行的逻辑,做延迟执行处理。比如检查新版本、注册推送通知等逻辑。
3.避免复杂/多余的计算。
4.避免在首页控制器的viewDidLoad和viewWillAppear做太多事情,这2个方法执行完,首页控制器才能显示,部分可以延迟创建的视图应做延迟创建/懒加载处理。
5.采用性能更好的API。
6.首页控制器用纯代码方式来构建。
14.tableView性能优化
1.cell复用
2.cell高度的计算,对于已经计算出的高度,我们需要进行缓存
3.渲染
(1)当有图像时,预渲染图像,在bitmap context先将其画一遍,导出成UIImage对象,然后再绘制到屏幕,这会大大提高渲染速度。具体内容可以自行查找“利用预渲染加速显示iOS图像”相关资料。
(2)渲染最好时的操作之一就是混合(blending)了,所以我们不要使用透明背景,将cell的opaque值设为Yes,背景色不要使用clearColor,尽量不要使用阴影渐变等
(3)由于混合操作是使用GPU来执行,我们可以用CPU来渲染,这样混合操作就不再执行。可以在UIView的drawRect方法中自定义绘制。
4.减少视图的数目
5.减少多余的绘制操作,在实现drawRect方法的时候,它的参数rect就是我们需要绘制的区域,在rect范围之外的区域我们不需要进行绘制,否则会消耗相当大的资源。
6、不要给cell动态添加subView
7、异步化UI,不要阻塞主线程
8、滑动时按需加载对应的内容
9.1、下面的情况或操作会引发离屏渲染:
为图层设置遮罩(layer.mask)
将图层的layer.masksToBounds / view.clipsToBounds属性设置为true
将图层layer.allowsGroupOpacity属性设置为YES和layer.opacity小于1.0
为图层设置阴影(layer.shadow *)。
为图层设置layer.shouldRasterize=true
使用CGContext在drawRect :方法中绘制大部分情况下会导致离屏渲染,甚至仅仅是一个空的实现
9.2、优化方案
当我们需要圆角效果时,可以使用一张中间透明图片蒙上去使用ShadowPath
指定layer阴影效果路径使用异步进行layer渲染(Facebook开源的异步绘制框架AsyncDisplayKit)
设置layer的opaque值为YES,减少复杂图层合成尽量使用不包含透明(alpha)通道的图片资源尽量
设置layer的大小值为整形值直接让美工把图片切成圆角进行显示,这是效率最高的一种方案
很多情况下用户上传图片进行显示,可以让服务端处理圆角使用代码手动生成 圆角Image设置到要显示的View上,
利用UIBezierPath(CoreGraphics框架)画出来圆角图片
15.__strongSelf 和 __weakSelf的使用场景?
当block被self持有时,这时需要使用__weakSelf,避免循环引用,当block没有执行完,self不能被销毁需保留至block执行完,这时使用__strongSelf.见如下代码:
不需要使用__weakSelf,因为block不被self持有:
[UIView animateWithDuration:1 animations:^{
NSLog(@"%@",self);
}];
需要使用__weakSelf,不使用会循环引用:
__weak typeof(self) weakSelf = self;
self.block = ^{
[weakSelf dismissViewControllerAnimated:NO completion:nil];
};
需要使用__weakSelf和__strongSelf,如果不使用__strongSelf,self.str 为空添加到数组会崩溃:
self.str = @"ddgggg";
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf dismissViewControllerAnimated:NO completion:nil];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"因为下面引用strongSelf,所以controller不会销毁 %@",weakSelf);
//controller不会被销毁,因此strongSelf.str不会为nil,所以不会崩溃,当block执行完以后,controller会自动销毁
NSArray *arr = @[@"key",strongSelf.str];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"因为下面已经没有引用strongSelf,所以controller此时销毁了 %@",weakSelf);
});
});
};
16.MutableCopy 和 Copy解析?
image.png//深拷贝遵循NSCopying 或NSMutableCopying 协议
- (id)copyWithZone:(NSZone *)zone
{
PersonItem *personMutableCopy = [[PersonItem allocWithZone:zone] init];
personMutableCopy.name = [self.name mutableCopy];
// assign 修饰的基本数据类型,没有对应的指针,可以直接赋值操作,没有也无需copy操作。
personMutableCopy.age = self.age;
personMutableCopy.dog = [self.dog copy];
return personMutableCopy;
}
17.简单说点runloop?
一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出。 1432798883604537.png1432798974517485.png
定时器添加到runLoop的模式下运行
UITrackingRunLoopMode :交互模式
NSDefaultRunLoopMode :默认 不包括手势交互
NSRunLoopCommonModes :手势交互模式+默认 不包括手势交互
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
监听runloop状态
// 创建observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
NSLog(@"----监听到RunLoop状态发生改变---%zd", activity);
});
// 添加观察者:监听RunLoop的状态
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
// 释放Observer
CFRelease(observer);
线程常驻
self.thread = [[XMGThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[self.thread start];
- (void)run {
NSLog(@"----------run----%@", [NSThread currentThread]);
//1
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
NSLog(@"---------");
//2
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
}
18.消息调用的过程?
1.是不是需要处理该方法
对象在收到无法处理的消息时,会调用下面的方法,前者是调用类方法时会调用,后者是调用对象方法时会调用
// 类方法专用
+ (BOOL)resolveClassMethod:(SEL)sel
// 对象方法专用
+ (BOOL)resolveInstanceMethod:(SEL)sel
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSString *method = NSStringFromSelector(sel);
if ([@"playPiano" isEqualToString:method]) {
/**
添加方法
@param self 调用该方法的对象
@param sel 选择子
@param IMP 新添加的方法,是c语言实现的
@param 新添加的方法的类型,包含函数的返回值以及参数内容类型,eg:void xxx(NSString *name, int size),类型为:v@i
*/
class_addMethod(self, sel, (IMP)playPiano, "v");
return YES;
}
return NO;
}
2.找别的类调用该方法
- (id)forwardingTargetForSelector:(SEL)aSelector
{
NSString *seletorString = NSStringFromSelector(aSelector);
if ([@"playPiano" isEqualToString:seletorString]) {
Student *s = [[Student alloc] init];
return s;
}
// 继续转发
return [super forwardingTargetForSelector:aSelector];
}
3.完整的消息转发
了前两步,还是无法处理消息,那么就会做最后的尝试,先调用methodSignatureForSelector:获取方法签名,然后再调用forwardInvocation:进行处理
// 完整的消息转发
- (void)travel:(NSString*)city
{
NSLog(@"Teacher travel:%@", city);
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSString *method = NSStringFromSelector(aSelector);
if ([@"playPiano" isEqualToString:method]) {
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:@"];
return signature;
}
return nil;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
SEL sel = @selector(travel:);
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:@"];
anInvocation = [NSInvocation invocationWithMethodSignature:signature];
[anInvocation setTarget:self];
[anInvocation setSelector:@selector(travel:)];
NSString *city = @"北京";
// 消息的第一个参数是self,第二个参数是选择子,所以"北京"是第三个参数
[anInvocation setArgument:&city atIndex:2];
if ([self respondsToSelector:sel]) {
[anInvocation invokeWithTarget:self];
return;
} else {
Student *s = [[Student alloc] init];
if ([s respondsToSelector:sel]) {
[anInvocation invokeWithTarget:s];
return;
}
}
// 从继承树中查找
[super forwardInvocation:anInvocation];
}
4.以上三步都没有处理,会调用父类方法,父类也没有的话直接崩溃
19.http 相关?
TCP/IP按照层次从上至下分为四层:应用层,传输层,网络层,数据链路层。(实际上最初理论上OSI模型是分的七层,我们程序猿的话通常只用分四层就行了。)
TCP的优点: 可靠,稳定 TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接。
UDP的优点: 快,比TCP稍安全 UDP没有TCP的握手、确认、窗口、重传、拥塞控制等机制,UDP是一个无状态的传输协议,所以它在传递数据时非常快。
TCP三次握手(个人理解):
一次:a发出请求给b; a:(什么也不知道) b:(知道a发送成功)
二次:b告诉a我收到了;a:(知道a发成功,b发成功) b:(知道a发成功)
三次:a告诉b我收到了;a:(知道a发成功,b发成功) b:(知道a发成功,b发成功)
HTTP请求的方法:OPTIONS、HEAD、GET、POST、PUT、DELETE、TRACE、CONNECT
20.介绍下内存的几大区域?
1.栈区
栈区(stack)由编译器自动分配并释放,存放的是函数的参数值,局部变量等,方法调用的实参也是保存在栈区的。栈是系统数据结构,对应线程/进程是唯一的。优点是快速高效,缺点是有限制,数据不灵活。由编译器自动分配释放。主要存放一些基本类型的变量和对象引用类型。
2.堆区
由程序员分配和释放,如果程序员不释放,可能会出现内存泄露,程序结束的时候,可能会由操作系统回收,比如iOS中alloc都是存放在堆中,优点是灵活方便,数据适应面广泛,但是效率有一定降低,堆空间的分配总是动态的,不同堆分配的内存无法互相操作。虽然程序结束的时候所有的数据空间都会被释放回系统,但是精确的申请内存,释放内存匹配是良好程序的基本要素。主要存放用new构造的对象和数组。
3.全局区(静态区)
全局变量和静态变量是放在一起的,初始化的全局变量和静态变量存放在一块区域,未初始化的全局变量和静态变量在相邻的另一块区域,程序结束后由系统释放。
注意:全局区又可分为未初始化全局区:.bss段和初始化全局区:data段。
4.文字常量区
存放常量字符串,程序结束后由系统释放。
5.代码区
存放函数的二进制代码
21.你是如何组件化解耦的?
提供一个中间管理类,将个个组件由这个管理类统一调配。
22.GCD的实现原理?
GCD有一个底层线程池,这个池中存放的是一个个的线程。之所以称为“池”,很容易理解出这个“池”中的线程是可以重用的,当一段时间后这个线程没有被调用话,这个线程就会被销毁。注意:开多少条线程是由底层线程池决定的(线程建议控制再3~5条),池是系统自动来维护,不需要我们程序员来维护
如果队列中存放的是同步任务,则任务出队后,底层线程池中会提供一条线程供这个任务执行,任务执行完毕后这条线程再回到线程池。这样队列中的任务反复调度,因为是同步的
如果队列中存放的是异步的任务,(注意异步可以开线程),当任务出队后,底层线程池会提供一个线程供任务执行,因为是异步执行,队列中的任务不需等待当前任务执行完毕就可以调度下一个任务,这时底层线程池中会再次提供一个线程供第二个任务执行,执行完毕后再回到底层线程池中
-这样就对线程完成一个复用,而不需要每一个任务执行都开启新的线程,也就从而节约的系统的开销,提高了效率。在iOS7.0的时候,使用GCD系统通常只能开5~8条线程,iOS8.0以后,系统可以开启很多条线程,但是实在开发应用中,建议开启线程条数:3-5条最为合理
23.怎么防止别人反编译你的app?
iOS应用安全风险
1、内购破解iOS应用需防反编译风险之一:插件法(仅越狱)、iTools工具替换文件法(常见为存档破解)、八门神器修改
2、网络安全风险iOS应用需防反编译风险之二:截获网络请求,破解通信协议并模拟客户端登录,伪造用户行为,对用户数据造成危害
3、应用程序函数PATCH破解iOS应用需防反编译风险之三:利用FLEX 补丁软件通过派遣返回值来对应用进行patch破解
4、源代码安全风险iOS应用需防反编译风险之四:通过使用ida等反汇编工具对ipa进行逆向汇编代码,导致核心代码逻辑泄漏与被修改,影响应用安全
5、面对这些iOS应用存在的风险,iOS应用如何防止被反编译,下面看下iOS应用加密技术
iOS应用加密防反编译技术
1、本地数据加密iOS应用防反编译加密技术之一:对NSUserDefaults,sqlite存储文件数据加密,保护帐号和关键信息
2、URL编码加密iOS应用防反编译加密技术之二:对程序中出现的URL进行编码加密,防止URL被静态分析
3、网络传输数据加密iOS应用防反编译加密技术之三:对客户端传输数据提供加密方案,有效防止通过网络接口的拦截获取数据
4、方法体,方法名高级混淆iOS应用防反编译加密技术之四:对应用程序的方法名和方法体进行混淆,保证源码被逆向后无法解析代码
5、程序结构混排加密iOS应用防反编译加密技术之五:对应用程序逻辑结构进行打乱混排,保证源码可读性降到最低
24.你知道哪些加密?
1:非对称加密(RSA)
可逆,算法公开,效率高,适合大型文件(一般对文件用对称加密,对加密用的秘钥用非对称加密),公钥加密,私钥解密;私钥加密,公钥解密;
2:哈希(散列)函数 (MD5,SHA1、256、512、HMAC)
不可逆,算法公开,对于相同的数据加密,得到的结果是一样的。对于不同的数据,得到的结果可能是一样的,:MD5->32位(有限),信息摘要(指纹特点,局部指定整体),用途: 密码
3:对称加密(AES、DES、3DES)
可逆,高级密码标准,美国国家安全局在使用,加密与解密使用同一个秘钥,秘钥的保密工作非常重要。
25.dSYM你是如何分析的?
方法1 使用XCode
这种方法可能是最容易的方法了。
要使用Xcode符号化 crash log,你需要下面所列的3个文件:
a.crash报告(.crash文件)
b.符号文件 (.dsymb文件)
c.应用程序文件 (appName.app文件,把IPA文件后缀改为zip,然后解压,Payload目录下的appName.app文件), 这里的appName是你的应用程序的名称。
把这3个文件放到同一个目录下,打开Xcode的Window菜单下的organizer,然后点击Devices tab,然后选中左边的Device Logs。
然后把.crash文件拖到Device Logs或者选择下面的import导入.crash文件。
这样你就可以看到crash的详细log了。
方法2 使用命令行工具symbolicatecrash
有时候Xcode不能够很好的符号化crash文件。我们这里介绍如何通过symbolicatecrash来手动符号化crash log。
在处理之前,请依然将“.app“, “.dSYM”和 ".crash"文件放到同一个目录下。现在打开终端(Terminal)然后输入如下的命令:
export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
然后输入命令:
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/PrivateFrameworks/DTDeviceKitBase.framework/Versions/A/Resources/symbolicatecrash appName.crash appName.app > appName.log
现在,符号化的crash log就保存在appName.log中了。
方法3 使用命令行工具atos
如果你有多个“.ipa”文件,多个".dSYMB"文件,你并不太确定到底“dSYMB”文件对应哪个".ipa"文件,那么,这个方法就非常适合你。
特别当你的应用发布到多个渠道的时候,你需要对不同渠道的crash文件,写一个自动化的分析脚本的时候,这个方法就极其有用。
具体方法 请百度
本文分析了拿到用户的.crash文件之后,如何符合化crash文件的3种方法,分别有其适用场景,方法3适用于自动化crash文件的分析。
26.0x8badf00d表示是什么?
看门狗(watch dog),启动超时会触发此异样,解决办法:优化启动。
27.runtime 中,SEL和IMP的区别 ?
SEL : 类成员方法的指针,但不同于C语言中的函数指针,函数指针直接保存了方法的地址,但SEL只是方法编号。
IMP:一个函数指针,保存了方法的地址
//获取方法编号
SEL methodId = @selector(func1);
//执行对应方法
[self performSelector:methodId methodIdwithObject:nil];
//获取方法name
NSString*methodName = NSStringFromSelector(methodId);
//获取方法地址
IMP methodPoint = [self methodForSelector:methodId];
//执行方法地址
methodPoint();
28.多线程有哪些?
1.NSThread
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil];
[thread setName:@"测试thread"];
[thread start];
// 什么线程调用,就返回什么线程
@property (class, readonly, strong) NSThread *currentThread;
//类属性,用于返回主线程,不论在什么线程调用都返回主线程
@property (class, readonly, strong) NSThread *mainThread;
/*设置线程的优先级,范围为0-1的doule类型,数字越大优先级越高
我们知道,系统在进行线程调度时,优先级越高被选中到执行状态的可能性越大
但是我们不能仅仅依靠优先级来判断多线程的执行顺序,多线程的执行顺序无法预测*/
@property double threadPriority;
//线程的名称,前面的栗子已经介绍过了
@property (nullable, copy) NSString *name
//判断线程是否正在执行
@property (readonly, getter=isExecuting) BOOL executing;
//判断线程是否结束
@property (readonly, getter=isFinished) BOOL finished;
//判断线程是否被取消
@property (readonly, getter=isCancelled) BOOL cancelled;
//让线程睡眠,立即让出当前时间片,让出CPU资源,进入阻塞状态类方法,什么线程执行该方法,什么线程就会睡眠
+ (void)sleepUntilDate:(NSDate *)date;
//同上,这里传入时间
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
//退出当前线程,什么线程执行,什么线程就退出
+ (void)exit;
//实例方法,取消线程调用该方法会设置cancelled属性为YES,但并不退出线程
- (void)cancel;
2.GCD
//单例
+ (instancetype)sharedUtil {
static GCD *gcd = nil;
//保证初始化创建只执行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
gcd = [[GCD alloc] init];
});
return gcd;
}
//--------基本使用
//创建并行队列
dispatch_queue_t queue = dispatch_queue_create("ddddd", DISPATCH_QUEUE_CONCURRENT);
//创建串行队列
dispatch_queue_t queue1 = dispatch_queue_create("ddddd", DISPATCH_QUEUE_SERIAL);
//全局并行队列
dispatch_queue_t queue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//主队列
dispatch_queue_t queue3 = dispatch_get_main_queue();
//同步执行
dispatch_sync(queue, ^{
//任务
NSLog(@"%@",[NSThread currentThread]);
});
//异步执行
dispatch_async(queue, ^{
//任务
NSLog(@"%@",[NSThread currentThread]);
});
//--------栅栏方法,该方法用于阻塞队列
dispatch_barrier_async(queue, ^{
for (int i = 0; i < 5; i++)
{
[NSThread sleepForTimeInterval:1];
NSLog(@"Task2 %@ %d", [NSThread currentThread], i);
}
});
//---------快速迭代方法,执行该方法的是主线程,不能传入主队列否则会死锁,后文会讲解
dispatch_apply(10, queue, ^(size_t t) {
NSLog(@"Task %@ %ld", [NSThread currentThread], t);
});
//---------延时执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 5), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"In %@", [NSThread currentThread]);
});
//----------调度组
//dispatch_group_enter 标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数+1
// dispatch_group_leave 标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数-1。
//当 group 中未执行完毕任务数为0的时候,才会使dispatch_group_wait解除阻塞,以及执行追加到dispatch_group_notify中的任务。
//全局并行队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 500; i++)
{
NSLog(@"Task1 %@ %d", [NSThread currentThread], i);
}
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_main_queue(), ^{
for (int i = 0; i < 500; i++)
{
NSLog(@"Task2 %@ %d", [NSThread currentThread], i);
}
dispatch_group_leave(group);
});
//暂停当前线程
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 500; i++)
{
NSLog(@"Task3 %@ %d", [NSThread currentThread], i);
}
[NSThread sleepForTimeInterval:3];
dispatch_group_leave(group);
});
//等待组任务完成,会阻塞当前线程,当任务组执行完毕时,才会解除阻塞当前线程
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
//都完成后调用此方法
dispatch_group_notify(group, queue, ^{
NSLog(@"All Task Complete");
});
// GCD 信号量:dispatch_semaphore -------------------------
// dispatch_semaphore_create:创建一个Semaphore并初始化信号的总量
// dispatch_semaphore_signal:发送一个信号,让信号总量加1
// dispatch_semaphore_wait:可以使总信号量减1,当信号总量为0时就会一直等待(阻塞所在线程),否则就可以正常执行。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); //最大并发数,当为1时,可以理解为线程锁
__block int number = 0;
for (int i = 0; i < 10; i ++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//相当于加锁,
// 追加任务1
[NSThread sleepForTimeInterval:2];
NSLog(@"%d---%@ ",i,[NSThread currentThread]); // 打印当前线程
number = 100;
dispatch_semaphore_signal(semaphore);
});
}
// dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"semaphore---end,number = %d",number);
//死锁
//主线程等待block执行完,block等待主线程执行完
+ (void)die {
NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3
}
3.NSOperation
//NSOperation、NSOperationQueue 是苹果提供给我们的一套多线程解决方案。实际上 NSOperation、NSOperationQueue 是基于 GCD 更高一层的封装,完全面向对象。但是比 GCD 更简单易用、代码可读性也更高。
/**
* 使用子类 NSInvocationOperation
*/
+ (void)useOperation {
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.创建操作
// 使用 NSInvocationOperation 创建操作1
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
// 使用 NSInvocationOperation 创建操作2
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
// 使用 NSBlockOperation 创建操作3
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[op3 addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"4---%@", [NSThread currentThread]); // 打印当前线程
}
}];
// 3.使用 addOperation: 添加所有操作到队列中
[queue addOperation:op1]; // [op1 start]
[queue addOperation:op2]; // [op2 start]
[queue addOperation:op3]; // [op3 start]
}
28.崩溃相关?
//1.崩溃信息收集
/*
在iOS中获取崩溃信息的方式有很多,比较常见的是使用友盟、云测、百度等第三方分析工具,或者自己收集崩溃信息并上传公司服务器。
下面列举一些我们常用的崩溃分析方式:
使用友盟、云测、百度等第三方崩溃统计工具。
自己实现应用内崩溃收集,并上传服务器。
Xcode-Devices中直接查看某个设备的崩溃信息。
使用苹果提供的Crash崩溃收集服务。(少用)
*/
void handleException(NSException *exception) {
NSMutableDictionary *muDic = [NSMutableDictionary new];
[muDic setValue:exception.name forKey:@"name"];
[muDic setValue:exception.callStackSymbols forKey:@"callStackSymbols"];
[muDic setValue:exception.reason forKey:@"reason"];
NSLog(@"%@",exception.userInfo);
};
+ (void)runAll{
NSLog(@"Exception");
NSSetUncaughtExceptionHandler(handleException);
// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// NSArray *arr = @[@"ddf"];
// NSString *str = arr[2];
// NSLog(@"%@",str);
// });
[self makeException];
}
+ (void)makeException {
@try {
NSArray *arr = @[@"ddf"];
NSString *str = arr[2];
NSLog(@"%@",str);
} @catch (NSException *exception) {
NSLog(@"发生异常:%@",exception.reason);
//抛出异常
@throw exception;
} @finally {
NSLog(@"最后调用了");
}
}
//2.崩溃日志解析
/* Xcode自带的崩溃分析工具
使用命令解析Crash文件,*号指的是具体的文件名
./symbolicatecrash ./*.crash ./*.app.dSYM > symbol.crash
*/
/* 使用友盟umcrashtool 解析友盟.csv
将umcrashtool 和.csv放到同一个目录下
运行umcrashtool
新建命令行输入 ./umcrashtool +.csv路径
*/
//
//3崩溃预防
/*APP常见崩溃:
runtime 交换方法解决:
数组越界,插nil等、字典的构造与修改、NSString crash (字符串操作的crash)
消息转发解决:
unrecognized selector
UI not on Main Thread Crash (非主线程刷UI (机制待改善))
预防sdk https://github.com/chenfanfang/AvoidCrash
*/
29.Block的底层原理,结构,内存以及需要注意的地方
_NSConcreteGlobalBlock: 存储在全局数据区
_NSConcreteStackBlock: 存储在栈区
_NSConcreteMallocBlock: 存储在堆区
30.什么是GDB和LLDB?
xcode里有内置的Debugger,老版使用的是GDB,xcode自4.3之后默认使用的就是LLDB了。
print命令
print命令的简化方式有prin pri p,唯独pr不能用来作为检查,因为会和process混淆,幸运的是p被lldb实现为特指print。
po命令
命令po跟p很像。p输出的是基本类型,po输出的Objective-C对象。调试器会输出这个 object 的 description。
expression命令
expression的简写就是e。可以用expression来声明新的变量,也可以改变已有变量的值。我们看到e声明的都是符号。
30.怎样判断某个cell 是否显示在屏幕上?
NSArray * visibleCells = [self.tableView visibleCells];
if ([visibleCells containsObject:cell]) {
//cell 在当前屏幕上
}
网友评论