iOS 面试iOS-面试题面试

我的iOS面试题

2016-05-13  本文已影响1028人  _ToBeBetterMan

从今天开始每天总结几道iOS面试题!!!

Day--1

1.#include与#import的区别、#import与@class的区别

a.#include与#import的区别:#include用于对系统文件的引用,编译器会在系统文件目录下去查找该文件。#include与#import 都会包含引用的类的所有信息,包括实体变量和方法,但是#import处理了重复引用的问题,而include需要自己处理重复引用。

b.#import与@class的区别:1.import会包含这个类的所有信息,包括实体变量和方法,而@class只是告诉编译器,其后面声明的名称是类的名称,至于这些类是如何定义的,暂时不用考虑,后面会再告诉你。2.在头文件中, 一般只需要知道被引用的类的名称就可以了。 不需要知道其内部的实体变量和方法,所以在头文件中一般使用@class来声明这个名称是类的名称。 而在实现类里面,因为会用到这个引用类的内部的实体变量和方法,所以需要使用#import来包含这个被引用类的头文件。

2.深、浅拷贝的基本概念以及他们的区别

深拷贝 : 拷贝出来的对象与源对象地址不一致! 这意味着我修改拷贝对象的值对源对象的值没有任何影响.

浅拷贝 : 拷贝出来的对象与源对象地址一致! 这意味着我修改拷贝对象的值会直接影响到源对象.

这里有更详细的介绍,以及copy的详细的用法详解。让我们谢谢原作者,我只是搬运工

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

一.什么情况使用 weak 关键字

1)在ARC中,在有可能出现循环引用的时候,往往要通过让其中一端使用weak来解决,比如:delegate代理属性

2)自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用weak,自定义IBOutlet控件属性一般也使用weak;当然,也可以使用strong。

二.不同点:

1)weak 此特质表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似, 然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。 而 assign 的“设置方法”只会执行针对“纯量类型” (scalar type,例如 CGFloat 或 NSlnteger 等)的简单赋值操作。

2)使用weak不会让引用计数器+1,weak当对象销毁的时候,指针会被自动设置为nil,而assign不会* assigin 可以用非OC对象,而weak必须用于OC对象。

4. @property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的

@property 的本质 :@property = ivar + getter + setter;

“属性” (property)有两大概念:ivar(实例变量)、存取方法(access method = getter + setter)。

ivar、getter、setter 是如何生成并添加到这个类中的?

完成属性定义后,编译器会自动编写访问这些属性所需的方法,此过程叫做“自动合成”( autosynthesis)。需要强调的是,这个过程由编译 器在编译期执行,所以编辑器里看不到这些“合成方法”(synthesized method)的源代码。除了生成方法代码 getter、setter 之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字。

也就是说我们每次在增加一个属性,系统都会在ivar_list中添加一个成员变量的描述,在method_list中增加setter与getter方法的描述,在属性列表中增加一个属性的描述,然后计算该属性在对象中的偏移量,然后给出setter与getter方法对应的实现,在setter方法中从偏移量的位置开始赋值,在getter方法中从偏移量开始取值,为了能够读取正确字节数,系统对象偏移量的指针类型进行了类型强转.

Day--2

5. @synthesize和@dynamic有什么区别

6. GCD底层的三种队列和简单理解

1.the main queue: 与主线程功能相同。是一个串行队列。
主队列为串行队列,可以又叫 UI队列,关于处理UI界面的请求都在主队列中异步并发执行。
2.global queues: 全局队列是并发队列,并由整个进程共享。global_queue是一个全局并发队列,即加入global_queue的代码块会立即被执行
3.用户队列: 用户队列是串行的

7. 请简述AFNetWorking的实现原理

8. CALayer和UIView的关系

1.UIView本身不具备显示的功能,是它内部的CALayer在起作用

2.CALayer是图层,和界面展示相关。UIView很多属性和方法和CALayer里的属性和方法是一致的。UIView可以看做是CALayer的管理者,除了负责视图展示之外,还可以处理一些事件,比如手势交互等。但我们对UIView做的一些有关视图显示和动画的操作,本质上还是对CALayer进行的操作。

3.CALayer 和UIView的选择

•通过CALayer,就能做出跟UIImageView一样的界面效果

•既然CALayer和UIView都能实现相同的显示效果,那究竟该选择谁好呢?

其实,对比CALayer,UIView多了一个事件处理的功能。也就是说,CALayer不能处理用户的触摸事件,而UIView可以;所以,

如果显示出来的东西需要跟用户进行交互的话,用UIView;

如果不需要跟用户进行交互,用UIView或者CALayer都可以

Day--3

9. iOS中有哪些数据持久化方式,请简要介绍它们的应用场景

iOS下数据持久化常用的几种方式:

上面几种方式,有一个共同的要素,就是应用的/Documents文件夹。每个应用都有自己的/Documents文件夹,且仅能读写各自/Documents文件中的内容。

10. 事件是如何传递的,简述事件响应链机制

事件的产生和传递过程:

1.发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的队列事件中

2.UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常会先发送事件给应用程序的主窗口(keyWindow)

3.主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件

4.找到合适的视图控件后,就会调用视图控件的touches方法来作事件的具体处理:touchesBegin… touchesMoved…touchesEnded等

5.这些touches方法默认的做法是将事件顺着响应者链条向上传递,将事件叫个上一个相应者进行处理

响应者链条其实就是很多响应者对象(继承自UIResponder的对象)一起组合起来的链条称之为响应者链条

一般默认做法是控件将事件顺着响应者链条向上传递,将事件交给上一个响应者进行处理。那么如何判断当前响应者的上一个响应者是谁呢?有以下两个规则:

1.判断当前是否是控制器的View,如果是控制器的View,上一个响应者就是控制器

2.如果不是控制器的View,上一个响应者就是父控件

UIApplication–>UIWindow–>递归找到最合适处理的控件–>控件调用touches方法–>判断是 否实现touches方法–>没有实现默认会将事件传递给上一个响应者–>找到上一个响应者–>找不到方法作废

11. 理解单例设计模式

一、单例模式的作用

它可以保证某个类在运行过程中,只有一个实例,也就是对象实例只占用一份系统内存资源。

二、单例的要点

该类有且只有一个实例
该类必须能自行创建这个实例
该类必须能够向整个系统提供这个实例

三、单例的优缺点

#import <Foundation/Foundation.h>
@interface Class : NSObject
+ (instancetype)defaultManager;
@end

//重写init
- (instancetype)init {

    @throw [NSException exceptionWithName:@"不允许调用Class" reason:@"因为Class是一个单例,只能通过default方法获获取对象" userInfo:nil];
}

//私有方法创建
- (instancetype)initPrivate {

    if (self = [super init]) {
        
        //干一些事情
    }
    return self;
}

+ (instancetype)defaultManager{

    static DataBaseManager * instance = nil;
    static dispatch_once_t onceToken = 0;
    dispatch_once(&onceToken, ^{
        if (!instance) {
            
            instance = [[Class alloc]initPrivate];
        }     
    });
    return instance;
}

Day--4

12. 下面的代码输出什么?

@implementation Son : Father
    - (id)init
    {
        self = [super init];
        if (self) {
            NSLog(@"%@", NSStringFromClass([self class]));
            NSLog(@"%@", NSStringFromClass([super class]));
        }
        return self;
    }
@end
2016-05-13 17:52:51.694 Test[12816:262134] Son
2016-05-13 17:52:51.696 Test[12816:262134] Son

原因:调用父类初始化的时候,是不看指针看对象的,因此谁调用super谁就是super的拥有者

13. objc中的类方法和实例方法有什么本质区别和联系?

类方法
实例方法

14. SDWebImage的原理

1.入口
setImageWithURL:placeholderImage:options: 会先把placeholderImage展示,然后 SDWebImageManager根据URL开始处理图片.

2.进入
SDWebImageManager-downloadWithURL:delegate:options:userInfo:,交给SDImageCache从缓存查找图片是否已经下载 :
通过: queryDiskCacheForKey:delegate:userInfo:.

3.先从内存图片缓存查找是否有图片,如果内存中已经有图片缓存SDWebImageDelegate回调imageCache:didFindImage:ForKey:userInfo: 到SDWebImageManager

4.SDWebImageManagerDelegate回调

webImageManager:didFinishWithImage: 

到UIImageView+WebCache等前端展示图片

5.如果内存缓存中没有,生成NSInvocationOperation添加到队列开始从硬盘查找图片是否已经缓存。

6.根据 URLKey 在硬盘缓存目录下尝试读取图片文件.这一步是在 NSOperation 进行的操作,所以回主线程进行结果回调.

7.如果上一操作从硬盘读取到了图片,将图片添加到内存缓存中,如果内存空间过小会先清空内存缓存.
SDImageCacheDelegate回调 imageCache:didFindImage:forKey:userInfo: 进而回调展示图片.

8.如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调imageCache:didNotFindImageForKey:userInfo:。

9.共享或重新生成一个下载器 SDWebImageDownloder开始下载图片

10.图片下载由 NSURLConnection来做,实现相关的Delegate来判断图片下载中,下载完成和下载失败.

11.connection:didReceiveData:中利用 ImageIO 做了按图片下载进度加载效果。

12.connectionDidFinishLoading: 数据下载完成后交给 SDWebImageDecoder 做图片解码处理。

13.图片解码处理在一个 NSOperationQueue 完成,不会拖慢主线程 UI。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多。

14.在主线程notifyDelegateOnMainThreadWithInfo:宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo:回调给 SDWebImageDownloader。

15.imageDownloader:didFinishWithImage: 回调给 SDWebImageManager 告知图片下载完成。

16.通知所有的 downloadDelegates 下载完成,回调给需要的地方展示图片。

17.将图片保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存。写文件到硬盘也在以单独 NSInvocationOperation 完成,避免拖慢主线程。

18.SDImageCache 在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片。

19.SDWI 也提供了 UIButton+WebCache 和 MKAnnotationView+WebCache,方便使用。

20.SDWebImagePrefetcher 可以预先下载图片,方便后续使用

15. BAD_ACCESS在什么情况下出现?如何调试BAD_ACCESS错误?

什么是 EXC_BAD_ACCESS?

不管什么时候当你遇到EXC_BAD_ACCESS这个错误,那就意味着你向一个已经释放的对象发送消息。访问了野指针,比如对一个已经释放的对象执行了release、访问已经释放对象的成员变量或者发消息。

EXC_BAD_ACCESS的本质

技术层面的解释有些复杂。在C和Objective-C中,你一直在处理指针。指针无非是存储另一个变量的内存地址的变量。当您向一个对象发送消息时,指向该对象的指针将会被引用。这意味着,你获取了指针所指的内存地址,并访问该存储区域的值。

当该存储器区域不再映射到您的应用时,或者换句话说,该内存区域在你认为使用的时候却没有使用,该内存区域是无法访问的。 这时内核会抛出一个异常( EXC ),表明你的应用程序不能访问该存储器区域(BAD ACCESS) 。

总之,当你碰到EXC_BAD_ACCESS ,这意味着你试图发送消息到的内存块,但内存块无法执行该消息。但是,在某些情况下, EXC_BAD_ACCESS是由被损坏的指针引起的。每当你的应用程序尝试引用损坏的指针,一个异常就会被内核抛出。

调试EXC_BAD_ACCESS

1.重写object的respondsToSelector方法,现实出现EXEC_BAD_ACCESS前访问的最后一个object
2.通过 Zombie
3.设置全局断点快速定位问题代码所在行
4.Xcode 7 已经集成了BAD_ACCESS捕获功能:Address Sanitizer。 用法如下:在配置中勾选✅Enable Address Sanitizer

16.什么是block,简述block实现原理

block循环引用问题
block尽量少使用self
block尽量少使用下划线(_)直接访问成员属性
要避免强引用到self的话,用__weak把self重新引用一下就行

Block的实现

我们所需要知道的是 block 就是一个对象,在它所在的内存中,保存着block自身的实现函数,可在调用block时用block自身的代码替代,同时保持着一个Block描述,标志着block的内存size与持有对象的指针。当声明与实现一个Block时,创建的闭包会捕获在它的域中的任何涉及的变量,通过在内存中持有他们,能够在block的实现中对其进行访问。在默认情况下,任何在block的域中被捕获的变量都不能被修改,除非这个变量已被给予了__block的标志。当block捕获了一个对象时,它会对其进行retain操作,并在block代码执行完毕完release对象,这样才能保证在block执行过程中,对象不会因引用计数为0而被释放掉。我们需要理解的是,block本身就是一个对象,它对其他对象的引用与一般的对象引用类似,都是需要对引用对象进行retain与release

17. 写一个宏MIN,这个宏输入两个参数并返回较小的一个

#define MIN(a,b)  ((a)>(b)?(b):(a))

18.frame和bounds有什么不同?

frame是在父视图坐标系下的位置和大小。bounds是自身坐标系下的位置和大小。

frame以父控件的左上角为坐标原点,bounds以控件本身的左上角为坐标原点

frame:以父控件左上角为原点。bounds:以自己的左上角为原点,bounds x,y永远为0(这是错误的认识)

frame和bounds都是用来描述一块区域frame:描述可视范围,也就是说从左上角的0,0点开始延伸,它延伸的区域就是我们的可视范围

bounds:描述可视范围在内容的区域,所有的子控件都有内容,它就类似于空气,是看不到的,正常情况下,内容是无限大的,所有的子控件其实都是放在内容上的,在可视范围内的内容我们才能 看见,所以正常情况下,内容的左上角(bounds)与可视范围(frame)的左上角是重合的,当修 改bounds的x与y都会导致子控件跟着移动.需要注意的是,可视范围(frame)是永远不会变的,它是相对父控件的.

所有的子控件都是相对于内容bounds:修改内容原点

相对性:可视范围相对于父控件位置永远不变 可视范围相对于内容,位置改变

Day-5

19.程序输出的结果

int main(){
         int a[5] = {1, 2, 3, 4, 5};
         int * ptr = (int *)(&a + 1);
         printf("%d\n", *(a + 1));
         printf("%d", *(ptr - 1));
       return 0;
}
2
5Program ended with exit code: 0

1.*(a+1)=a[0+1]=a[1]=2
2.&a表示对数组取地址,&a+1表示a[5]后面一个地址,
(ptr-1)表示对当前数组元素地址向前移动一位,并取值,故等于a[4]=5

20.谈谈RunLoop的简单理解

一、RunLoop从字面意思上看:

RunLoop的基本作用:

假如没有了RunLoop:

int main(int argc, char * argv[]) {
       @autoreleasepool { 
          return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        }
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        BOOL running = YES;
        do {
            /**
             * 在这里执行各种任务,处理事件
             * 这个过程是持续运行
             */  
        } while (running);
    }
    return 0;
}
二、RunLoop对象
三、RunLoop与线程
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象

21.找错误

试题1:

char * GetMemory(void){
    char p[] = "Hello World";
    return p;
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
            char * str = NULL;
            str = GetMemory();
            printf("%s\n",str);
    }
    return 0;
}
错误为:
char p[] = "Hello World";
return p;//不能返回栈地址,因为栈空间自动释放,应该返回栈空间的数据
正确应该为:
char *p = "Hello World";

试题2:

void GetMemory(char **p, int num){ 
      *p = (char *)malloc(num);
}
void Test(void){
     char * str = NULL; 
     GetMemory(&str, 100); 
     strcpy(str, "Hello"); 
     printf(str);
}
      // 堆空间需要手动释放,申请了堆内存时也要判断是否申请成功
      char * str = NULL;
      GetMemory(&str, 100); 
      if(str) {
           strcpy(str, "Hello"); 
           printf("%s",str); 
           free(str); 
           str = NULL; 
      }

Day-6

22. 写出下列两个属性的Setter方法

@property (nonatomic, retain) NSString * name;
@property (nonatomic, copy) NSString * name;
- (void) setName:(NSString *) name {
         if( _name != name) { [
              _name release]; 
              _name = [name retain]; 
          }
}
- (void) setName:(NSString *) name { 
             _name = [name copy];  
}

retain修饰的属性setter方法的实现步骤:
1:判断新值与旧值是否相等,如果不等执行以下操作
2:将旧值执行一次release操作(旧值release)
3:再将新值执行一次retain操作再赋给旧值(新值retain再赋值)

copy修饰的属性:如果要保证返回的是一个不可变的版本就要将新值执行一次copy操作

23. 类别和继承什么区别

区别:

24. 线程与进程的区别和联系?

上一篇下一篇

猜你喜欢

热点阅读