美文网首页
Vickate_iOS面试点积累

Vickate_iOS面试点积累

作者: Vickate | 来源:发表于2017-05-19 14:13 被阅读0次

    BEGAN

    1、如何正确使用const,static, extern?

    作用:
    const:限制类型(1、仅仅用来修饰右边的变量;2、被 const 修饰的变量只能是只读的)
    static:
    1、修饰局部变量:
    1)延长局部变量的生命周期,程序结束才会销毁;
    2)局部变量只能生成一份内存,只会初始化一次;
    3)改变局部变量的作用域;
    2、修饰全局变量:
    1)只能在本文件中访问,修改全局变量的作用域,生命周期不会改;
    2)避免重复定义全局变量
    extern:用来获取全局变量的值(包括静态的全局变量)

    延伸:
    1、const 与宏的区别?
    1)编译时刻:宏是预编译,const 是编译阶段
    2)编译检查:宏只是替换,在编译时不会报错,不检查,const 会编译检查,会报错
    3)宏的好处:宏能定义一些函数、方法,const 不能
    4)宏的坏处:大量的宏会导致编译时间变长,每次都要重新替换

    2、const 与 static 配合使用
    在多个文件中使用同一个字符串常量,可以使用 const 与 static 组合;
    const 与 static 组合:在每个文件都需要定义一份静态全局变量
    extern 与 static 组合:只需要定义一次全局变量,多个文件共享

    2、@property 关键字详解,assign 与weak、 __block 与 __weak、strong 与copy的区别?

    1、assign 与 weak 区别:
    assign 适用于基本数据类型,基础数据类型一般分配在栈上,栈的内存用系统自动处理。
    weak 适用于修饰 NSObject 对象,是一个弱引用。一般修饰代理和 IB 控件。

    2、strong 与copy的区别:
    strong 与 copy 都会使引用计数加1,但是 strong 是两个指针指向统一个内存地址,copy 会在内存里拷贝一份对象,两个指针指向不用的内存地址。

    3、__weak与__block的区别:
    1)使用 __block修饰的变量在block代码块中会被retain(ARC下会retain,MRC下不会retain)
    2)使用__weak修饰的变量不会在block代码块中被retain
    要避免block出现循环引用 __weak __typedof(&*self)weakSelf = self;

    4、block变量定义时为什么用copy?block 是放在哪里的?
    block本身是像对象一样可以retain,和release。但是,block在创建的时候,它的内存是分配在栈(stack)上,可能被随时回收,而不是在堆(heap)上。通过copy可以把block拷贝(copy)到堆,保证block的声明域外使用

    3、一个按钮被一个半透明的View部分遮挡,需要点击到按钮的时候,按钮始终响应
    // 一个View超出了父视图的范围,需要点击超出范围的View也有响应
    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
        //当触摸点在按钮上的时候,才让按钮去响应事件.
        //把当前点转换成按钮坐标系上的点.
        CGPoint btnP =  [self convertPoint:point toView:self.btn];
        if ( [self.btn pointInside:btnP withEvent:event]) {
            return self.btn;
        }else{
            return [super hitTest:point withEvent:event];
        }
    }
    
    4、iOS 的沙盒目录结构是怎样的?

    Application:存放程序源文件,上架前经过数字签名,上架后不可修改
    Documents:常用目录,iCloud备份目录,存放数据,这里不能存缓存文件,否则上架不被通过
    Library :
    Caches:存放体积大又不需要备份的数据,SDWebImage缓存路径就是这个
    Preference:设置目录,iCloud会备份设置信息
    tmp:存放临时文件,不会被备份,而且这个文件下的数据有可能随时被清除的可能

    1049769-7507c548ecd32f0a.png
    5、+load 和 +initialize 的区别是什么?

    +(void)load;
    当类对象被引入项目时, runtime 会向每一个类对象发送 load 消息 load 方法会在每一个类甚至分类被引入时仅调用一次,调用的顺序:父类优先于子类, 子类优先于分类
    load 方法不会被类自动继承
    +(void)initialize;
    也是在第一次使用这个类的时候会调用这个方法

    6、如何让 Category 支持属性? runtime实现

    头文件

    @interface NSObject (test)
    @property (nonatomic, copy) NSString *name;
    @end
    

    .m文件

    @implementation NSObject (test)
    // 定义关联的key
    static const char *key = "name";
    - (NSString *)name {
        // 根据关联的key,获取关联的值。
        return objc_getAssociatedObject(self, key);
    }
    - (void)setName:(NSString *)name {
        // 第一个参数:给哪个对象添加关联
        // 第二个参数:关联的key,通过这个key获取
        // 第三个参数:关联的value
        // 第四个参数:关联的策略
        objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    7、strong / weak / unsafe_unretained 的区别?

    • weak只能修饰OC对象,使用weak不会使计数器加1,对象销毁时修饰的对象会指向nil
    • strong等价与retain,能使计数器加1,且不能用来修饰数据类型
    • unsafe_unretained等价与assign,可以用来修饰数据类型和OC对象,但是不会使计数器加1,且对象销毁时也不会将对象指向nil,容易造成野指针错误

    8、frame 和 bounds 的区别是什么?

    (1)frame相对于父视图,是父视图坐标系下的位置和大小。bounds相对于自身,是自身坐标系下的位置和大小。
    (2)frame以父控件的左上角为坐标原点,bounds以自身的左上角为坐标原点

    9、KVO与Notification的异同

    KVO和Notification本质都是观察者模式。
    KVO是被观察者直接发消息(-willChange和-didChange),耦合性较强,适合某些绑定,比如说界面上的进度条显示;
    Notification是被观察者发消息给NotificationCenter,再由NotificationCenter转发出去,耦合性较低,适合登录、等级变化、监听全局的某个属性变化;

    10、Objective-C消息机制的原理

    Objective-C的类结构

    @interface NSObject <NSObject> {
        Class isa  OBJC_ISA_AVAILABILITY;
    }
    typedef struct objc_class *Class;
    

    objc_msgSend方法:objc_msgSend含两个必要参数:receiver、方法名(selector)
    [receiver message];将被转换为:objc_msgSend(receiver, selector);
    带参数的情况是:objc_msgSend(receiver, selector, arg1, arg2, …);
    当向一个对象发送消息时,objc_msgSend方法根据对象的isa指针找到对象的原来类,然后在类的方法列表中查找selector;
    如果查找不到,通过Class super_class指针找到父类,并在父类的方法列表查找,直到NSObject类;

    11、数据的持久化

    ios中存储数据基本上就是plist、sqlite和CoreData (NSUserDefault其实也是plist)
    常见的持久化实现:
    1)实现NSCoding,配合runtime读取属性,再用NSKeyedArchiver存储到文件中;
    2)实现NSCoding,存储到NSUserDefault;
    3)数据库,使用SQLitePersistentObjects写入db;
    4)使用CoreData;

    12、聊天室中UITableView的优化

    每一条消息是单独的UITableViewCell,通过富文本显示聊天消息,耗时操作是:富文本拼接、高度计算、滚动显示
    业务方向:
    (1)下发房间配置文件,房间分普通、热闹、火爆等状态,某些情况下省略不必要的消息,再进行发言等级控制等;
    (2)消息合并,对同类型的消息进行合并;

    代码方向:
    (1)根据帧率动态加载消息数量,当进行消息追赶的时候,多条消息调用一次insert,用CADisplayLink保证添加速率和帧率一致;
    (2)代码创建cell
    (3)图像预加载,程序在启动的时候会进行礼物版本同步,把礼物图片预先下载好,在显示直接通过富文本进行图片拼接;(为了避免锯齿,图像大小和显示使用整数)
    (4)富文本根据消息内容进行拼接后缓存;

    13、TCP/IP

    (1)3次握手-建立连接
    1、A发送sync报文;seq=x Sync=1
    2、B回复ack报文;seq=y Sync=1 ack=x+1
    3、A回复ack报文;seq=x+1 Sync=1 ack=y+1

    (2)4次握手-断开连接
    1、A端发送FIN,停止发送报文;A进入FIN-WAIT
    2、B端发送ACK,表示收到,继续发送报文; A收到报文进入FIN2-WAIT
    3、B端发送FIN,停止发送报文;B进入CLOSE_WAIT
    4、A端收到FIN,发送ACK报文,A进入TIME_WAIT状态

    14、HTTP协议

    http(超文本传输协议)是一个基于请求与响应模式的、无状态的、应用层的协议,常基于TCP的连接方式
    http请求由三部分组成,分别是:请求行、消息报头、请求正文

    常见状态码:
    200 成功
    400 请求的语法错误
    403 Forbidden
    404 not found 服务器找不到请求的资源
    408 Request Time out
    500 服务器内部错误

    请求头
    GET 请求方法、地址、协议版本
    GET /foo.php?first_name=John&last_name=Doe&action=Submit HTTP/1.1

    请求体(POST请求有)
    form-data

    HTTP响应
    HTTP响应也是由三个部分组成,分别是:状态行、消息报头、响应正文

    15、线程安全问题

    线程之间的资源共享,本质是对同一对象、变量、文件等进行修改和访问,主要有以下同步方式:
    加锁;
    原子操作;
    sync代码块;

    @synchronized( 同一对象){
      // 线程执行代码;
      }
    

    NSOperationQueue 可以停止队列还没执行
    suspended
    但是不能终止当前操作。

    16、NSOperation 相比 GCD 有哪些优势

    (1)更容易的添加依赖关系;
    (2)提供了任务的状态:isExecuteing、isFinished;
    (3)可以很方便的取消一个NSOperation的执行。

    17、如何为 Class 定义一个对外只读对内可读写的属性?

    在头文件申明属性的时候用 readonly, 在.m 文件重新申明属性的时候用 readwrite

    18、UIView 和 CALayer 之间的关系?

    (1)UIView 继承自 UIResponder,可以响应事件,CALayer不可以响应用户事件;
    (2)UIView本身,更像是一个CALayer的管理器,访问它的根绘图和坐标有关的属性,如frame,bounds等,实际上内部都是访问它所在CALayer的相关属性。

    19、创建一个单例
    + (XYHundle *)shareHaudle {
        static XYHundle *shareHaudle = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            shareHaudle = [[XYHundle alloc] init];
        });
        return shareHaudle;
    }
    
    20、冒泡排序(或其他算法)
    NSMutableArray *array = [NSMutableArray arrayWithObjects:@"10", @"2", @"22", @"14", @"18", nil];
        NSLog(@"打印前的数组:%@", array);
        for (int i = 0; i < array.count - 1; i++) {
            for (int j = 0; j < array.count - 1 - i; j++) {
                if ([array[j + 1] intValue] < [array[j] intValue]) {
                    int temp = [array[i] intValue];
                    temp = [array[j] intValue];
                    array[j] = array[j + 1];
                    array[j + 1] =  [NSString stringWithFormat:@"%d",temp];
                }
            }
        }
        NSLog(@"打印后的数组:%@", array);
    
    21、哪些途径可以让 ViewController 瘦下来?(MVVM 思想)

    (1)把 Data Source 和其他 Protocols 分离出来(将UITableView或者UICollectionView的代码提取出来放在其他类中)
    (2)将业务逻辑移到 Model 中(和模型有关的逻辑全部在model中写)
    (3)把网络请求逻辑移到 Model 层(网络请求依靠模型)
    (4)把 View 代码移到 View 层(自定义View)

    22、有哪些常见的 Crash 场景?

    (1)访问了僵尸对象
    (2)访问不存在的方法
    (3)数组越界
    (4)在定时器下一次回调前将定时器释放,会Crash

    23、如果一个函数10次中有7次正确,3次错误,问题可能出现在哪里?

    1.首先既然有正确有错误,那么这个bug肯定是不一定会出错的,先看函数条件是否有漏写;
    2.然后再检查函数是否会存在空的情况;
    3.反复操作以上步骤去查明每个调用的函数结果都是正确的。

    24、iOS开发中Debug和Release的区别

    Debug : 调试版本,主要是让程序员使用,在调试的过程中调用 Debug 会启动更多的服务来监控错误,运行速度相对较慢,而且比较耗能.

    Release : 发布版本,主要是让用户使用, 在使用的过程中会去掉那些繁琐的监控服务,运行速度相对较快,而且比较节约内存.

    25、请写一个”标准"宏MIN ,这个宏输入两个参数并返回较小的一个。
     #define MIN(A,B) ((A) <= (B) ? (A) : (B))
    
    26、请分别说明@public、@protected、@private的含义与作用

    @public:对象的实例变量的作用域在任意地方都可以被访问 ;
    @protected:对象的实例变量作用域在本类和子类都可以被访问 ;
    @private:实例变量的作用域只能在本类(自身)中访问 。

    27、@synthesize、@dynamic的区别

    @synthesize是系统自动生成getter和setter属性声明;
    @synthesize的意思是,除非开发人员已经做了,否则由编译器生成相应的代码,以满足属性声明;

    @dynamic是开发者自已提供相应的属性声明;
    @dynamic意思是由开发人员提供相应的代码:对于只读属性需要提供getter,对于读写属性需要提供 setter 和getter。

    28、block使用时的注意点?

    1.在block内部使用外部指针且会造成循环引用情况下,需要用weak修饰外部指针weak typeof(self) weakSelf = self;

    2.在block内部如果调用了延时函数还使用弱指针会取不到该指针,因为已经被销毁了,需要在block内部再将弱指针重新强引用一下__strong typeof(self) strongSelf = weakSelf;

    3.如果需要在block内部改变外部变量的话,需要在用__block修饰外部变量

    举个🌰

    // 创建一个 Student 类
    #import <Foundation/Foundation.h>
    
    @interface Student : NSObject
    
    @property(nonatomic, strong) NSString *name;
    
    @end
    
    //  Student.m
    
    #import "Student.h"
    
    @implementation Student
    
    @end
    

    随便在一个函数中调用,例如下面的函数中:

    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
        Student *student = [[Student alloc] init];
        student.name = @"Tom";
        __weak typeof(Student) *weakSelf = student;
        void(^block)()=^{
            NSLog(@"Student Name = %@",weakSelf.name);
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                NSLog(@"Student Name(Delay) = %@",weakSelf.name);
            });
        };
        block();
    }
    

    打印出来的信息:

    2017-02-23 10:55:03.876 TestBlock[1904:336958] Student Name = Tom
    2017-02-23 10:55:05.876 TestBlock[1904:337002] Student Name(Delay) = (null)
    

    dispatch_after函数在viewDidLoad函数结束后执行,此时student对象已经被销毁,所以weakSelf所引用的内容已经不存在,所以取得不到Student Name。

    因此对于Block内部的延时函数,为了保证延时之后Block所引用的对象还存在,需要用__strongSelf引用。上面的代码修改为:

    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
        Student *student = [[Student alloc] init];
        student.name = @"Tom";
        __weak typeof(Student) *weakSelf = student;
        void(^block)()=^{
            // 需要强引用下
            __strong typeof(Student) *strongSelf = weakSelf;
            NSLog(@"Student Name = %@",strongSelf.name);
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                NSLog(@"Student Name(Delay) = %@",strongSelf.name);
            });
        };
    
        block();
    }
    
    
    29、[@property (nonatomic, copy) NSString *name; 重写 setter 方法]
    - (void)setName:(NSString *)name {
        _name = [name copy];
    }
    

    [@property (nonatomic, retain) NSString *name; 重写 setter 方法]

    – (void) setName:(NSString*) str {
    [str retain];
    [name release];
    name = str;
    }
    
    ——————————底层实现————————————
    1、字典转模型之MJExtension底层实现
    //返回一个创建好的模型
    + (instancetype)modelWithDict:(NSDictionary *)dict
    {
        //创建一个模型
        id objc = [[self alloc] init];
        int count = 0;
        /*
         方法:获取成员变量列表
         参数一:class获取哪个类成员变量列表
         参数二:count成员变量总数
         */
        // 成员变量数组 指向数组第0个元素
        Ivar *ivarList = class_copyIvarList(self, &count);
    
        // 遍历所有成员变量
        for (int i = 0; i < count; i++) {
            // 获取成员变量
            Ivar ivar = ivarList[i];
            // 获取成员变量名称(将c转为oc)
            NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
            // 成员变量名称转换key(将成员变量前边的"_"截取掉)
            NSString *key = [ivarName substringFromIndex:1];
    
            // 从字典中取出对应value
            id value = dict[key];
    
            // 给模型中属性赋值(底层会去找对应的属性和值)
            [objc setValue:value forKey:key];
        }
        return objc;
    }
    
    2、无限轮播实现原理
    UIScrollView

    1、准备好一个UIScrollView,上面依次放上去三个UIImageView,简称为A,B,C。
    2、假设我们要放4张图片,那么首先我们在A B C上依次放的是:A -> 最后一张图片, B -> 第一张图片, C -> 第二张图片。首先让ScrollView显示B也就是第一张图片。
    (1)当我们左滑显示第二张图片也就是C,当页面显示滑动停止以后,我们将A B C重新显示图片为:A -> 第一张图片, B -> 第二张图片, C -> 第三张图片,然后让ScrollView再次显示B。所以界面显示的图片虽然变了,但一直显示的还是中间那个UIImageView。这样就造成了无限循环滑动。右滑原理一样。
    (2)如果我们右滑显示最后一张图片也就是A,当页面显示滑动停止以后,我们将A B C重新显示图片为:A -> 倒数第二张图片, B -> 最后一张图片, C -> 第一张图片,然后让ScrollView再次显示B。这样子就是一个无限循环轮播。

    func reloadImage() {
        let currentIndex = pageView.currentPage
        let nextIndex = (currentIndex + 1) % 4
        let preIndex = (currentIndex + 3) % 4
    
        (scrollView.subviews[0] as! UIImageView).image = UIImage(named: "\(preIndex).png")
        (scrollView.subviews[1] as! UIImageView).image = UIImage(named: "\(currentIndex).png")
        (scrollView.subviews[2] as! UIImageView).image = UIImage(named: "\(nextIndex).png")
    }
    
    UICollectionView

    1、设置UICollectionView的cell为两倍image数量,然后循环对cell从第一张图片到最后一张图片赋值,首先显示第二部分的第一张图片。
    2、当左滑显示到第二部分的最后一张图片的时候,也就是最后一个cell,当图片显示完成以后,我们将scrollView设置为显示第一部分的最后一张图片,这样子就可以继续右滑,就是无限循环轮播的效果。
    3、当右滑显示到第一部分的第一张图片的时候,也就是第一个cell,当图片显示完成以后,我们将scrollView设置为显示第二部分的第一张图片,这样子就可以继续左滑,就是无限循环轮播的效果。

    func reloadImage() {
        guard let currentIndexPath = currentIndexPath else {
            return
        }
        if currentIndexPath.item == images.count * 2 - 1 {  //如果是最后一个图片,回到第一部分的最后一张图片
            let newIndexPath = IndexPath(item: images.count - 1, section: 0)
            self.currentIndexPath = newIndexPath
            collectionView.scrollToItem(at: newIndexPath, at: .centeredHorizontally, animated: false)
        } else if currentIndexPath.item == 0 {  //如果是第一个图片,就回到第二部分的第一张图片
            let newIndexPath = IndexPath(item: images.count, section: 0)
            self.currentIndexPath = newIndexPath
            collectionView.scrollToItem(at: newIndexPath, at: .centeredHorizontally, animated: false)
        }
    }
    
    后续持续更新~

    END

    相关文章

      网友评论

          本文标题:Vickate_iOS面试点积累

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