美文网首页
知识整理

知识整理

作者: 未来小文学家 | 来源:发表于2018-10-10 23:11 被阅读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 在当前屏幕上
    
        }
    

    相关文章

      网友评论

          本文标题:知识整理

          本文链接:https://www.haomeiwen.com/subject/frzmaftx.html