知识整理

2018-10-10  本文已影响0人  未来小文学家

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.冷启动优化

  1. 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方法中。
  1. 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.png
1432798974517485.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 在当前屏幕上

    }
上一篇下一篇

猜你喜欢

热点阅读