iOS 面试题

2018-03-20  本文已影响42人  名字太多不会起

1.介绍下内存的几大区域?

  1. 栈区(stack) 由编译器自动分配并释放,存放函数的参数值,局部变量等。栈是系统数据结构,对应线程/进程是唯一的
    优点是快速高效,缺点时有限制,数据不灵活。[先进后出]
  2. 堆区(heap) 由程序员分配和释放,如果程序员不释放,程序结束时,可能会由操作系统回收 ,比如在ios 中 alloc 都是存放在堆中
    优点是灵活方便,数据适应面广泛,但是效率有一定降低[顺序随意]
  3. 全局区(静态区) (static) 全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量存放在一块区域,未初始化的全局变量和静态变量在相邻的另一块区域,程序结束后有系统释放
    全局区又可分为未初始化全局区: .bss段和初始化全局区:data段。

BSS段:BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。

main{
   int b; 栈区
   char s[] = "abc" 栈
   char *p1; 栈 
   char *p2 = "123456";  123456\\\\0在常量区,p2在栈上。
   static int c =0; 全局(静态)初始化区 
   w1 = (char *)malloc(10); 
   w2 = (char *)malloc(20); 
   分配得来得10和20字节的区域就在堆区。 
 }
内存示意图.png
  1. 常量区 存放常量字符串,程序结束后由系统释放
  2. 代码区 存放函数的二进制代码

2.什么情况使用 weak 关键字,相比 assign 有什么不同?

  1. 在ARC模式下,在有可能出现循环引用时,让其一端使用weak修饰。例如:delegate(代理)属性
  2. 自身已经对它强引用一次了,没有必再强引用一次使用weak解决。例如:自定义IBOutlet控件属性
  1. weak只能用于修饰对象类型,基本数据类型不能使用
  2. assign修饰对象和基本数据类型都可以,但是只是简单地进行赋值操作而已
    注意:assign修饰的对象在释放之后,指针的地址还是存在的,也就是说指针并没有被置为nil,造成野指针。对象一般分配在堆上的某块内存,如果在后续的内存分配中,刚好分到了这块地址,程序就会崩溃掉。 那为什么可以用assign修饰基本数据类型?因为基础数据类型一般分配在栈上,栈的内存会由系统自己自动处理,不会造成野指针。 weak修饰的对象在释放之后,指针地址会被置为nil。所以现在一般弱引用就是用weak。

3.属性关键字 readwrite,readonly,assign,retain,copy,nonatomic 各是什么作用,在那种情况下用

那什么是原子性和非原子性呢?

首先,我们要理解什么叫原子操作,原子操作可以理解为:在多线程操作同一对象时,在非程序代码加锁状况下,保证被操作对象是结果是符合预期的。
之前说过滴,具备atomic特质的获取方法会通过锁定机制来确保其操作的原子性。
也就是说,如果两个线程同时读取一个属性,那么不论何时,总能看到有效的属性值。
如果不加锁的话(或者说使用nonatomic语义),那么当其中一个线程正在改写某属性值的时候,另外一个线程也许会突然闯入,把尚未修改好的属性值读取出来。发证这种情况时,线程读取道德属性值肯能不对。

一般iOS程序中,所有属性都声明为nonatomic。这样做的原因是:
在iOS中使用同步锁的开销比较大, 这会带来性能问题。一般情况下并不要求属性必须是“原子的”,因为这并不能保证“线程安全”(thread safety),若要实现“线程安全”的操作,还需采用更为深层的锁定机制才醒。
例如:一个线程在连续多次读取某个属性值的过程中有别的线程在同时改写该值,那么即便将属性声明为atomic,也还是会读取到不同的属性值。
因此,iOS程序一般都会使用nonatomic属性。但是在Mac OS X程序时, 使用atomic属性通常都不会有性能瓶颈

4.@synthesize 和 @dynamic 分别有什么作用?

5.代理和Block的区别

6.浅拷贝和深拷贝的区别?

判断是浅拷贝和深拷贝就看一下两个变量的内存地址是否一样,一样就是浅拷贝,不一样就是深拷贝,也可以改变一个变量的其中一个属性值看两者的值都会发生变化;

7 系统对象的 copy 与 mutableCopy 方法有何区别copy与mutableCopy方法

8.设计模式是什么? 你知道哪些设计模式,并简要叙述?

设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一种类型的事情。

  1. MVC模式:Model View Control,把模型 视图 控制器 层进行解耦合编写。
  2. MVVM模式:Model View ViewModel 把模型 视图 业务逻辑 层进行解耦和编写。
  3. 单例模式:通过static关键词,声明全局变量。在整个进程运行期间只会被赋值一次。
  4. 观察者模式:KVO是典型的通知模式,观察某个属性的状态,状态发生变化时通知观察者。
  5. 委托模式:代理+协议的组合。实现1对1的反向传值操作。
  6. 工厂模式:通过一个类方法,批量的根据已有模板生产对象。

先重点解释一下什么是工厂模式

  1. 何为工厂模式?
    工厂模式可以简单概括为同类型不同型号的产品有各自对应的工厂进行生产。好比如iPhone手机旗下有iPhoneX及iPhone8两种型号的手机,iPhoneX有自己iPhoneX的专属工厂进行生产,而iPhone8有自己iPhone8的专属工厂进行生产。两条生产线没有交集互不影响,后期如果新增或废弃相关类型的产品也不会影响到原有的生产线。
    工厂模式也称为虚构造器,它适用于:一个类无法预期生成那个类对象,想让其子类来指定所生成具体对象。
  2. 何时使用工厂模式
  1. 案例
    我们常见的UITableView列表包含了各种不同的UI展示的Cell。我们在其相关的代理方法实现可能是这样的:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    if (indexPath.row == 0) {
         
        WCQAStyleTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"WCQAStyleTableViewCell"]];
        [cell configUI:nil];  //不同样式的Cell所展示的UI各不相同,由于是Demo样例,这里并未配置相关数据源
        return cell;
    }else if (indexPath.row == 1) {
         
        WCQBStyleTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"WCQBStyleTableViewCell"]];
        [cell configUI:nil];
        return cell;
    }else if (indexPath.row == 2) {
         
        WCQCStyleTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"WCQCStyleTableViewCell"]];
        [cell configUI:nil];
        return cell;
    }
}

随着需求的迭代,我们后期可能会增加更多样式的Cell,此时我们就需要变更这部分的代码。增加与之对应的else if条件判断,代码将变得越来越长。同时,在更加复杂的场景下,我们并不能保证调整原有代码以后,之前功能不受影响。我们将投入更多的时间进行原有代码功能的回归,这是我们不希望发生的。
我们发现上述例子都是通过不同样式的Cell进行不同的UI展示,就好比如我们用不同型号的iPhone进行拍照。我们可以将上述例子调整为工厂模式,看看其能为我们带什么样的效果。

  1. 将各种展示功能相似的Cell抽象为WCQBaseTableViewCell(继承于UITableViewCell),WCQTableViewCell提供默认展示UI的方法configUI

  2. 其他多种的Cell继承于WCQBaseTableViewCell,根据其特定的需求对configUI方法进覆写,这里我们以WCQAStyleTableViewCell为例:

WCQAStyleTableViewCell.h
#import "WCQBaseTableViewCell.h"
@interface WCQAStyleTableViewCell : WCQBaseTableViewCell
@end
WCQAStyleTableViewCell.m
#import "WCQAStyleTableViewCell.h"
@implementation WCQAStyleTableViewCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
     
    if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
         
        //UI
    }
    return self;
}
- (void)awakeFromNib {
    [super awakeFromNib];
    // Initialization code
}
- (void)configUI:(id)model {
     
    self.textLabel.text = NSStringFromClass([self class]);
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
    [super setSelected:selected animated:animated];
    // Configure the view for the selected state
}
@end
  1. 对之前UITableView代理方法进行调整
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    WCQBaseTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:self.cellIdentifiers[indexPath.row]];
    [cell configUI:nil];
    return cell;
}

这里可能很多同学会问,说好的工厂模式怎么只有产品工厂没了呢?其实这里

[tableView dequeueReusableCellWithIdentifier:self.cellIdentifiers[indexPath.row]]

方法即为一个工厂。该方法在调用时会执行各种样式Cell的工厂方法并返回一个具体产品

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
     
    if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
         
        //UI
    }
    return self;
}

我们能看到在使用了工厂模式之后,最直观的感受是代码量明显减少了,也并未依赖具体的实现类。维护的代码越少其BUG产生的概率也越低。而且,即便是后期有新样式Cell的增加,我们也可以在基本不修改原来客户端的代码来进行样式的增加,我们只需要增加对应Cell的实现类以及对新增样式Cell的注册即可。对应Cell的初始化还是UI展示都封装进了各自的工厂中,互不影响。

Cell注册代码示例:
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
     
    UITableView *tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
    for (NSInteger index = 0; index < self.cellIdentifiers.count; index++) {
         
        [tableView registerClass:self.cellClasses[index]
          forCellReuseIdentifier:self.cellIdentifiers[index]];
    }
    tableView.delegate = self;
    tableView.dataSource = self;
    [self.view addSubview:tableView];
}
#pragma mark - Getter
- (NSArray *)cellIdentifiers {
     
    if (!_cellIdentifiers) {
         
        _cellIdentifiers = @[@"WCQAStyleTableViewCell",
                             @"WCQBStyleTableViewCell",
                             @"WCQCStyleTableViewCell"];
    }
    return _cellIdentifiers;
}
- (NSArray *)cellClasses {
     
    if (!_cellClasses) {
         
        _cellClasses = @[[WCQAStyleTableViewCell class],
                         [WCQBStyleTableViewCell class],
                         [WCQCStyleTableViewCell class]];;
    }
    return _cellClasses;
}
  1. 总结
    工厂模式总体在同一类型差异性较小的子类之间以抽象基类作为其返回类型来适应未来新增产品的动态调整,由于具有同样的接口,我们可以在新增产品类型时尽可能保障原有客户端代码逻辑的稳定性。同时,由于各自类型的产品的初始化方案都已隔离进各自的工厂方法中,避免了牵一发而动其身的尴尬境地。
最后感谢

https://www.jianshu.com/p/7fbbf5e3ba6b
https://www.cnblogs.com/bossren/p/6401067.html
http://blog.csdn.net/tinylight/article/details/38903865
https://www.jianshu.com/p/3b07f92d44ca
https://www.jianshu.com/p/f3c1b920e8eb
https://www.cnblogs.com/ydhliphonedev/archive/2012/04/27/2473927.html

上一篇 下一篇

猜你喜欢

热点阅读