IOS面试总结

基础
  • 为什么说Objective-C是一门动态的语言?
个人理解:这里拿java和oc来做对比、其实简单来说就是java 从.java变成.class的过程属于编译的过程,变为.class之后就不能更改了。只能从.java重新编译。相对于java,oc是可以通过runtime重新在运行时的时候改变方法顺序的,而java不能。。所以java是静态语言而oc是动态语言
  • 讲一下MVC和MVVM,MVP?
    一、MVC模式
IOS面试总结_第1张图片
MVC
1、视图(View):用户界面。
2、控制器(Controller):业务逻辑
3、模型(Model):数据保存

1、View 传送指令到 Controller
2、Controller 完成业务逻辑后,要求 Model 改变状态
3、Model 将新的数据发送到 View,用户得到反馈

注:MVC模式通讯都是单方向的

二、MVP模式

IOS面试总结_第2张图片
MVP模式
1. 各部分之间的通信,都是双向的。
2. View 与 Model 不发生联系,都通过 Presenter 传递。
3. View 非常薄,不部署任何业务逻辑,称为"被动视图"(Passive View),即没有任何主动性,而 Presenter非常厚,所有逻辑都部署在那里。

三、MVVM模式

IOS面试总结_第3张图片
MVVM模式
1. 各部分之间的通信,都是双向的。
2. View 与 Model 不发生联系,都通过ViewModel 传递。
3. View 非常薄,不部署任何业务逻辑,称为"被动视图"(Passive View),即没有任何主动性,而ViewModel非常厚,所有逻辑都部署在那里(包括原本在VC里面的网络请求)
  • 为什么代理要用weak?代理的delegate和dataSource有什么区别?block和代理的区别?
    首先是第一个问题为什么代理要用weak?
    例子:为什么代理要用weak
    下面是个人理解:为什么要用weak,用四个字来形容就是“循环引用”,为什么会造成循环引用的原因就是该释放的没被释放,为什么没被释放因为引用计数没有为0,为什么没有为0因为使用了strong.
例子:
a 引用b 
b引用c
c引用b
这时候b和c的引用计数分别是2和1。当a不再使用b,调用release释放对b的所有权,因为c还引用了b,所以b的引用计数为1,b不会被释放。b不释放,c的引用计数就是1,c也不会被释放。从此,b和c永远留在内存中,造成内存浪费。这种情况,必须打断循环引用,通过其他规则来维护引用关系。那么这个时候无论是b或者c谁使用weak都会打破这个僵局。b引用c,c使用弱引用就会先释放b,因为程序为了避免野指针也会先释放指针指向的对象,然后再释放本身,反之c引用b,b使用弱引用也会先释放c,同样的道理
  • 第二个问题:代理的delegate和dataSource有什么区别?
首先Delegate是委托的意思,在oc中则是一个类委托另一个类实现某个方法。当一个对象接受到某个事件或者通知的时候, 会向它的Delegate对象查询它是否能够响应这个事件或者通知,如果可以这个对象就会给它的Delegate对象发送一个消息(执行一个方法调用)。
Datasource字面是数据源,一般和Delegate伴生,这时数据源处理的数据就是Delegate中发送委托的类中的数据,并通过Datasource发送给接受委托的类。

第三个问题:block和代理的区别?

书写简易程度: block>代理
代理:它的本质就是对象的地址调用对象的方法,A写代理在B中实现, A.delegate 就是 控制器的地址 A.delegate = self 就是获取到控制器对象的地址。
block:它是一个函数,效率更高。
  • 属性的实质是什么?包括哪几个部分?属性默认的关键字都有哪些?@dynamic关键字和@synthesize关键字是用来做什么的
    第一个问题:属性的本质是什么 包括哪几部分?
@property = ivar(实例变量) + getter + setter;
  • 第二个问题:属性默认的关键字都有哪些?@dynamic关键字和@synthesize关键字是用来做什么的
atomic: 

原子操作(原子性是指事务的一个完整操作,操作成功就提交,反之就回滚. 原子操作就是指具有原子性的操作)在objective-c 属性设置里面 默认的就是atomic ,意思就是 setter /getter函数是一个原子操作,如果多线程同时调用setter时,不会出现某一个线程执行完setter所有语句之前,另一个线程就开始执行setter,相当于 函数头尾加了锁 . 这样的话 并发访问性能会比较低 .

nonatomic:

非原子操作 一般不需要多线程支持的时候就用它,这样在 并发访问的时候效率会比较高 . 在objective-c里面通常对象类型都应该声明为非原子性的. iOS中程序启动的时候系统只会自动生成一个单一的主线程.程序在执行的时候一般情况下是在同一个线程里面对一个属性进行操作. 如果在程序中 我们确定某一个属性会在多线程中被使用,并且需要做数据同步,就必须设置成原子性的,但也可以设置成非原子性的,然后自己在程序中用加锁之类的来做数据同步.

在头文件中声明属性的时候使用atomic 和 nonatomic等价于在头文件里面添加2个函数一个是用于设置这个属性的,一个是用于读取这个属性,例如:- (nsstring *)name; - (void)setName:(NSString *)str;

atomic / nonatomic 需要和@synthesize/@dynamic配和使用才有意义.

@synthesize

如果没有实现setter和getter方法,编译器将会自动在生产setter和getter方法。

@dynamic

表示变量对应的属性访问器方法 , 是动态实 现的 , 你需要在 NSObject 中继承而来的 +(BOOL) resolveInstanceMethod:(SEL) sel 方法中指定 动态实现的方法或者函数。

属性修饰其他关键字:

getter=getterName

指定 get 方法,并需要实现这个方法 。必须返回与声明类型相同的变量,没有参数

setter=setterName

指定 set 方法,并需要实现这个方法 。带一个与声明类型相同的参数,没有返回值(返回空值)

当声明为 readonly 的时候,不能指定 set 方法

readwrite

如果没有声明成 readonly ,那就 默认是 readwrite 。可以用来赋值,也可以被赋值

readonly

不可以被赋值

assign

所有属性都 默认 assign ,通常用于标量(简单变量 int , float , CGRect 等)

一种典型情况是用在对对象没有所有权的时候,通常是 delegate ,避免造成死循环(如果用 retain 的话会死循环)

retain

属性必须是 objc 对象,拥有对象所有权,必须在 dealloc 中 release 一次。

copy

属性必须是 objc 对象,拥有对象所有权,必须在 dealloc 中 release 一次。且属性必须实现 NSCopying 协议

一般常用于 NSString 类型
  • 第三个问题:属性的默认关键字是什么?
    看看这篇文章就都懂了:@property知识
  • 第四个问题:NSString为什么要用copy关键字,如果用strong会有什么问题?(注意:这里没有说用strong就一定不行。使用copy和strong是看情况而定的)
  1、因为父类指针可以指向子类对象,使用copy的目的是为了让本对象的属性不受外界影响,使用copy无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本.

  2、如果我们使用是strong,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性.
  • 第五个问题:如何令自己所写的对象具有拷贝功能?
    答案:对象拷贝问题
  • 第六个问题:可变集合类 和 不可变集合类的 copy 和 mutablecopy有什么区别?如果是集合是内容复制的话,集合里面的元素也是内容复制么?
[immutableObject copy]  //浅复制  
[immutableObject mutableCopy] //深复制
[mutableObject copy] //深复制
[mutableObject mutableCopy] //深复制
备注:浅复制就是指针的复制,指针不会改变,里面的成员变量也不用改变,但是深复制会改变指针,里面的成员变量不会改变。上面的结论就是只有不可变数组copy时候才是浅拷贝,剩下的都是深拷贝。
  • 第七个问题:为什么IBOutlet修饰的UIView也适用weak关键字?

个人理解:我们将Storyboard上或者XIB上面的控件拖拽到VC的时候会发现控件的默认属性就是weak,那是因为IBOutlet属性相对于它放在的View是强引用的,而这个view对于加载在它上面控件是强引用,当我们在VC使用的时候我们只是要使用它,所以没必要使用Strong属性,当然使用strong也不会错的(如果将weak改为strong,也是没有问题的,并不会造成强引用循环。当viewController的指针指向其他对象或者为nil,这个viewController销毁,那么对控件就少了一个强引用指针。然后它的view也随之销毁,那么subViews也不存在了,那么控件就又少了一个强引用指针,如果没有其他强引用,那么这个控件也会随之销毁。)
参考:参考文献

  • 第八个问题:nonatomic和atomic的区别?atomic是绝对的线程安全么?为什么?如果不是,那应该如何实现?
原子(atomic) 非原子(nonatomic)
安全性: nonatomic < atomic
速度: nonatomic >  atomic
消耗CPU性能: nonatomic  < atomic
程序默认状态下 是原子(atomic)的,我们常用nonatomic的原因也是因为atomic过于消耗内存,而且速度慢,当然atomic在安全性上也不是绝对安全的,因为我们要想绝对安全就必须使用线程锁将两个线程都调用的方法进行lock,这样线程A访问时候线程B就无法访问,这样就能够达到安全的效果,下面有例子关于线程锁和线程的。

线程大全:线程大全以及使用方法
线程锁:线程锁大全demo

  • 第九个问题:UICollectionView自定义layout如何实现?
    Demo地址UICollectionView自定义layout
  • 第十个问题:进程和线程的区别?同步异步的区别?并行和并发的区别?
1. 并发:在[操作系统](http://baike.baidu.com/view/880.htm)中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个[处理机](http://baike.baidu.com/view/2107226.htm)上运行。其中两种并发关系分别是同步和互斥2. 互斥:进程间相互排斥的使用临界资源的现象,就叫互斥。3. 同步:进程之间的关系不是相互排斥临界资源的关系,而是相互依赖的关系。进一步的说明:就是前一个进程的输出作为后一个进程的输入,当第一个进程没有输出时第二个进程必须等待。具有同步关系的一组并发进程相互发送的信息称为消息或事件。其中并发又有伪并发和真并发,伪并发是指单核处理器的并发,真并发是指多核处理器的并发。4. 并行:在单处理器中多道程序设计系统中,进程被交替执行,表现出一种并发的外部特种;在多处理器系统中,进程不仅可以交替执行,而且可以重叠执行。在多处理器上的程序才可实现并行处理。从而可知,并行是针对多处理器而言的。并行是同时发生的多个并发事件,具有并发的含义,但并发不一定并行,也亦是说并发事件之间不一定要同一时刻发生。
5. 多线程:多线程是程序设计的逻辑层概念,它是进程中并发运行的一段代码。多线程可以实现线程间的切换执行。
6. 异步:异步和同步是相对的,同步就是顺序执行,执行完一个再执行下一个,需要等待、协调运行。异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这一事件完成后再工作。线程就是实现异步的一个方式。异步是让调用方法的主线程不需要同步等待另一线程的完成,从而可以让主线程干其它的事情。   异步和多线程并不是一个同等关系,异步是最终目的,多线程只是我们实现异步的一种手段。异步是当一个调用请求发送给被调用者,而调用者不用等待其结果的返回而可以做其它的事情。实现异步可以采用多线程技术或则交给另外的进程来处理。
为了对以上概念的更好理解举一个简单例子,  假设我要做 烧开水,举杠铃100下, 洗衣服 3件事情。烧开水 这件事情,  我要做的事情为, 准备烧开水 1分钟, 等开水烧开 8 分钟 , 关掉烧水机 1分钟举杠铃100下        我要做的事情为,  举杠铃100下  10分钟洗衣服            我要做的事情为, 准备洗衣服 1分钟, 等开水烧开 5 分钟 , 关掉洗衣机 1分钟单核情况下同步的完成,我需要做的时间为 1+ 8 +1 + 10 + 1+ 5 +1 = 27 分如果异步,就是在等的时候,我可以切换去做别的事情准备烧开水(1) + 准备洗衣服(1) + 举50下杠铃 (5)分钟+ 关洗衣机 1分钟 + 举杠铃20下 (2)分钟+ 关烧水机 1分钟 + 举30下杠铃(3)分钟1+1+5+1+2+1+3 =14 分钟
双核 异步 并行核1  准备烧开水 1分钟+ 举杠铃50下(5)分钟+ 等待3分钟 + 关掉烧水机 1分钟核2  准备洗衣服 1分钟+ 举杠铃50下(5)分钟+ 关掉洗衣机 1分钟 + 等待3分钟其实只花了 1+5+3+1 = 10分钟其中还有双核都等待了3分钟
双核 异步 非并行核1  举杠铃100下(10)分钟核2  准备烧开水 1分钟+ 准备洗衣服 1分钟+ 等待5 分钟+ + 关掉烧水机 1分钟  + 等待 1 分钟 + 关掉洗衣机 1分钟其实只花了 1+5+3+1 = 10分钟
多线程的做法单核下线程1  准备烧开水 1分钟, 等开水烧开 8 分钟 , 关掉烧水机 1分钟线程2  举杠铃100下  10分钟线程3  准备洗衣服 1分钟, 等开水烧开 5 分钟 , 关掉洗衣机 1分钟cpu 可能这么切换 最理想的切换方式线程1  准备烧开水1  sleep  1          sleep 5            sleep 1          sleep 2          关开水 1分钟 exit线程2    sleep 1      sleep  1            举杠铃50 5分钟    sleep 1          举杠铃20 2分钟  sleep1      举杠铃30下 3分钟      线程3    sleep  1      准备洗衣服1 分钟    sleep 5            关洗衣机1分钟    exit最后使用了  14分钟  和异步是一样的。但是实际上是不一样的,因为线程不会按照我们设想的去跑, 如果线程2 举杠铃先跑,整个流程的速度就下来了。异步和同步的区别,  在io等待的时候,同步不会切走,浪费了时间。如果都是独占cpu 的业务, 比如举杠铃的业务, 在单核情况下 多线和单线 没有区别。多线程的好处,比较容易的实现了 异步切换的思想, 因为异步的程序很难写的。多线程本身程还是以同步完成,但是应该说比效率是比不上异步的。 而且多线很容易写, 相对效率也高。多核的好处,就是可以同时做事情, 这个和单核完全不一样的。
  • 第十二个问题: 线程间通信?进程间通信?
    答案:线程、进程 通信
  • 第十三个问题:GCD的一些常用的函数?
//1、创建主线程(串行)
    dispatch_async(dispatch_get_main_queue(), ^{
        //刷新界面代码
    });
    //2、创建异步线程(并行)
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //比较耗时的代码放这里
    });
    
    //3、gcd延迟
    double delayInSeconds = 1.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        //延迟代码
    });
    
    //4、gcd只执行一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //只执行一次代码
    });
    
    //5、有三个任务,需要异步并发执行前两个任务,前两个任务执行完成后再执行第三个任务。
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        //创建组
        dispatch_group_t group=dispatch_group_create();
        
        // 关联一个任务到group
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            //任务一
            NSLog(@"******执行任务一******");
        });
        
        // 关联一个任务到group
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            //任务二
            NSLog(@"******执行任务二******");
        });
        
        // 等待组中的任务执行完毕,回到主线程执行block回调
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            //任务三
            NSLog(@"******等待组中的任务执行完毕,回到主线程执行block回调,执行任务三******");
        });
        
    });
    
 
    
    //6、dispatch_barrier_async的使用,dispatch_barrier_async是在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行
    dispatch_queue_t queue = dispatch_queue_create("create_asy_queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"dispatch_async1");
    });
    dispatch_async(queue, ^{
        NSLog(@"dispatch_async2");
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"dispatch_barrier_async");
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"刷新界面");
        });
        
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"dispatch_async3");
    });
    
    /*7、GCD的另一个用处是可以让程序在后台较长久的运行。
    在没有使用GCD时,当app被按home键退出后,app仅有最多5秒钟的时候做一些保存或清理资源的工作。但是在使用GCD后,app最多有10分钟的时间在后台长久运行。这个时间可以用来做清理本地缓存,发送统计数据等工作。
    让程序在后台长久运行的示例代码如下:
    */
    // AppDelegate.h文件
    @property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundUpdateTask;
    
    // AppDelegate.m文件
    - (void)applicationDidEnterBackground:(UIApplication *)application
    {
        [self beingBackgroundUpdateTask];
        // 在这里加上你需要长久运行的代码
        [self endBackgroundUpdateTask];
    }
    
    - (void)beingBackgroundUpdateTask
    {
        self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
            [self endBackgroundUpdateTask];
        }];
    }
    
    - (void)endBackgroundUpdateTask
    {
        [[UIApplication sharedApplication] endBackgroundTask: self.backgroundUpdateTask];
        self.backgroundUpdateTask = UIBackgroundTaskInvalid;
    }

第十四个问题:数据持久化的几个方案?

1、Userdefaults
2、Plist
3、归档
4、FMDB
5、单例
6、CoreData
  • 第十五个问题:知不知道Designated Initializer?使用它的时候有什么需要注意的问题?
    答案:参考文献
  • 第十六个问题:实现description方法能取到什么效果?
    答案:参考文献
  • 第十七个问题:objc使用什么机制管理对象内存?
RAC自动释放 
MRC手动释放
引用计数为0,对象才会释放,只要没有强指针指向对象, 对象就会被释放.当然使用weak(弱引用)也会被释放
  • 第十八个问题:类变量的 @public,@protected,@private,@package 声明各有什么含义?
@public 任何地方都能访问; @protected 该类和子类中访问,是默认的; @private 只能在本类中访问; @package 本包内使用,跨包不可以。
  • 第十九个问题:isKindOfClass、isMemberOfClass、selector作用分别是什么
isKindOfClass:作用是某个对象属于某个类型或者继承自某类型。 isMemberOfClass:某个对象确切属于某个类型。 selector:通过方法名,获取在内存中的函数的入口地址。
  • 第二十个问题:delegate 和 notification 的区别
1). 二者都用于传递消息,不同之处主要在于一个是一对一的,另一个是一对多的。 2). notification通过维护一个array,实现一对多消息的转发。 3). delegate需要两者之间必须建立联系,不然没法调用代理的方法;notification不需要两者之间有联系。
  • 第二十一个问题:iOS的沙盒目录结构是怎样的?
沙盒结构: 1). Application:存放程序源文件,上架前经过数字签名,上架后不可修改。 2). Documents:常用目录,iCloud备份目录,存放数据。(这里不能存缓存文件,否则上架不被通过) 3). Library: Caches:存放体积大又不需要备份的数据。(常用的缓存路径) Preference:设置目录,iCloud会备份设置信息。 4). tmp:存放临时文件,不会被备份,而且这个文件下的数据有可能随时被清除的可能。
  • 第二十二个问题:iOS中常用的数据存储方式有哪些?
数据存储有四种方案:NSUserDefault、KeyChain、file、DB。 其中File有三种方式:plist、Archive(归档) DB包括:SQLite、FMDB、CoreData
  • 第二十三个问题:说说你对 OC 中 load 方法和 initialize 方法的异同。——主要说一下执行时间,各自用途,没实现子类的方法会不会调用父类的?

答:load和initialize方法都会在实例化对象之前调用,以main函数为分水岭,前者在main函数之前调用,后者在之后调用。这两个方法会被自动调用,不能手动调用它们。
load和initialize方法都不用显示的调用父类的方法而是自动调用,即使子类没有initialize方法也会调用父类的方法,而load方法则不会调用父类。
load方法通常用来进行Method Swizzle,initialize方法一般用于初始化全局变量或静态变量。
load和initialize方法内部使用了锁,因此它们是线程安全的。实现时要尽可能保持简单,避免阻塞线程,不要再使用锁

  • 第二十四个问题:谈谈你对 ARC 的理解。ARC 是编译器完成的,依靠引用计数,谈谈几个属性修饰符的内存管理策略,什么情况下会内存泄露。

  • 第二十五个问题:*说说 UITableView 的调优。——一方面是通过 instruments 检查影响性能的地方,另一方面是估算高度并在 runloop 空闲时缓存。
    1、减少视图的数目 (UITableViewCell包含了textLabel、detailTextLabel和imageView等view)---->如果你的table cell包含图片,且数目较多,使用默认的UITableViewCell会非常影响性能。奇怪的是,使用自定义的view,而非预定义的view,明显会快些。
    2、(UITableView只需要一屏幕的UITableViewCell对象即可。因此在cell不可见时,可以将其缓存起来,而在需要时继续使用它即可)--->重用池的使用,但是你觉得重用池的使用就很牛逼了吗?----->
    注意cellForRowAtIndexPath在cell 在tableview展示之前就会调用,此时cell实例已经生成,所以不能更改cell的结构,只能是改动cell上的UI的一些属性(例如label的内容等)。
    3、cell高度我们可以直接设置tableView.rowHeight的属性来设置高度,不用非要写heightForRowAtIndexPath的代理方法,rowHeight默认为44的高度,以节省不必要的计算和开销。。当然动态实现高度还是需要heightForRowAtIndexPath方法的,当你设置这个代理之后,tableView的rowHeight属性也将无复存在了。
    4、不要在heightForRowAtIndexPath调用cellForRowAtIndexPath来算高度,这样会爆炸的,尽量使用缓存的高度数据来处理。。另外cellForRowAtIndexPath和heightForRowAtIndexPath要相对独立。不要重复调用。
    5、不要在heightForRowAtIndexPath中创建视图。尽量在外面创建好,利用hidden现实隐藏就行了。

    6、遇到复杂的页面我们可以采用异步绘制的方式来处理复杂的页面布局(首先需要给自定义的Cell添加draw方法,(当然也可以重写drawRect)然后在方法体中实现)
    7、滑动时按需加载,这个在大量图片展示,网络加载的时候很管用!(SDWebImage已经实现异步加载,配合这条性能杠杠的)。
    8、
    (1)正确使用reuseIdentifier来重用Cells
    (2)尽量使所有的view opaque,包括Cell自身
    (3)尽量少用或不用透明图层
    (4)如果Cell内现实的内容来自web,使用异步加载,缓存请求结果
    (5)减少subviews的数量
    (6)在heightForRowAtIndexPath:中尽量不使用cellForRowAtIndexPath:,如果你需要用到它,只用一次然后缓存结果
    (7)尽量少用addView给Cell动态添加View,可以初始化时就添加,然后通过hide来控制是否显示
    关于TableView优化的那些事

  • 第二十六个问题:weak和assign的区别?
    1.weak:
    (1).在ARC中,在有可能出现循环引用的时候,往往要通过让其中一端使用weak来解决,比如:delegate代理属性。
    (2).如果自身已经对它进行一次强引用,没有必要再强引用一次时也会使用weak。比如:自定义IBOutlet控件属性一般也使用weak,当然也可以使用strong。
    2.assign:
    用于基础数据类型(例如NSInteger等)和C数据类型(int, float, double, char)等,另外还有id类型。

不同点:
(1).weak,表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。
(2).assign也可以修饰对象,但是用assign修饰的对象在释放后,指针的地址还是存在的,也就是说指针并没有被置为nil,会造成众所周知的野指针异常。然而,assign修饰的基础数据类型(例如NSInteger等)和C数据类型(int, float, double, char)等一般分配在栈空间上,栈空间的内存会由系统自动处理,当分配的栈空间的内存没有被指针指向时就会被销毁,所以不会造成野指针异常。
(3).weak比 assign多了一个功能就是当属性所指向的对象消失的时候(也就是内存引用计数为0)会自动赋值为 nil,这样再向 weak修饰的属性发送消息就不会导致野指针操作crash。

  • 第二十七个问题:这个写法会出什么问题: @property (copy) NSMutableArray *array;????

两个问题:
1、添加,删除,修改数组内的元素的时候,程序会因为找不到对应的方法而崩溃.因为 copy 就是复制一个不可变 NSArray 的对象;
2、使用了 atomic 属性会严重影响性能 ;

  • 第二十八个问题:[@synthesize和@dynamic区别]?(在声明property属性后,有2种实现选择)

@synthesize

编译器期间,让编译器自动生成getter/setter方法。

当有自定义的存或取方法时,自定义会屏蔽自动生成该方法

@dynamic

告诉编译器,不自动生成getter/setter方法,避免编译期间产生警告

然后由自己实现存取方法

或存取方法在运行时动态创建绑定:主要使用在CoreData的实现NSManagedObject子类时使用,由Core Data框架在程序运行的时动态生成子类属性

  • 第二十九个问题:什么时候会报unrecognized selector错误?

    1、对象未实现该方法。
    2、对象已经被释放。

  • 第三十个问题:IBOutlet连出来的视图属性为什么可以被设置成weak?

    因为既然有外链那么视图在xib或者storyboard中肯定存在,视图已经对它有一个强引用了。

    不过这个回答漏了个重要知识,使用storyboard(xib不行)创建的vc,会有一个叫_topLevelObjectsToKeepAliveFromStoryboard的私有数组强引用所有top level的对象,所以这时即便outlet声明成weak也没关系

本人个人微信公众号地址(喜欢记得关注)


辛小二个人微信公众号地址

你可能感兴趣的:(IOS面试总结)