iOS面试题iOS面试题/2019iOS 开发继续加油

iOS面试题-常问题

2019-06-14  本文已影响280人  小熊_07cb

未完,待更新

一、必备题

1、AFN 原理

链接:AFNetworking源码——基本架构 - 简书

2、SDWebImage原理

链接:iOS利用SDWebImage图片下载缓存 - 简书

3、runtime

链接:谈谈iOS-runtime - 简书

4、runloop

5、多线程

6、block 、代理

7、MVC、MVVM

8、KVC、KVO

以上就是面试的时候问的最多的

以下是我觉得很有用的题

二、面试题

1、看下面的程序,三次NSLog会输出什么?为什么?

    NSMutableArray* ary = [[NSMutableArray array] retain];

    NSString *str = [NSString stringWithFormat:@"test"];

    [str retain];

    [ary addObject:str];

    NSLog(@"%ld", (unsignevbfücd long)[str retainCount]);

    [str retain];

    [str release];

    [str release];

    NSLog(@"%ldx", (unsigned long)[str retainCount]);

    [ary removeAllObjects];

    NSLog(@"%ld", (unsigned long)[str retainCount]);

结果:-1、-1、-1 。-1代表没有引用计数或者引用计数非常大,因为str是字符串,字符串在常量区,没有引用计数。引用计数为-1,这可以理解为NSString实际上是一个字符串常量,是没有引用计数的(或者它的引用计数是一个很大的值(使用%lu可以打印查看),对它做引用计数操作没实质上的影响)

2、冒泡排序

  void sort(int array[],int n)

  { // n 为数组元素个数

  int i,j,k,temp; // i 为基准位置,j 为当前被扫描元素位置,k 用于暂存出现的较小的元素的位置

  for(i=0;i<n-1;i++)

  {k=i;//初始化为基准位置

  for(j=i+1;j<n;j++)

  {

  if (array[j]<array[k]) k=j ; // k 始终指示出现的较小的元素的位置

  if(k!=i)

  { temp=array[i];

  array[i]=array[k];

  array[k]=temp; // 将此趟扫描得到的最小元素与基准互换位置

  }

  } //for

  }

  }

3、谈谈week

weak表其实是一个hash表,Key是所指对象的地址,Value是weak指针的地址数组,weak是弱引用,所引用对象的计数器不会+1,并在引用对象被释放的时候自动被设置为nil。通常用于解决循环引用问题。

4、在项目什么时候选择使用 GCD,什么时候选择NSOperation?

项目中使用 NSOperation 的优点是 NSOperation 是对线程的高度抽象,在项目中使用它,会使项目的程序结构更好,子类化NSOperation 的设计思路,是具有面向对象的优点(复用、封装),使得实现是多线程支持,而接口简单,建议在复杂项目中使用。项目中使用 GCD 的优点是 GCD 本身非常简单、易用,对于不复杂的多线程操作,会节省代码量,而 Block 参数的使用,会是代码更为易读,建议在简单项目中使用。

5、什么是 TCP连接的三次握手

 1、建立连接时,客户端发送SYN包到服务器,并进入SYN_SEND状态,等待服务器确认;

 2、服务器收到SYN包,必须确认客户的SYN,同时自己也发送一个SYN包,即SYN+ACK包,此时服务器进入SYN_RECV状态;

 3、客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK,此包发送完毕,客户端和服务器进入established状态,完成三次握手;

另一种白话理解:

第一次握手: 客户端发送网络包,服务端收到了。这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。

第二次握手:服务端发包,客户端收到了。这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常。

第三次握手: 客户端发包,服务端收到了。这样服务端就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常。

6、Socket的实现原理及 Socket之间是如何通信的

网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。建立网络通信连接至少要一对端口号(socket)。socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口

7、自动释放池底层怎么实现

自动释放池以栈的形式实现:当你创建一个新的自动释放池时,它将被添加到栈顶。当一个对象收到发送autorelease消息时,它被添加到当前线程的处于栈顶的自动释放池中,当自动释放池被回收时,它们从栈中被删除,并且会给池子里面所有的对象都会做一次release操作.

8、自动释放池是什么,如何工作

当您向一个对象发送一个autorelease消息时,Cocoa就会将该对象的一个引用放入到最新的自动释放池。它仍然是个正当的对象,因此自动释放池定义的作用域内的其它对象可以向它发送消息。当程序执行到作用域结束的位置时,自动释放池就会被释放,池中的所有对象也就被释放。

1).ojc-c是通过一种"referring counting"(引用计数)的方式来管理内存的,对象在开始分配内存(alloc)的时候引用计数为一,以后每当碰到有copy,retain的时候引用计数都会加一,每当碰到release和autorelease的时候引用计数就会减一,如果此对象的计数变为了0, 就会被系统销毁.

2). NSAutoreleasePool就是用来做引用计数的管理工作的,这个东西一般不用你管的.

3). autorelease和release没什么区别,只是引用计数减一的时机不同而已,autorelease会在对象的使用真正结束的时候才做引用计数减一.

10、怎么保证多人开发进行内存泄露的检查.

* 使用Analyze进行代码的静态分析

* 为避免不必要的麻烦, 多人开发时尽量使用ARC

11、按钮或者其它 UIView控件的事件传递的具体过程

触摸事件的传递是从父控件传递到子控件也就是UIApplication->window->寻找处理事件最合适的view

注 意: 如果父控件不能接受触摸事件,那么子控件就不可能接收到触摸事件

应用如何找到最合适的控件来处理事件?

1).首先判断主窗口(keyWindow)自己是否能接受触摸事件

2).判断触摸点是否在自己身上

3).子控件数组中从后往前遍历子控件,重复前面的两个步骤(所谓从后往前遍历子控件,就是首先查找子控件数组中最后一个元素,然后执行1、2步骤)

4).view,比如叫做fitView,那么会把这个事件交给这个fitView,再遍历这个fitView的子控件,直至没有更合适的view为止。

5).如果没有符合条件的子控件,那么就认为自己最合适处理这个事件,也就是自己是最合适的view。

UIView不能接收触摸事件的三种情况:

* 不允许交互:userInteractionEnabled = NO

* 隐藏:如果把父控件隐藏,那么子控件也会隐藏,隐藏的控件不能接受事件

* 透明度:如果设置一个控件的透明度<0.01,会直接影响子控件的透明度。alpha:0.0~0.01为透明。

注 意:默认UIImageView不能接受触摸事件,因为不允许交互,即userInteractionEnabled = NO。所以如果希望UIImageView可以交互,需要设置UIImageView的userInteractionEnabled = YES。

12、控制器 View的生命周期及相关函数是什么?你在开发中是如何用的?

* 1.首先判断控制器是否有视图,如果没有就调用loadView方法创建:通过storyboard或者代码;

* 2.随后调用viewDidLoad,可以进行下一步的初始化操作;只会被调用一次;

* 3.在视图显示之前调用viewWillAppear;该函数可以多次调用;

* 4.视图viewDidAppear

* 5.在视图消失之前调用viewWillDisappear;该函数可以多次调用;

如需要);

* 6.在布局变化前后,调用viewWill/DidLayoutSubviews处理相关信息;

13、把程序自己关掉和程序进入后台,远程推送的区别

1). 关掉后不执行任何代码, 不能处理事件

2). 应用程序进入后台状态不久后转入挂起状态。在这种状态下,应用程序不执行任何代码,并有可能在任意时候从内存中删除。只有当用户再次运行此应用,应用才会从挂起状态唤醒,代码得以继续执行

3).或者进入后台时开启多任务状态,保留在内存中,这样就可以执行系统允许的动作

4).远程推送是由远程服务器上的程序发送到APNS,再由APNS把消息推送至设备上的程序,当应用程序收到推送的消息会自动调用特定的方法执行事先写好的代码

14、谈谈runloop

    1、 运行循环是与线程相关的基础的一部分。运行循环是一个事件处理循环,用于调度工作并协调事件的接收。运行循环的目的是在有工作要做时保持线程忙,当没有线程时将线程放在睡眠中

    2、底层就是do-while循环

    3、runloop是来管理线程的,当线程的runloop被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务

15、runloop和线程有什么关系?

* 每条线程都有唯一的一个RunLoop对象与之对应的

* 主线程的RunLoop是自动创建并启动

* 子线程的RunLoop需要手动启动,子线程runloop是懒加载用到的时候获取

* 子线程的RunLoop创建步骤如下:

    * 获得RunLoop对象后要调用run方法来启动一个运行循环

// 启动RunLoop

[[NSRunLoop currentRunLoop] run];

    * RunLoop的其他启动方法

// 第一个参数:指定运行模式

// 第二个参数:指定RunLoop的过期时间,即:到了这个时间后RunLoop就失效了

[[NSRunLoop currentRunLoop] runMode:kCFRunLoopDefaultMode beforeDate:[NSDate distantFuture]];

* RunLoop是来管理线程的,当线程的RunLoop被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务。

* RunLoop在第一次获取时被创建,在线程结束时被销毁。

16、runloop是来做什么的?runloop和线程有什么关系?主线程默认开启了runloop么?子线程呢?

runloop的mode是用来做什么的?有几种mode?

为什么把NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环以后,滑动scrollview的时候NSTimer却不动了?

答:1).runloop是来管理线程的,当线程的runloop被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务。runloop和线程有什么关系,看问题15.主线程默认开启runloop,子线程需要自己去获取.

2).model:是runloop里面的模式,不同的模式下的runloop处理的事件和消息有一定的差别。

系统默认注册了5个Mode:

(1)kCFRunLoopDefaultMode: App的默认 Mode,通常主线程是在这个 Mode 下运行的。

(2)UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。

(3)UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。

(4)GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。

(5)kCFRunLoopCommonModes: 这是一个占位的 Mode,没有实际作用。

3).nstime对象是在 NSDefaultRunLoopMode下面调用消息的,但是当我们滑动scrollview的时候,NSDefaultRunLoopMode模式就自动切换到UITrackingRunLoopMode模式下面,却不可以继续响应nstime发送的消息。所以如果想在滑动scrollview的情况下面还调用nstime的消息,我们可以把nsrunloop的模式更改为NSRunLoopCommonModes

17、NSString为什么要用copy关键字,如果用strong会有什么问题?(注意:这里没有说用strong就一定不行。使用copy和strong是看情况而定的

经常使用copy关键字原因:

1).因为父类指针可以指向子类对象,使用copy的目的是为了让本对象的属性不受外界影响,使用copy无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本.

如果改用strong关键字,可能造成什么问题?

2).如果我们使用是strong,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性.

举例说明:

定义一个以 strong 修饰的 array:

@property (nonatomic ,readwrite, strong)NSArray *array;

然后进行下面的操作:

NSArray *array = @[ @1, @2, @3, @4 ];

NSMutableArray *mutableArray = [NSMutableArrayarrayWithArray:array]; 

self.array = mutableArray;

[mutableArrayremoveAllObjects];

NSLog(@"%@",self.array);

[mutableArrayaddObjectsFromArray:array];

self.array = [mutableArraycopy]; 

[mutableArrayremoveAllObjects];

NSLog(@"%@",self.array);

打印结果如下所示:

19:10:32.523 XXArrayCopyDmo[10681:713670] ()

19:10:32.524 XXArrayCopyDmo[10681:713670] (1,2,3,4)

18、 UITableview的优化方法(缓存高度,异步绘制,减少层级,hide,避免离屏渲染)

19、有没有用过运行时,用它都能做什么?(交换方法,创建类,给新创建的类增加方法,改变isa指针)

20、以+scheduledTimerWithTimeInterval...的方式触发的 timer,在滑动页面上的列表时,timer会暂定回调,为什么?如何解决?

这里强调一点:在主线程中以+scheduledTimerWithTimeInterval...的方式触发的timer 默认是运行在 NSDefaultRunLoopMode模式下的,当滑动页面上的列表时,进入了 UITrackingRunLoopMode模式,这时候 timer 就会停止。可以修改 timer 的运行模式为 NSRunLoopCommonModes,这样定时器就可以一直运行了。

以下是我的补充:

在子线程中通过 scheduledTimerWithTimeInterval:...方法来构建

NSTimer方法内部已经创建NSTimer 对象 ,并加入到 RunLoop 中 , 运行模式为NSDefaultRunLoopMode。

由于 Mode 有 timer 对象,所以 RunLoop 就开始监听定时器事件了,从而开始进入运行循环。

这个方法仅仅是创建 RunLoop 对象,并不会主动启动 RunLoop,需要再调用 run方法来启动

如果在主线程中通过 scheduledTimerWithTimeInterval:...方法来构

建 NSTimer,就不需要主动启动 RunLoop 对象,因为主线程的 RunLoop 对象在程序运行起来就已经被启动了

// userInfo 参数:用来给 NSTimer 的

userInfo属性赋值,userInfo是只读的,只能在构建 NSTimer对象

时赋值

[NSTimer scheduledTimerWithTimeInterval:1.0 target:self

selector:@selector(run:)    userInfo:@"ya了个hoo"

repeats:YES];

//scheduledTimer...方法创建出来 NSTimer虽然已经指定了默认模

式,但是【允许你修改模式】

[[NSRunLoop currentRunLoop] addTimer:timer

forMode:NSRunLoopCommonModes];

// 【仅在子线程】需要手动启动 RunLoop对象,进入运行循环

[[NSRunLoop currentRunLoop] run];

21、以下代码运行结果如何?

- (void)viewDidLoad

{

[super viewDidLoad];

NSLog(@"1");

dispatch_sync(dispatch_get_main_queue(), ^{

NSLog(@"2");

});

NSLog(@"3");

}

答案:输出1    原因:主线程死锁

22、HTTP协议中 POST方法和 GET方法有那些区别? GET和POST本质上就是TCP链接,并无差别。但是由于HTTP的规定和浏览器/服务器的限制,导致他们在应用过程中体现出一些不同

GET和POST还有一个重大区别

简单的说:

GET产生一个TCP数据包;POST产生两个TCP数据包。

长的说:

对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);

而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。

注意 :并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次。

23、使用 block时什么情况会发生引用循环,如何解决?

一个对象中强引用了block,在block中又强引用了该对象,即双向引用,就会发生循环引用。

解决方法是将该对象使用__weak或者__block修饰符修饰之后再在block中使用。

id __weak weakSelf = self; 或者 __weak __typeof(self)weakSelf = self;. 该方法可以设置宏

id __block weakSelf = self;

或者将其中一方强制制空 xxx = nil。

检测代码中是否存在循环引用问题,可使用 Facebook 开源的一个检测工具 FBRetainCycleDetector或者在类中重写delloc方法,只要每个类调用了delloc即释放了 。

24、沙盒目录结构是怎样的?各自用于那些场景?

Application:存放程序源文件,上架前经过数字签名,上架后不可修改

Documents:常用目录,iCloud 备份目录,存放数据

Library

1).Caches:存放体积大又不需要备份的数据

2).Preference:设置目录,iCloud 会备份设置信息

3).tmp:存放临时文件,不会被备份,而且这个文件下的数据有可能随时被清除的可能

25、控制器的生命周期

就是问的 view 的生命周期,下面已经按方法执行顺序进行了排序

// 自定义控制器 view,这个方法只有实现了才会执行

- (void)loadView

{

self.view = [[UIView alloc] init];

self.view.backgroundColor = [UIColor orangeColor];

}

// view 是懒加载,只要 view 加载完毕就调用这个方法

- (void)viewDidLoad

{

[super viewDidLoad];

}

// view 即将显示

- (void)viewWillAppear:(BOOL)animated{

[super viewWillAppear:animated];

}

// view 即将开始布局子控件

- (void)viewWillLayoutSubviews{

[super viewWillLayoutSubviews];

}

// view 已经完成子控件的布局

- (void)viewDidLayoutSubviews{

[super viewDidLayoutSubviews];

}

// view 已经出现

- (void)viewDidAppear:(BOOL)animated

{

    [super viewDidAppear:animated];

}

// view 即将消失

- (void)viewWillDisappear:(BOOL)animated{

    [super viewWillDisappear:animated];

}

// view 已经消失

- (void)viewDidDisappear:(BOOL)animated{

[super viewDidDisappear:animated];

}

// 收到内存警告

- (void)didReceiveMemoryWarning{

[super didReceiveMemoryWarning];

}

// 方法已过期,即将销毁 view

- (void)viewWillUnload{

}

// 方法已过期,已经销毁 view

- (void)viewDidUnload{

}

但是我们最长常用方法:

viewDidLoad

viewWillAppear

viewWillDisappear

25、ARC是为了解决什么问题诞生的?

首先解释 ARC: automatic reference counting 自动引用计数

MRC 的缺点:

在 MRC 时代当我们要释放一个堆内存时,首先要确定指向这个堆空间的指针都被release 了

释放指针指向的堆空间,首先要确定哪些指针指向同一个堆,这些指针只能释放一次(MRC 下即谁创建,谁释放,避免重复释放)

模块化操作时,对象可能被多个模块创建和使用,不能确定最后由谁去释放

多线程操作时,不确定哪个线程最后使用完毕

综上所述,MRC 有诸多缺点,很容易造成内存泄露和坏内存的问题,这时苹果为尽量解决这个问题,从而诞生了 ARC

26、ARC下还会存在内存泄露吗?

循环引用会导致内存泄露.

Objective-C 对象与 CoreFoundation 对象进行桥接的时候如果管理不当也会造成内存泄露.

CoreFoundation 中的对象不受 ARC 管理,需要开发者手动释放

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

首先明白什么情况使用 weak关键字?

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

2.自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用 weak,自定义

3.IBOutlet 控件属性一般也使用 weak;当然,也可以使用 strong,但是建议使用 weak

weak 和 assign 的不同点

weak 策略在属性所指的对象遭到摧毁时,系统会将 weak 修饰的属性对象的指针指向 nil,在 OC 给 nil 发消息是不会有什么问题的;如果使用 assign 策略在属性所指的对象遭到摧毁时,属性对象指针还指向原来的对象,由于对象已经被销毁,这时候就产生了野指针,如果这时候在给此对象发送消息,很容造成程序奔溃

assigin 可以用于修饰非 OC 对象,而 weak 必须用于 OC 对象

28、+(void)load; +(void)initialize;有什么用处?

+(void)load;

当类对象被引入项目时, runtime 会向每一个类对象发送 load 消息。

在main函数调用前,+(void)load就已经调用完了。load 方法会在每一个类甚至分类被引入时仅调用一次,调用的顺序:父类优先于子类, 子类优先于分类。

由于 load 方法会在类被 import 时调用一次,而这时往往是改变类的行为的最佳时机,在这里可以使用例如 method swizlling 来修改原有的方法。

load 方法不会被类自动继承。

+(void)initialize;

也是在第一次使用这个类的时候会调用这个方法,也就是说 initialize 也是懒加载

总结:

在 Objective-C 中,runtime 会自动调用每个类的这两个方法

1.+load 会在类初始加载时调用

2.+initialize 会在第一次调用类的类方法或实例方法之前被调用

这两个方法是可选的,且只有在实现了它们时才会被调用

两者的共同点:两个方法都只会被调用一次

29、Foundation对象与 Core Foundation对象有什么区别

Foundation 框架是使用 OC 实现的,Core Foundation 是使用 C 实现的。

30、UIView 和CALayer的区别?

1、UIView可以响应事件,CALayer不可以,因为他们继承不同,UIView继承UIResponder,CALayer继承NSObject.

2、UIView像是一个CALayer的管理器,访问它的跟绘图和跟坐标有关的属性,例如frame,bounds等等,实际上内部都是在访问它所包含的CALayer的相关属性。

3、CALayer侧重动画,UIView显示及事件响应

链接:详解 CALayer 和 UIView 的区别和联系 - CocoaChina_让移动开发更简单

31、Category 有什么好处?为什么不能扩张属性?

答:好处:

1).比如一个类功能太庞大,可以使用category进行分散

2).创建私有方法的前向引用

-例如Person类有一个私有方法

-(void)processStr:(NSString*)str;

在viewDidLoad方法中想要使用Person的这个方法

Person* p = [[Person alloc] init];

[p precoessStr:@"str"];

这样编译不能通过,如果给Person添加了一个分类,并且在分类中声明这个方法,再在viewDidLoad方法的文件中包含这个分类,那么就不会报错,可以正常运行了。

3).添加非正式协议

正式协议需要用@protocol,@required,

@optional,@delegate来规范书写方式,而非正式协议则不需要,只要相应的delegate类直接实现就可以。但是category仅限于NSObject类的分类,不能用它的子类实现非正式协议

如:NSObject+Test分类

-(NSString)inputStr:(NSString)str;这个方法要么在NSObject+Test.m文件中实现,要么在Person.m文件中实现

//NSObject+Test.h#import<Foundation/Foundation.h>@interfaceNSObject(Test)-(NSString*)inputStr:(NSString*)str;@end//NSObject+Test.m文件#import"NSObject+test.h"@implementationNSObject(test)@end

Person类

//Person.h文件

#import<Foundation/Foundation.h>

#import"NSObject+Test.h"

@interfacePerson:NSString

@end

//Person.m文件

#import"Person.h"

@implementationPerson

-(NSString*)inputStr:(NSString*)str{

if([str isEqualToString:@"wdc"])

{

        return@"ldy"; 

  }

return@"error";

}

@end

在viewcontroller中

- (void)viewDidLoad {

    [superviewDidLoad]; 

  Person* p = [[Person alloc] init];

NSString* str = [p inputyouName:@"wdc"];

NSLog(@"%@",str);}

输出:ldy

不能扩展原因请参考:探究iOS分类(category)为什么不能直接添加属性 - lixuezhi - CSDN博客

32、简述NotificationCenter、KVC、KVO、Delegate?并说明它们之间的区别?

Notification是观察者模式的实现,KVO是观察者模式的OB-C底层实现。

NOtification通过Notifydcation addobserver和remove observer工作。

KVO是键值监听,键值观察机制,提供了观察某一属性变化的方法

KVC是键值编码,是一种间接访问对象的属性,使用字符串来标示属性(例如:setValue:forKey:) Delegate:把某个对象要做的事情委托给别的对象去做。那么别的对象就是这个对象的代理,代替它来打理要做的事。反映到程序中,首先要明确一个对象的委托方是哪个对象,委托所做的内容是什么。

33、数据持久化存储方式有哪些?以及特点?

1).plist 属性列表 最外层只能存储数组字典 里面只能存储 bool NSNumber String Data Date

2).NSUserDefault 最终也是保存成plist 系统封装了保存的路径 保存的方法

3).归档 可以对保存数据的文件 进行加密

4).sqlite  关系型数据库 以表的形式存储  FMDB是对 OC中 sqlite操作封装 的第三方库

5).coreData 是苹果封装的 对文件操作的框架 可以 以对象的形式存储 底层数据文件可以是sqlite类型 也可以是XML JSON …

34、如何对定位和分析项⽬目中影响性能的地⽅方?以及如何进⾏行行性能优化? 定位⽅法:

instruments  在iOS上进⾏行行性能分析的时候,⾸首先考虑借助instruments这个利利器器分析出问题出 在哪,不不要凭空想象,不不然你可能把精力花在了1%的问题上,最后发现其实啥都没优 化,比如要查看程序哪些部分最耗时,可以使⽤用Time Profiler,要查看内存是否泄漏了,可以使用Leaks等。关于instruments网上有很多资料料,作为一个合格iOS开发 者,熟悉这个工具还是很有必要的。

优化建议:

1.⽤ARC管理理内存

* ARC(Automatic Reference Counting, ⾃自动引⽤用计数)和iOS5⼀一起发布,它避免了了 最常⻅见的也就是经常是由于我们忘记释放内存所造成的内存泄露露。它⾃自动为你管理理 retain和release的过程,所以你就不不必去⼿手动⼲干预了了。下⾯面是你会经常⽤来去创建一 个View的代码段: UIView *view = [[UIView alloc] init];

* // ...

* [self.view addSubview:view];

* [view release];

* 忘掉代码段结尾的release简直像记得吃饭一样简单。而ARC会自动在底层为你做这 些工作。除了帮你避免内存泄露,ARC还可以帮你提高性能,它能保证释放掉不再需要的对象的内存。

3.尽量量把views设置为完全不不透明

* 如果你有透明的Views你应该设置它们的opaque(不透明)属性为YES。例例如⼀个黑色半透明的可以设置为一个灰色不透明的View替代.原因是这会使系统⽤一个最优的方式渲染这些views。这个简单的属性在IB或者代码⾥都可以设定。

* Apple的⽂文档对于为图⽚片设置透明属性的描述是:

* (opaque)这个属性给渲染系统提供了一个如何处理理这个view的提示。如果设为 YES, 渲染系统就认为这个view是完全不透明的,这使得渲染系统优化一些渲染过程和提高性能。如果设置为NO,渲染系统正常地和其它内容组成这个View。默认值是 YES。

* 在相对⽐较静止的画面中,设置这个属性不会有太大影响。然而当这个view嵌在 scroll view里边,或者是一个复杂动画的一部分,不设置这个属性的话会在很⼤程度上影响app的性能。

* 换种说法,⼤家可能更好理解:只要一个视图的不透明度小于1,就会导致 blending.blending操作在iOS的图形处理器(GPU)中完成的,blending主要指的是混合像素颜色的计算。举个例子,我们把两个图层叠加在一起,如果第一个图层的有透明效果,则最终像素的颜色计算需要将第二个图层也考虑进来。这⼀过程即为Blending。为什么Blending会导致性能的损失?原因是很直观的,如果一个图层是完全不透明的,则系统直接显示该图层的颜色即可。⽽如果图层是带透明效果的,则会引入更多的计算,因为需要把下面的图层也包括进来,进行混合后颜⾊的计算。

4. 避免过于庞⼤大的XIB

* iOS5中加入的Storyboards正在快速取代XIB。然而XIB在一些场景中仍然很有用。⽐如你的app需要适应iOS5之前的设备,或者你有⼀个自定义的可重⽤的view,你 就不可避免地要用到他们。

* 如果你不得不XIB的话,使他们尽量简单。尝试为每个Controller配置⼀一个单独的 XIB,尽可能把一个View Controller的view层次结构分散到单独的XIB中去。

* 需要注意的是,当你加载一个XIB的时候所有内容都被放在了内存里里,包括任何图片。如果有一个不会即刻用到的view,你这就是在浪费宝贵的内存资源了。 Storyboards就是另一码事儿了,storyboard仅在需要时实例例化一个view controller.

* 当你加载⼀个引用了了图片或者声音资源的nib时,nib加载代码会把图⽚片和声音文件写进内存。在OS X中,图片和声音资源被缓存在named cache中以便将来用到时获取。在iOS中,仅图片资源会被存进named caches。取决于你所在的平台,使⽤用 NSImage 或UIImage 的imageNamed:方法来获取图片资源。

5. 不要阻塞主线程

* 永远不要使主线程承担过多。因为UIKit在主线程上做所有工作,渲染,管理理触摸反 应回应输入等都需要在它上面完成。一直使用主线程的风险就是如果你的代码真的block了主线程,你的app会失去反应

* 大部分阻碍主进程的情形是你的app在做一些牵涉到读写外部资源的I/O操作,比如 存储或者网络。或者使用像 AFNetworking这样的框架来异步地做这些操作。如果你需要做其它类型的需要耗费巨⼤资源的操作(⽐如时间敏感的计算或者存储读写)那就用 Grand Central Dispatch,或者 NSOperation 和 NSOperationQueues.你可以使用NSURLConnection异步地做⽹络操作: + (void)sendAsynchronousRequest: (NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler

6. 在Image Views中调整图片大小

* 如果要在UIImageView中显示一个来自bundle的图片,你应保证图片的⼤小和

UIImageView的⼤小相同。在运行中缩放图片是很耗费资源的,特别是UIImageView 嵌套在UIScrollView中的情况下。

* 如果图片是从远端服务加载的你不能控制图⽚大⼩,比如在下载前调整到合适⼤小 的话,你可以在下载完成后,最好是用background thread,缩放一次,然后在 UIImageView中使用缩放后的图片。

7. 选择正确的Collection 学会选择对业务场景最合适的类或者对象是写出能效⾼高的代码的基础。当处理理 collections时这句话尤其正确。

Apple有⼀一个 Collections Programming Topics 的文档详尽介绍了可用的classes间 的差别和你该在哪些场景中使用它们。这对于任何使用collections的人来说是一个必 读的文档。

呵呵,我就知道你因为太长没看...这是一些常见collection的总结:

* Arrays: 有序的一组值。使用index来lookup很快,使用value lookup很慢, 插⼊/删 除很慢。

* Dictionaries: 存储键值对。 ⽤键来查找比较快。

* Sets: 无序的⼀组值。用值来查找很快,插入/删除很快。

8. 打开gzip压缩

* ⼤量app依赖于远端资源和第三⽅API,你可能会开发一个需要从远端下载XML, JSON, HTML或者其它格式的app。

* 问题是我们的目标是移动设备,因此你就不能指望⽹络状况有多好。一个⽤户现在还在edge网络,下一分钟可能就切换到了了3G。不论什么场景,你肯定不想让你的⽤户等太长时间。

* 减⼩文档的一个方式就是在服务端和你的app中打开gzip。这对于文字这种能有更⾼压缩率的数据来说会有更显著的效用。好消息是,iOS已经在NSURLConnection中默 认支持了gzip压缩,当然AFNetworking这些基于它的框架亦然。像Google App Engine这些云服务提供者也已经⽀支持了了压缩输出。

35、iOS app启动如何优化?

1. 查看启动时间,对这块优化。通过在 Xcode 中 Edit scheme -> Run -> Auguments 将环境变量量 DYLD_PRINT_STATISTICS 设为 1,在控制台看到main()函数之前的启动时间。

2. 分解优化目标分步达到优化⽬的

1). 耗时操作异步处理

2). 如果启动流程依赖网络请求回来才能继续,那么需要考虑网络极差情况下的启动速度

3). 如果APP有loading广告页并且对分辨率的要求较⾼,请尝试做缓存吧

4). 主⻚面Controller中的viewDidLoad和viewWillAppear⽅法中尽量少做事情

5). 排查清理项目中未使用到的静态类库以及Framework,因为在编译阶段系统会把静态类库加载到内存里

6). 删减合并⼀一些OC类,删减没有用到或者可以不用的静态变量、方法等

7). 轻量化+load方法中的内容,可延迟到+initialize中,因为load在mian()函数前已经加载过了,runtime机制

36、谈谈你对多线程开发的理理解?ios中有⼏几种实现多线程的⽅方法?

答案:

好处:

1.使⽤用线程可以把占据时间⻓的程序中的任务放到后台去处理

2.⽤户界面可以更加吸引人,比如用户点击了一个按钮去触发某些事件的处理, 可以弹出一个进度条来显示处理的进度

3.程序的运行速度可能加快

4·在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有 用了。

缺点:

1.如果有⼤量的线程,会影响性能,因为操作系统需要在它们之间切换。创建更多的线程需要更更多的内存空间。

3.线程的中⽌止需要考虑其对程序运⾏行行的影响。

4.通常块模型数据是在多个线程间共享的,需要防⽌止线程死锁情况的发⽣生。 实现多线程的⽅方法:

37、NSOPrationQueue 与GCD分别在什么情况下更合适使用?

1).GCD是底层的C语言构成的API,而NSOperationQueue是基于GCD的OC封装。

2).GCD支持FIFO队列,NSOperationQueue可以方便设置执行顺序,设置最大的并发数量。

3).NSOperationQueue可以方便地设置operation之间的依赖关系,GCDF则需要更多的代码。

4).NSOperationQueue支持KVO,可以检测operation是否正在执行(isExecuted),是否结束(isFinished),是否取消(isCanceled)。

5).GCD的执行速度比NSOperationQueue快。

使用场合:

任务之间不太相互依赖:GCD;

任务之间依赖或者监听任务的执行情况:NSOperationQueue

38、问:什么时候需要使用自动释放池?

官方解释:基本分为如下三点

1、当我们需要创建大量的临时变量的时候,可以通过@autoreleasepool 来减少内存峰值。

2、创建了新的线程执行Cocoa调用。

3、如果您的应用程序或线程是长期存在的,并且可能会生成大量自动释放的对象,那么您应该定期清空并创建自动释放池(就像UIKit在主线程上所做的那样);否则,自动释放的对象会累积,内存占用也会增加。但是,如果创建的线程不进行Cocoa调用,则不需要创建自动释放池。

追问:为什么会减少内存峰值?

答:借用YYImage的代码打个比方。

比如业务需要在一个代码块中需要创建大量临时变量,或临时变量足够大,占用了很多内存,可以在临时变量使用完以后就立即释放掉,在ARC的环境下只能通过自动释放池实现。

if ([UIDevice currentDevice].isSimulator) {

        @autoreleasepool {

            NSString *outPath = [NSString stringWithFormat:@"%@ermilio.gif.png",IMAGE_OUTPUT_DIR];

            NSData *outData = UIImagePNGRepresentation([UIImage imageWithData:gif]);

            [outData writeToFile:outPath atomically:YES];

            [gif writeToFile:[NSString stringWithFormat:@"%@ermilio.gif",IMAGE_OUTPUT_DIR] atomically:YES];

        }

        @autoreleasepool {

            NSString *outPath = [NSString stringWithFormat:@"%@ermilio.apng.png",IMAGE_OUTPUT_DIR];

            NSData *outData = UIImagePNGRepresentation([UIImage imageWithData:apng]);

            [outData writeToFile:outPath atomically:YES];

            [apng writeToFile:[NSString stringWithFormat:@"%@ermilio.png",IMAGE_OUTPUT_DIR] atomically:YES];

        }

        @autoreleasepool {

            NSString *outPath = [NSString stringWithFormat:@"%@ermilio_q85.webp.png",IMAGE_OUTPUT_DIR];

            NSData *outData = UIImagePNGRepresentation([YYImageDecoder decodeImage:webp_q85 scale:1]);

            [outData writeToFile:outPath atomically:YES];

            [webp_q85 writeToFile:[NSString stringWithFormat:@"%@ermilio_q85.webp",IMAGE_OUTPUT_DIR] atomically:YES];

        }

}

再比如在循环的场景下,如果创建大量的临时变量,会使内存峰值持续增加,加入自动释放池以后,在每次循环结束时,超出自动释放池的作用域,使得内部的大量临时变量被释放,从而大大降低了内存的使用。

for (int i = 0; i < count; i++) {

        @autoreleasepool {

            id imageSrc = _images[i];

            NSDictionary *frameProperty = NULL;

            if (_type == YYImageTypeGIF && count > 1) {

                frameProperty = @{(NSString *)kCGImagePropertyGIFDictionary : @{(NSString *) kCGImagePropertyGIFDelayTime:_durations[i]}};

            } else {

                frameProperty = @{(id)kCGImageDestinationLossyCompressionQuality : @(_quality)};

            }

}

上述这几种情况如果没必要就别这么写,毕竟创建自动释放池也需要耗费内存。

40、iOS面试题:有了线程,你觉得为什么还要有runloop?,runloop和线程有什么关系?

解析:关于为什么要,我觉得runloop是来管理(调度)线程的,当线程的runloop被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务。

关于这两者的更多关系:

* runloop与线程是一一对应的,一个runloop对应一个核心的线程,为什么说是核心的,是因为runloop是可以嵌套的,但是核心的只能有一个,他们的关系保存在一个全局的字典里。

* runloop在第一次获取时被创建,在线程结束时被销毁。

* 对于主线程来说,runloop在程序一启动就默认创建好了。

* 对于子线程来说,runloop是懒加载的,只有当我们使用的时候才会创建,所以在子线程用定时器要注意:确保子线程的runloop被创建,不然定时器不会回调。

41、iOS面试题:NSOperation 与 GCD 的主要区别?

• 1. GCD 的核心是 C 语言写的系统服务,执行和操作简单高效,因此 NSOperation 底层也通过 GCD 实现,换个说法就是 NSOperation 是对 GCD 更高层次的抽象,这是他们之间最本质的区别。因此如果希望自定义任务,建议使用 NSOperation;

• 2. 依赖关系,NSOperation 可以设置两个 NSOperation 之间的依赖,第二个任务依赖于第一个任务完成执行,GCD 无法设置依赖关系,不过可以通过dispatch_barrier_async来实现这种效果;

• 3. KVO(键值对观察),NSOperation 和容易判断 Operation 当前的状态(是否执行,是否取消),对此 GCD 无法通过 KVO 进行判断;

• 4. 优先级,NSOperation 可以设置自身的优先级,但是优先级高的不一定先执行,GCD 只能设置队列的优先级,无法在执行的 block 设置优先级;

• 5. 继承,NSOperation 是一个抽象类,实际开发中常用的两个类是 NSInvocationOperation 和 NSBlockOperation ,同样我们可以自定义 NSOperation,GCD 执行任务可以自由组装,没有继承那么高的代码复用度;

• 6. 效率,直接使用 GCD 效率确实会更高效,NSOperation 会多一点开销,但是通过 NSOperation 可以获得依赖,优先级,继承,键值对观察这些优势,相对于多的那么一点开销确实很划算,鱼和熊掌不可得兼,取舍在于开发者自己;

42、你是否接触过OC中的反射机制?简单聊一下概念和使用、OC

• class反射

  1、通过类名的字符串形式实例化对象

Class class NSClassFromString@(@"student"); Student *stu = [[class alloc ]init];

  2、将类名变为字符串

Class class =[Student class];

NSString *className = NSStringFromClass(class);

• SEL的反射

  1、通过方法的字符串形式实例化方法

SEL selector = NSSelectorFromClass(@"setName"); [stu performSelector:selector withObject:@"Mike"];

2、将方法变为字符串

NSStringFomrSelector(@selector*(setName:))

43、为什么NSAarray用copy修饰,而NSMutableArray不能用copy修饰

因为NSMutableArray用copy修饰,所得到的实际是NSArray,在删除、添加等操作会crash。

44、如何访问并修改一个类的私有属性?

• kvc

• 通过runtime访问并修改私有属性

45、在一个对象的方法里面:self.name= “object”;和 name =”object” 有什么不同吗?

答:self.name =”object”:会调用对象的setName()方法;

name = “object”:会直接把object赋值给当前对象的name属性。

46、ffmpeg框架

答: 音视频编解码框架,内部使用UDP协议针对流媒体开发,内部开辟了六个端口来接受流媒体数据,完成快速接受之目的。

47、fmdb框架

答:数据库框架,对sqllite的数据操作进行了封装,使用着可把精力都放在sql语句上面。

48、TCP和UDP的区别

答:他们类似申通和韵达快递,是传输层协议。

TCP可靠的数据流传输,而UDP提供的是不可靠的数据流传输。

简单的说,TCP注重数据安全,而UDP数据传输快点,但安全性一般

48、http和scoket通信的区别。

答:由于通常情况下Socket连接就是TCP连接,因此Socket连接一旦建立,通信双方即可开始相互发送数据内容,直到双方连接断开。但在实际网络应用中,客户端到服务器之间的通信往往需要穿越多个中间节点,例如路由器、网关、防火墙等,大部分防火墙默认会关闭长时间处于非活跃状态的连接而导致 Socket 连接断连,因此需要通过轮询告诉网络,该连接处于活跃状态。

  而HTTP连接使用的是“请求—响应”的方式即三次握手,不仅在请求时需要先建立连接,而且需要客户端向服务器发出请求后,服务器端才能回复数据。

很多情况下,需要服务器端主动向客户端推送数据,保持客户端与服务器数据的实时与同步。此时若双方建立的是Socket连接,服务器就可以直接将数据传送给客户端;若双方建立的是HTTP连接,则服务器需要等到客户端发送一次请求后才能将数据传回给客户端,因此,客户端定时向服务器端发送连接请求,不仅可以保持在线,同时也是在“询问”服务器是否有新的数据,如果有就将数据传给客户端。

区别简述:

  1)http是一种协议,socket是一种编程接口,主要包括TCP协议和UDP协议;

  2)http和TCP/UDP是两个不同层上的的协议。http是应用层的协议,TCP/UDP是传输层的协议,http是在TCP/UDP之上的协议,http协议使用了TCP/UDP,http更加高级一点但是没有很好的灵活性。也就是http使用起来比TCP/UDP要简单,只需要遵循规范就可以进行网络通信了。

49、谈谈Object-C的内存管理方式及过程?

答: 1).当你使用new,alloc和copy方法创建一个对象时,该对象的保留计数器值为1.当你不再使用该对象时,你要负责向该对象发送一条release或autorelease消息.这样,该对象将在使用寿命结束时被销毁.

2).当你通过任何其他方法获得一个对象时,则假设该对象的保留计数器值为1,而且已经被设置为自动释放,你不需要执行任何操作来确保该对象被清理.如果你打算在一段时间内拥有该对象,则需要保留它并确保在操作完成时释放它.

50、Category(类别)、 Extension(扩展)和继承的区别

区别:

1. 分类有名字,类扩展没有分类名字,是⼀一种特殊的分类。

2. 分类只能扩展⽅方法(属性仅仅是声明,并没真正实现),类扩展可以扩展属性、成 员变量量和⽅方法。

3. 继承可以增加,修改或者删除⽅方法,并且可以增加属性。

51、为什么代理要用weak?block和代理的区别?他们哪个性能好,为什么?

通过weak打破循环引⽤用。 代理和block的区别:代理和block的共同特性是回调机制。不同的是代理的方法比较多,比较注重过程。block代码比较集中、简洁,比较注重结果;代理的运行成本要低于block的运行成本,block的出站需要从栈内存拷⻉贝到堆内存。公共接⼝比较多时,用代理解耦;简单回调和异步线程中使用block。

52、HTTP协议三次握手?

53、用weak修饰的UIView *a,addSubview添加到另一个UIView  *b上,a能显示出来吗?

答:不能,weak 修饰的 属性,init 不会 使引用计数 + 1,strong修饰的 属性,init 会使 引用计数 + 1,addsubView 方法 会把 控件 添加到 subView 中,而subView是copy修饰,引用+1。所以:weak 修饰的控件 没有加到 VIew上 引用计数 为0,就被释放了

但是我们看到系统xib或者s'toryboard拖的属性能显示出来,那是因为系统内部对它所绑定的属性做了强引用操作

54、在block中打破循环引用有几种方法?

55、链表与数组的区别?

56、NSArray是放在堆还是栈里的?

57、  __weak与__block修饰符有什么区别?

1.__block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。

2.__weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)。

3.__block对象可以在block中被重新赋值,__weak不可以。

4.__block对象在ARC下可能会导致循环引用,非ARC下会避免循环引用,__weak只在ARC下使用,可以避免循环引用。

上一篇下一篇

猜你喜欢

热点阅读