Vickate_iOS面试点积累

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:存放临时文件,不会被备份,而且这个文件下的数据有可能随时被清除的可能

Vickate_iOS面试点积累_第1张图片
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  {
    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 

@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面试点积累)