面试题

作者: 阿龍飛 | 来源:发表于2019-03-02 10:56 被阅读4次

    怎么理解OC是动态语言,Runtime又是什么?

    静态语言:如C语言,编译阶段就要决定调用哪个函数,如果函数未实现就会编译报错。
    动态语言:如OC语言,编译阶段并不能决定真正调用哪个函数,只要函数声明过即使没有实现也不会报错。
    我们常说OC是一门动态语言,就是因为它总是把一些决定性的工作从编译阶段推迟到运行时阶段。OC代码的运行不仅需要编译器,还需要运行时系统(Runtime Sytem)来执行编译后的代码。

    Runtime是一套底层纯C语言API,OC代码最终都会被编译器转化为运行时代码,通过消息机制决定函数调用方式,这也是OC作为动态语言使用的基础。

    1.动态方法交换示例
    现在演示一个代码示例:在视图控制中,定义两个实例方法printA与printB,然后执行交换

    - (void)printA{
        NSLog(@"打印A......");
    }
    
    - (void)printB{
        NSLog(@"打印B......");
    }
    
    //交换方法的实现,并测试打印
    Method methodA = class_getInstanceMethod([self class], @selector(printA));
    Method methodB = class_getInstanceMethod([self class], @selector(printB));
    method_exchangeImplementations(methodA, methodB);
    
    [self printA];  //打印B......
    [self printB];  //打印A......
    

    2.拦截并替换系统方法
    Runtime动态方法交换更多的是应用于系统类库和第三方框架的方法替换。在不可见源码的情况下,我们可以借助Rutime交换方法实现,为原有方法添加额外功能,这在实际开发中具有十分重要的意义。
    下面将展示一个拦截并替换系统方法的示例:为了实现不同机型上的字体都按照比例适配,我们可以拦截系统UIFont的systemFontOfSize方法,具体操作如下:

    步骤1:在当前工程中添加UIFont的分类:UIFont +Adapt,并在其中添用以替换的方法。

    + (UIFont *)zs_systemFontOfSize:(CGFloat)fontSize{
        //获取设备屏幕宽度,并计算出比例scale
        CGFloat width = [[UIScreen mainScreen] bounds].size.width;
        CGFloat scale  = width/375.0;
        //注意:由于方法交换,系统的方法名已变成了自定义的方法名,所以这里使用了
        //自定义的方法名来获取UIFont
        return [UIFont zs_systemFontOfSize:fontSize * scale];
    }
    

    步骤2:在UIFont的分类中拦截系统方法,将其替换为我们自定义的方法,代码如下:

    //load方法不需要手动调用,iOS会在应用程序启动的时候自动调起load方法,而且执行时间较早,所以在此方法中执行交换操作比较合适。
    + (void)load{
        //获取系统方法地址
        Method sytemMethod = class_getClassMethod([UIFont class], @selector(systemFontOfSize:));
        //获取自定义方法地址
        Method customMethod = class_getClassMethod([UIFont class], @selector(zs_systemFontOfSize:));
        //交换两个方法的实现
        method_exchangeImplementations(sytemMethod, customMethod);
    }
    

    添加一段测试代码,切换不同的模拟器,观察在不同机型上文字的大小:

    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 100, 300, 50)];
    label.text = @"测试Runtime拦截方法";
    label.font = [UIFont systemFontOfSize:20];
    [self.view addSubview:label];
    

    3.实现分类添加新属性
    现在演示一个代码示例:为UIImage增加一个分类:UIImage+Tools,并为其设置关联属性urlString(图片网络链接属性),相关代码如下:

    //UIImage+Tools.h文件中
    UIImage+Tools.m
    @interface UIImage (Tools)
    //添加一个新属性:图片网络链接
    @property(nonatomic,copy)NSString *urlString;
    @end
    
    //UIImage+Tools.m文件中
    #import "UIImage+Tools.h"
    #import <objc/runtime.h>
    @implementation UIImage (Tools)
    //set方法
    - (void)setUrlString:(NSString *)urlString{
        objc_setAssociatedObject(self,
                                 @selector(urlString),
                                 urlString,
                                 OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    //get方法
    - (NSString *)urlString{
        return objc_getAssociatedObject(self,
                                        @selector(urlString));
    }
    //添加一个自定义方法,用于清除所有关联属性
    - (void)clearAssociatedObjcet{
        objc_removeAssociatedObjects(self);
    }
    @end
    

    测试文件中:

    UIImage *image = [[UIImage alloc] init];
    image.urlString = @"http://www.image.png";
    NSLog(@"获取关联属性:%@",image.urlString);
        
    [image clearAssociatedObjcet];
    NSLog(@"获取关联属性:%@",image.urlString);
    //打印:
    //获取关联属性:http://www.image.png
    // 获取关联属性:(null)
    

    iOS常用-锁

    我们在使用多线程的时候多个线程可能会访问同一块资源,这样就很容易引发数据错乱和数据安全等问题,这时候就需要我们保证每次只有一个线程访问这一块资源,锁 应运而生。

    @synchronized关键字加锁 互斥锁,性能较差不推荐使用

     @synchronized(这里添加一个OC对象,一般使用self) {
           这里写要加锁的代码
      }
    

    NSLock 互斥锁 不能多次调用 lock方法,会造成死锁

    //创建锁
        _mutexLock = [[NSLock alloc] init];
     //加锁
        [_mutexLock lock];
    //这里写要加锁的代码
    //解锁
        [_mutexLock unlock];
    

    RunLoop

    RunLoop的实质是一个死循环,用于保证程序的持续运行,只有当程序退出的时候才会结束

    1,保持程序的持续运行
    2,处理App中的各种事件(比如触摸事件、定时器事件、Selector事件)
    3,节省CPU资源,提高程序性能:该做事的时候做事,该休息的时候休息

    RunLoop模式常用
    NSDefaultRunLoopMode->默认模式,主线程中默认是NSDefaultRunLoopMode
    UITrackingRunLoopMode->视图滚动模式,RunLoop会处于该模式下

    runloop和线程的关系
    主线程Runloop已经创建好了,子线程的runloop需要手动创建 ;一条线程对应一个RunLoop对象,每条线程都有唯一一个与之对应的RunLoop对象。一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出

    runloop的实际使用
    1。RunLoop运行模式对NSTimer定时器的影响。
    2。ImageView推迟显示-->实现图片加载性能优化
    3。后台常驻线程(下载文件、后台播放音乐等)


    iOS持久化的方式有什么

    plist文件(属性列表)

    preference(偏好设置):保存的位置是Library/Preferences 这个目录下的 plist 文件

    NSKeyedArchiver(归档)

    SQLite 3

    CoreData


    简述block

    block是代码块,其本质和变量类似。不同的是代码块存储的数据是一个函数体。block使用最多的是存储代码块和回调。

    @property (copy , nonatomic) void (^touchXxxx) (void);
    
      !_touchXxxx ? : _touchXxxx();
    

    SDwebImage加载图片的 原理

    1、首先在webimagecache中寻找图片对应的缓存,它是以url为数据索引先在内存中查找是否有缓存;
    2、如果没有缓存,就通过md5处理过的key来在磁盘中查找对应的数据,如果找到就会把磁盘中的数据加到内存中,并显示出来;
    3、如果内存和磁盘中都没有找到,就会向远程服务器发送请求,开始下载图片;
    4、下载完的图片加入缓存中,并写入到磁盘中;
    5、整个获取图片的过程是在子线程中进行,在主线程中显示。


    GCD,NSOperationQueue

    事实:GCD是面向底层的C语言的API, NSOperationQueue是基于GCD面向OC的封装

    GCD:
    一般的需求很简单的多线程操作,用GCD都可以了,简单高效。
    1,GCD 栅栏方法:dispatch_barrier_async
    在执行完栅栏前面的操作之后,才执行栅栏操作,最后再执行栅栏后边的操作。

    /**
     * 栅栏方法 dispatch_barrier_async
     */
    - (void)barrier {
        dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
        
        dispatch_async(queue, ^{
            // 追加任务1
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        dispatch_async(queue, ^{
            // 追加任务2
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        
        dispatch_barrier_async(queue, ^{
            // 追加任务 barrier
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
            }
        });
        
        dispatch_async(queue, ^{
            // 追加任务3
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        dispatch_async(queue, ^{
            // 追加任务4
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"4---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
    }
    

    2,GCD 延时执行方法:dispatch_after

    /**
     * 延时执行方法 dispatch_after
     */
    - (void)after {
        NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            // 2.0秒后异步追加任务代码到主队列,并开始执行
            NSLog(@"after---%@",[NSThread currentThread]);  // 打印当前线程
        });
    }
    

    3,一次性代码(只执行一次):dispatch_once

    /**
     * 一次性代码(只执行一次)dispatch_once  我们在创建单例、或者有整个程序运行过程中只执行一次的代码时
     */
    - (void)once {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            // 只执行1次的代码(这里面默认是线程安全的)
        });
    }
    

    4,GCD的队列组 dispatch_group

    dispatch_group_t group =  dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      // 执行1个耗时的操作
          NSLog(@"耗时操作");
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      // 执行1个耗时的操作
          NSLog(@"耗时操作");
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
      // 等前面的异步操作都执行完毕后,回到主线程...
    });
    

    NSOperationQueue:
    各个操作之间有依赖关系、操作需要取消暂停、并发管理、控制操作之间优先级,限制同时能执行的线程数量.让线程在某时刻停止/继续等
    1,控制最大并发数

       // 创建队列
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
        // 设置最大并发操作数
        //    queue.maxConcurrentOperationCount = 2;
        queue.maxConcurrentOperationCount = 1; // 就变成了串行队列
        // 添加操作
        [queue addOperationWithBlock:^{
            NSLog(@"1-----%@", [NSThread currentThread]);
            [NSThread sleepForTimeInterval:0.01];
        }];
        [queue addOperationWithBlock:^{
            NSLog(@"2-----%@", [NSThread currentThread]);
            [NSThread sleepForTimeInterval:0.01];
        }];
    

    2,最吸引人的地方是它能添加操作之间的依赖关系
    比如说有A、B两个操作,其中A执行完操作,B才能执行操作,那么就需要让B依赖于A

    //操作依赖
    - (void)addDependency
    {
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
        NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"1-----%@", [NSThread  currentThread]);
        }];
        NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"2-----%@", [NSThread  currentThread]);
        }];
    
        [op2 addDependency:op1];    // 让op2 依赖于 op1,则先执行op1,在执行op2
    
        [queue addOperation:op1];
        [queue addOperation:op2];
    }
    //可以看到,无论运行几次,其结果都是op1先执行,op2后执行。
    

    3,NSOperation、NSOperationQueue 线程间的通信

    /**
     * 线程间通信
     */
    - (void)communication {
    
        // 1.创建队列
        NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
        // 2.添加操作
        [queue addOperationWithBlock:^{
            // 异步进行耗时操作
            for (int i = 0; i < 2; i++) {
                [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
                NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
            }
    
            // 回到主线程
            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                // 进行一些 UI 刷新等操作
                for (int i = 0; i < 2; i++) {
                    [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
                    NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
                }
            }];
        }];
    }
    

    1.串行队列+同步任务:不会开启新的线程,任务逐步完成。

    2.串行队列+异步任务:开启新的线程,任务逐步完成。

    3.并发队列+同步任务:不会开启新的线程,任务逐步完成。

    4.并发队列+异步任务:开启新的线程,任务同步完成。

    我们如果要让任务在新的线程中完成,应该使用异步线程。为了提高效率,我们还应该将任务放在并发队列中。因此在开发中使用最多的是并发队列+异步任务

    -(void)test7{
        //并发同步--可以看到,程序没有并发执行,而且没有开启新线程,是在主线程执行的
        NSLog(@"start");
        dispatch_queue_t queue = dispatch_queue_create("demo", DISPATCH_QUEUE_CONCURRENT);
        dispatch_sync(queue, ^{
            for (int i = 0; i < 2; i++) {
                NSLog(@"1----:%@",[NSThread currentThread]);
            }
        });
        dispatch_sync(queue, ^{
            for (int i = 0; i < 2; i++) {
                NSLog(@"2----:%@",[NSThread currentThread]);
            }
        });
        dispatch_sync(queue, ^{
            for (int i = 0; i < 2; i++) {
                NSLog(@"3----:%@",[NSThread currentThread]);
            }
        });
        NSLog(@"end");
    }
    
    2019-03-05 14:46:21.202900+0800 text[1765:21622] start
    2019-03-05 14:46:21.203168+0800 text[1765:21622] 1----:<NSThread: 0x6000025baf40>{number = 1, name = main}
    2019-03-05 14:46:21.203318+0800 text[1765:21622] 1----:<NSThread: 0x6000025baf40>{number = 1, name = main}
    2019-03-05 14:46:21.203428+0800 text[1765:21622] 2----:<NSThread: 0x6000025baf40>{number = 1, name = main}
    2019-03-05 14:46:21.203540+0800 text[1765:21622] 2----:<NSThread: 0x6000025baf40>{number = 1, name = main}
    2019-03-05 14:46:21.203645+0800 text[1765:21622] 3----:<NSThread: 0x6000025baf40>{number = 1, name = main}
    2019-03-05 14:46:21.203744+0800 text[1765:21622] 3----:<NSThread: 0x6000025baf40>{number = 1, name = main}
    2019-03-05 14:46:21.203853+0800 text[1765:21622] end
    
    -(void)test8{
        //并发异步--可以看到,是并发执行的,而且开启了不止一个新线程。
        NSLog(@"start");
        dispatch_queue_t queue = dispatch_queue_create("demo", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(queue, ^{
            for (int i = 0; i < 2; i++) {
                NSLog(@"1----:%@",[NSThread currentThread]);
            }
        });
        dispatch_async(queue, ^{
            for (int i = 0; i < 2; i++) {
                NSLog(@"2----:%@",[NSThread currentThread]);
            }
        });
        dispatch_async(queue, ^{
            for (int i = 0; i < 2; i++) {
                NSLog(@"3----:%@",[NSThread currentThread]);
            }
        });
        NSLog(@"end");
    }
    2019-03-05 14:56:36.803048+0800 text[1989:26884] start
    2019-03-05 14:56:36.803277+0800 text[1989:26884] end
    2019-03-05 14:56:36.803387+0800 text[1989:26916] 1----:<NSThread: 0x600000074200>{number = 3, name = (null)}
    2019-03-05 14:56:36.803543+0800 text[1989:26914] 2----:<NSThread: 0x600000076900>{number = 4, name = (null)}
    2019-03-05 14:56:36.803635+0800 text[1989:26978] 3----:<NSThread: 0x600000084b40>{number = 5, name = (null)}
    2019-03-05 14:56:36.803738+0800 text[1989:26916] 1----:<NSThread: 0x600000074200>{number = 3, name = (null)}
    2019-03-05 14:56:36.803826+0800 text[1989:26914] 2----:<NSThread: 0x600000076900>{number = 4, name = (null)}
    2019-03-05 14:56:36.803861+0800 text[1989:26978] 3----:<NSThread: 0x600000084b40>{number = 5, name = (null)}
    
    -(void)test9{
        //串行同步--可以看到是按顺输出的,是在同一个线程,但是没有开启新线程,是在主线程执行的
        NSLog(@"start");
        dispatch_queue_t queue = dispatch_queue_create("demo", DISPATCH_QUEUE_SERIAL);
        dispatch_sync(queue, ^{
            for (int i = 0; i < 2; i++) {
                NSLog(@"1----:%@",[NSThread currentThread]);
            }
        });
        dispatch_sync(queue, ^{
            for (int i = 0; i < 2; i++) {
                NSLog(@"2----:%@",[NSThread currentThread]);
            }
        });
        dispatch_sync(queue, ^{
            for (int i = 0; i < 2; i++) {
                NSLog(@"3----:%@",[NSThread currentThread]);
            }
        });
        NSLog(@"end");
    }
    2019-03-05 14:57:38.184850+0800 text[2026:27582] start
    2019-03-05 14:57:38.185092+0800 text[2026:27582] 1----:<NSThread: 0x600001069440>{number = 1, name = main}
    2019-03-05 14:57:38.185215+0800 text[2026:27582] 1----:<NSThread: 0x600001069440>{number = 1, name = main}
    2019-03-05 14:57:38.185318+0800 text[2026:27582] 2----:<NSThread: 0x600001069440>{number = 1, name = main}
    2019-03-05 14:57:38.185480+0800 text[2026:27582] 2----:<NSThread: 0x600001069440>{number = 1, name = main}
    2019-03-05 14:57:38.185587+0800 text[2026:27582] 3----:<NSThread: 0x600001069440>{number = 1, name = main}
    2019-03-05 14:57:38.185686+0800 text[2026:27582] 3----:<NSThread: 0x600001069440>{number = 1, name = main}
    2019-03-05 14:57:38.185791+0800 text[2026:27582] end
    
    -(void)test10{
        //串行异步--可以看到是按顺输出的,是在同一个线程,而且开启了新线程,
        NSLog(@"start");
        dispatch_queue_t queue = dispatch_queue_create("demo", DISPATCH_QUEUE_SERIAL);
        dispatch_async(queue, ^{
            for (int i = 0; i < 2; i++) {
                NSLog(@"1----:%@",[NSThread currentThread]);
            }
        });
        dispatch_async(queue, ^{
            for (int i = 0; i < 2; i++) {
                NSLog(@"2----:%@",[NSThread currentThread]);
            }
        });
        dispatch_async(queue, ^{
            for (int i = 0; i < 2; i++) {
                NSLog(@"3----:%@",[NSThread currentThread]);
            }
        });
        NSLog(@"end");
    }
    2019-03-05 14:58:50.021576+0800 text[2070:28513] start
    2019-03-05 14:58:50.021959+0800 text[2070:28513] end
    2019-03-05 14:58:50.022246+0800 text[2070:28543] 1----:<NSThread: 0x600002dc5480>{number = 3, name = (null)}
    2019-03-05 14:58:50.022493+0800 text[2070:28543] 1----:<NSThread: 0x600002dc5480>{number = 3, name = (null)}
    2019-03-05 14:58:50.022628+0800 text[2070:28543] 2----:<NSThread: 0x600002dc5480>{number = 3, name = (null)}
    2019-03-05 14:58:50.022752+0800 text[2070:28543] 2----:<NSThread: 0x600002dc5480>{number = 3, name = (null)}
    2019-03-05 14:58:50.022943+0800 text[2070:28543] 3----:<NSThread: 0x600002dc5480>{number = 3, name = (null)}
    2019-03-05 14:58:50.023092+0800 text[2070:28543] 3----:<NSThread: 0x600002dc5480>{number = 3, name = (null)}
    
    

    KVC,KVO

    KVC(Key-value coding)键值编码,我们可以通过以字符串为Key对对象属性进行操作。
    KVO(Key-Value-Observer)就是观察者模式。当一个对象被观察的属性发生变化时,观察者做出的处理


    分类(category)

    分类只能添加方法,不能添加属性
    本类和分类的话,分类优先于本类的方法


    类扩展(extension)

    可以用来给当前类添加属性和新方法


    协议 (protocol)

    协议是委托别人做自己想做的事情,可以用来传值,或者用来监听、通知。代理(delegate)


    深拷贝浅拷贝

    浅拷贝:就是拷贝后,并没有进行真正的复制,而是复制的对象和原对象都指向同一个地址
    深拷贝:是真正的复制了一份,复制的对象指向了新的地址

    浅拷贝好比你的影子,你死了,影子也没了;深拷贝好比克隆人,你死了,它还在。

    深拷贝和浅拷贝的本质是地址是否相同

    copy: 对于可变对象为深拷贝,对于不可变对象为浅拷贝
    mutableCopy:始终是深拷贝

    copy方法返回的对象都是不可变对象

    1118933-20170720203748536-592203434.png

    怎么定位突发bug

    对于致命的Bug,我们可以通过Crash日志进行分析,常用的有第三方友盟统计


    继承

    继承是类中的一个重要的特性,他的出现使得我们没必要别写重复的代码,可重用性很高


    苹果推送通知服务APNS

    客户端:
    客户端发送自身设备的 UDID 和 Bundle Identifier 给 APNs 服务器,经苹果服务器加密后生成 deviceToken,随后只需将用户的 deviceToken 发送服务器保存。
    服务器:
    服务器在需要给某个用户推送消息时,需要将消息内容和 deviceToken 一起发送给 APNs 服务器,苹果服务器对 deviceToken 解密后可以找到具体的设备,然后将消息推送给该用户。


    降低耦合

    耦合度的道理其实说起来很简单,就是模块之间相关联程度的度量,指模块与模块之间的关联性,所谓的低耦合就是将两个模块之间的关联性尽可能的降低,一个模块的改动对于其他模块的影响尽量小。
    这样的话看起来很明了,平时简单的功能做起来也不难,比如一些简单的低耦合技巧:给tableViewCell赋值的时候,如果有dataSource,那么有些人会在tableView的代理中从dataSource取出需要的数据来赋值给cell,这样就增大了主视图的代码,增大了cell和主视图的联系,这时候就可以改为将dataSource里面的Model赋值给cell并重写setModel方法来实现低耦合。
    1、少使用类的继承,多用接口隐藏实现的细节。
    2、多用设计模式,比如采用MVC的设计模式就可以降低界面与业务逻辑的耦合度。


    ARC基本原理

    ARC的规则就是只要对象没有强指针引用,就会被释放掉,换而言之 只要还有一个强引用指针变量指向对象,那么这个对象就会存在内存中。弱指针指向的对象,会被自动变成空指针(nil指针),从而不会引发野指针错误。

    ARC是Automatic Reference Counting(自动引用计数器)的简称

    当一个对象被持有的时候计数加一,不再被持有的时候引用计数减一,当引用计数为零的时候,说明这个对象已经无用了,则将其释放。


    内存溢出和内存泄露的区别

    内存溢出:out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如说你申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。
    内存泄露: memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
    memory leak会最终会导致out of memory!


    堆和栈的区别

    对于栈来讲,是由编译器自动管理,无需我们手工控制;
    对于堆来说,释放工作由程序员控制,容易产生内存泄露(memory leak)


    循环引用的问题

    系统自带的方法中的Block不会造成循环引用,比如UIView动画block、Masonry添加约束block、AFN网络请求回调block等;
    但我们在实际开发中,使用自定义Block,在Block { xxx }中使用self,容易导致了循环引用 ,
    循环引用导致的原因: 相互强指向;比如block为了保证代码块内部对象不被提前释放,会对block中的对象进行强引用,就相当于持有了其中的对象,而如果此时block中的对象又持有了该block,就会造成循环引用。


    TCP和UDP概念和区别

    TCP是传输控制协议,提供的是面向连接、可靠的字节流服务。当客户的服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。
    UDP是用户数据报协议,是一个简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序穿给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不用再客户的服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快。


    单例模式

    单例模式是一种常用的设计模式,对于一个单例类,必须保证任意时刻只有一个单例对象,并且自行实例化该对象,并向整个系统提供该对象,也就是说无论实例化单例对象多少次,都只能创建出一个对象,该对象是全局的能够整个系统所访问
    单例对象很像c中全局变量,单例类可以实现不同对象之间的数据共享

    //SingleClass.h文件
    #import <Foundation/Foundation.h>
    
    @interface SingleClass : NSObject
    
    @property (copy, nonatomic)NSString *name;
    
    + (SingleClass *)sharedSingleClass;
    
    @end
    
    #import "SingleClass.h"
    
    //1:创建一个全局静态的单例子对象指针,初始值为nil
    static SingleClass *single = nil;
    
    @implementation SingleClass
    + (SingleClass *)sharedSingleClass{
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            single = [[SingleClass alloc] init];
        });
        return single;
    }
    
    @end
    

    沙盒

    1,Documents/
    保存应用程序的重要数据文件和用户数据文件等。用户数据基本上都放在这个位置(例如从网上下载的图片或音乐文件),该文件夹在应用程序更新时会自动备份,在连接iTunes时也可以自动同步备份其中的数据。

    2,Library:
    这个目录下有两个子目录,可创建子文件夹。可以用来放置您希望被备份但不希望被用户看到的数据。该路径下的文件夹,除Caches以外,都会被iTunes备份.
    Library/Caches:
    保存应用程序使用时产生的支持文件和缓存文件(保存应用程序再次启动过程中需要的信息),还有日志文件最好也放在这个目录。iTunes 同步时不会备份该目录并且可能被其他工具清理掉其中的数据。
    Library/Preferences:
    保存应用程序的偏好设置文件。NSUserDefaults类创建的数据和plist文件都放在这里。会被iTunes备份。

    3,tmp/:
    保存应用运行时所需要的临时数据。不会被iTunes备份。iPhone重启时,会被清空。

    相关文章

      网友评论

          本文标题:面试题

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