iOS面试题1-25

1.多线程的底层实现?

 (1).什么是多线程???

     多线程是一个比较轻量级的方法来实现单个应用程序内多个代码执行路径。 

     从技术角度来看,一个线程就是一个需要管理执行代码的内核级和应用级数据结 构组合。内核级结构协助调度线程事件,并抢占式调度一个线程到可用的内核之上。 应用级结构包括用于存储函数调用的调用堆栈和应用程序需要管理和操作线程属性 和状态的结构。

 (2).在应用程序中存 在多个线程提供了两个非常重要的的潜在优势:

     多个线程可以提高应用程序的感知响应。
     多个线程可以提高应用程序在多核系统上的实时性能。 

多线程的弊端

   当然多线程并不是解决程序性能问题的灵丹妙药。多线程带来好处同时也伴随着 潜在问题。应用程序内拥有多个可执行路径,会给你的代码增加更多的复杂性。每个 线程需要和其他线程协调其行为,以防止它破坏应用程序的状态信息。因为应用程序 内的多个线程共享内存空间,它们访问相同的数据结构。如果两个线程试图同时处理 相同的数据结构,一个线程有可能覆盖另外线程的改动导致破坏该数据结构。即使有 适当的保护,你仍然要注意由于编译器的优化导致给你代码产生很微妙的(和不那么 微妙)的 Bug。 

   你需要考虑的另一个因素是你是否真的需要多线程或并发。多线程解决了如何在

同一个进程内并发的执行多路代码路径的问题。然而在很多情况下你是无法保证你所 在做的工作是并发的。多线程引入带来大量的开销,包括内存消耗和 CPU 占用。你会 发现这些开销对于你的工作而言实在太大,或者有其他方法会更容易实现 

    线程编程的危害之一是在多个线程之间的资源争夺。如果多个线程在同一个时间 试图使用或者修改同一个资源,就会出现问题。缓解该问题的方法之一是消除共享资 源,并确保每个线程都有在它操作的资源上面的独特设置。 

(3).线程 进程 任务的区别?

    线程(线程)用于指代独立执行的代码段。
    进程(process)用于指代一个正在运行的可执行程序,它可以包含多个线程。

    任务(task)用于指代抽象的概念,表示需要执行工作。 

(4).线程包

     虽然多线程的底层实现机制是 Mach 的线程,你很少(即使有)使用 Mach 级的线 程。相反,你会经常使用到更多易用的 POSIX  API 或者它的衍生工具。Mach 的实 现没有提供多线程的基本特征,但是包括抢占式的执行模型和调度线程的能力,所以 它们是相互独立的。

(5).Run Loops

    一个 run loop 是用来在线程上管理事件异步到达的基础设施。一个 run loop 为 线程监测一个或多个事件源。当事件到达的时候,系统唤醒线程并调度事件到 run loop,然后分配给指定程序。如果没有事件出现和准备处理,run loop 把线程置于休 眠状态。 

    你创建线程的时候不需要使用一个 run loop,但是如果你这么做的话可以给用户 带来更好的体验。Run Loops 可以让你使用最小的资源来创建长时间运行线程。因为 run loop 在没有任何事件处理的时候会把它的线程置于休眠状态,它消除了消耗 CPU 周期轮询,并防止处理器本身进入休眠状态并节省电源。 为了配置 run loop,你所需要做的是启动你的线程,获取 run loop 的对象引用, 设置你的事件处理程序,并告诉 run loop 运行。

(6).解决多线程共享内存资源对同一资源的改变的冲突

    线程编程的危害之一是在多个线程之间的资源争夺。如果多个线程在同一个时间 试图使用或者修改同一个资源,就会出现问题。缓解该问题的方法之一是消除共享资 源,并确保每个线程都有在它操作的资源上面的独特设置。因为保持完全独立的资源 是不可行的,所以你可能必须使用锁,条件,原子操作和其他技术来同步资源的访问。

    :锁提供了一次只有一个线程可以执行代码的有效保护形式。最普遍的一种锁是互斥排他锁,也就是我们通常所说的“mutex”。当一个线程试图获取一个当前已经被其 他线程占据的互斥锁的时候,它就会被阻塞直到其他线程释放该互斥锁。系统的几个 框架提供了对互斥锁的支持,虽然它们都是基于相同的底层技术。此外 Cocoa 提供了 几个互斥锁的变种来支持不同的行为类型,比如递归。

    条件:确保在你的应用程序任务执行的适当顺序。一个条 件作为一个看门人,阻塞给定的线程,直到它代表的条件变为真。当发生这种情况的 时候,条件释放该线程并允许它继续执行。POSIX 级别和基础框架都直接提供了条件 的支持。(如果你使用操作对象,你可以配置你的操作对象之间的依赖关系的顺序确 定任务的执行顺序,这和条件提供的行为非常相似)。

    原子操作

尽管锁和条件在并发设计中使用非常普遍,原子操作也是另外一种保护和同步访 问数据的方法。原子操作在以下情况的时候提供了替代锁的轻量级的方法,其中你可 以执行标量数据类型的数学或逻辑运算。原子操作使用特殊的硬件设施来保证变量的 改变在其他线程可以访问之前完成。



2.为什么要用网络第三方库?

答:网络第三方框架把复杂的网络底层操作封装成友好的类和方法,并且加入异常处理等

从而可以:1> 高效的与服务端 API 进行数据交换 2> 提高开发效率和稳定性 3>主要体现在下载和上传文件上比较方便。


3.介绍一下iOS7中增加的NSURLSession

1. 没经过用户同意,你不能随便获取用户信息。

2.  所有的程序都在沙盒里运行,B程序不能进入A程序的运行范围。

3.  如果跟钱有关,比如说支付宝,这些底层的实现都是保密的,只提供接口供开发者调用,这样的话安全性得到保障。

4.  如果要防止代码被反编译,可以将自己的代码中的.m文件封装成静态库(.a文件)或者是framework文件,只提供给其它人.h文件。这样就保证了个人代码的安全性。

5. 提供keychain保护机制 


4.地图是怎么定位的?

答:三种定位方式,wifi、GPS、GPRS基站....


5.讲讲runtime机制

1> runtime,运行时机制,它是一套C语言库

2> 实际上我们编写的所有OC代码,最终都是转成了runtime的东西,比如类转成了runtime库里面的结构体等数据类型,方法转成了runtime库里面的C语言函数,平时调方法都是转成了objc_msgSend函数(所以说OC有个消息发送机制)

3> 因此,可以说runtime是OC的底层实现,是OC的幕后执行者

4> 有了runtime库,能做什么事情呢?runtime库里面包含了跟类、成员变量、方法相关的API,比如获取类里面的所有成员变量,为类动态添加成员变量,动态改变类的方法实现,为类动态添加新的方法等

(当时答了这里面的一部分,但是他说不是他想要的。我就又跟他说KVO是runtime的一个典型应用,他瞪了我两眼。不知道丫想要什么。)


6.本地推送和远程推送

答:本地通知和远程推送通知都可以向不在前台运行的应用发送消息,这种消息既可能是即将发生的事件,也可能是服务器的新数据.不管是本地通知还是远程通知,他们在程序界面的显示效果相同,都可能显示为一段警告信息或应用程序图标上的微章.

本地通知和远程推送通知的基本目的都是让应用程序能够通知用户某些事情, 而且不需要应用程序在前台运行.二者的区别在于本地通知由本应用负责调用,只能从当前设备上的iOS发出, 而远程通知由远程服务器上的程序发送到APNS,再由APNS把消息推送至设备上的程序


7.数据库文件放在哪?

答:沙盒里documents文件夹下。沙盒里边有三个文件夹:

documents(默认为空),library(有子文件夹cashes和preferences),tmp(默认为空)


8.FMDB有什么亮点


9.发布流程


10.企业级发布


11.极光推送原理(第三方框架),怎么用的?遇到什么问题?怎么解决的?



12.怎么实现第三方登陆



13.网络基础知识

TCP:安全的协议,能保证数据顺序和正确性,服务器和客户端能随时互发数据。如果服务器要主动发送数据给客户端,可以用这个协议

UDP:非安全的协议,容易丢失数据,一般用于联机对战的游戏

XMPP:基于XML通讯的协议,基于TCP发送XML数据,一般用于即时通讯(比如QQ、微信)

HTTP:一般用于非实时连接的请求,只有客户端主动向服务器发送请求时,服务器才能返回数据给客户端

SOCKET:套接口,可以使用TCP/UDP/XMPP通讯

状态编码, 200 表示是一个正确的请求,206表示请求只加载了一部分,404表示网络请求的页面不存在;503表示服务器超时,400请求出错

断点续传:客户端软件断点续传时在下载或者上传时,将下载或者上传的文件认为的划分成几个部分,每个部分一个线程进行上传或者下载的,如果网络异常,可以从上传或者下载的部分重新上传或者下载未上传下载的部分,提高速度,节省时间。

创建串行队列 加入异步任务

生成文件名,用该文件名和存放路径 生成文件路径

发送网络请求获取待生成文件文件大小

设定每次下载的字节数,循环下载 (循环判断是剩余字节是否大于循环下载字节)

发送请求时设定http头的range范围根据每次循环 fromB  toB 来设定

每次下载成功返回的数据写入到之前设定好的文件中

Socket连接与HTTP连接

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

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

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

http基于socket做出来的,所有的网络功能都是基于socket做出来的,比如:即时通讯,ftp


14.文件存取

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
(1).Plist文件存储
  // 1.获得沙盒根路径
  NSString *home = NSHomeDirectory();
  // 2.document路径
  NSString *docPath = [home stringByAppendingPathComponent:@ "Documents" ];
// 3.新建数据
  NSArray *data = @[@ "jack" , @10, @ "ffdsf" ];
// 4.将数据写入沙盒document 目录的data.plist文件中
  NSString *filepath = [docPath stringByAppendingPathComponent:@ "data.plist" ];
   [data writeToFile:filepath atomically:YES];
// 5.读取数据
  NSArray *data = [NSArray arrayWithContentsOfFile:filepath];
  NSLog(@ "%@" , data);
  (2).偏好设置存储
  // 1.利用NSUserDefaults,就能直接访问软件的偏好设置(Library/Preferences)
     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
   // 2.存储数据
     [defaults setObject:@ "mj"  forKey:@ "account" ];
     [defaults setObject:@ "123"  forKey:@ "pwd" ];
     [defaults setInteger:10 forKey:@ "age" ];
     [defaults setBool:YES forKey:@ "auto_login" ];
  // 3.立刻同步
     [defaults synchronize];
  // 4.读取数据
     NSString *account = [defaults objectForKey:@ "account" ];
     BOOL  autoLogin = [defaults boolForKey:@"auto_login”];
  (3).NSKeyedArchiver  和  NSKeyedUnarchiver
  MJStudent实现  <NSCoding > 协议的方法
/**
  *  将某个对象写入文件时会调用
  *  在这个方法中说清楚哪些属性需要存储
  */
- ( void )encodeWithCoder:(NSCoder *)encoder
{
     [encoder encodeObject:self.no forKey:@ "no" ];
     [encoder encodeInt:self.age forKey:@ "age" ];
     [encoder encodeDouble:self.height forKey:@ "height" ];
}
  /**
  *  从文件中解析对象时会调用
  *  在这个方法中说清楚哪些属性需要存储
  */
- (id)initWithCoder:(NSCoder *)decoder
{
     if  (self = [super init]) {
         // 读取文件的内容
         self.no = [decoder decodeObjectForKey:@ "no" ];
         self.age = [decoder decodeIntForKey:@ "age" ];
         self.height = [decoder decodeDoubleForKey:@ "height" ];
     }
     return  self;
}
     MJStudent *stu1 = [[MJStudent alloc] init];
     stu1.no = @ "42343254" ;
     stu1.age = 20;
     stu1.height = 1.55;
     MJStudent *stu2 = [[MJStudent alloc] init];
     stu2.no = @ "42343254" ;
     stu2.age = 20;
     stu2.height = 1.55;
     // 新建一块可变数据区
     NSMutableData *data = [NSMutableData data];
     // 将数据区连接到一个NSKeyedArchiver对象
     NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
     // 开始存档对象,存档的数据都会存储到NSMutableData中
     [archiver encodeObject:stu1 forKey:@ "person1" ];
     [archiver encodeObject:stu2 forKey:@ "person2" ];
     // 存档完毕(一定要调用这个方法)
     [archiver finishEncoding];
     // 将存档的数据写入文件
     [data writeToFile:path atomically:YES]
     // 从文件中读取数据
     NSData *data = [NSData dataWithContentsOfFile:path];
     // 根据数据,解析成一个NSKeyedUnarchiver对象
     NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
     MJStudent *stu11 = [unarchiver decodeObjectForKey:@ "stu1" ];
     MJStudent *stu22= [unarchiver decodeObjectForKey:@ "stu2" ];
     // 恢复完毕
     [unarchiver finishDecoding];
    如果父类也遵守了NSCoding协议,请注意:应该在encodeWithCoder:方法中加上一句
    [super encodeWithCode:encode];确保继承的实例变量也能被编码,即也能被归档
    应该在initWithCoder:方法中加上一句self = [super initWithCoder:decoder];
    确保继承的实例变量也能被解码,即也能被恢复
    利用解归档实现深复制
    通过解归档, 被归档的对象 ,再被解档后,内存地址已经不一样了,即实现了深复制


15. 数据库的线程安全

答:如果是coredata,需要将context放在主线程上;因为context统一负责数据库的读写操作。

        如果是使用FMDB,

16.GCD相关

(1). 全局队列与并行队列的区别

dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

1> 不需要创建,直接GET就能用

2> 两个队列的执行效果相同

3> 全局队列没有名称,调试时,无法确认准确队列

4> 全局队列有高中默认优先级

(2). 并行队列

dispatch_queue_t q =  dispatch_queue_create("ftxbird", DISPATCH_QUEUE_CONCURRENT);

(3). 串行队列

dispatch_queue_t t = dispatch_queue_create("ftxbird",DISPATCH_QUEUE_SERIAL);

(4). 开发中,跟踪当前线程

[NSThread currentThread]

(5). 并行队列的任务嵌套例子

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    dispatch_queue_t q = dispatch_queue_create( "ftxbird" , DISPATCH_QUEUE_CONCURRENT);
     // 任务嵌套
         dispatch_sync(q, ^{
             NSLog(@ "1 %@" , [NSThread currentThread]);
             dispatch_sync(q, ^{
                 NSLog(@ "2 %@" , [NSThread currentThread]);
                         dispatch_sync(q, ^{
                               NSLog(@ "3 %@" , [NSThread currentThread]);
                         });
              });
             dispatch_async(q, ^{
                 NSLog(@ "4 %@" , [NSThread currentThread]);
             });
              NSLog(@ "5 %@" , [NSThread currentThread]);
         });
  
// 运行结果是: 12345 或12354

(6). 主队列(线程)

1>每一个应用程序都只有一个主线程

2>所有UI的更新工作,都必须在主线程上执行!

3>主线程是一直工作的,而且除非将程序杀掉,否则主线程的工作永远不会结束!

dispatch_queue_t q = dispatch_get_main_queue();

(7).在主队列上更新UI的例子

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
      //创建代码块
     void  (^TaskOne)( void ) = ^( void )
     {
         NSLog(@ "Current thread = %@" , [NSThread currentThread]);
         NSLog(@ "Main thread = %@" , [NSThread mainThread]);
         [[[UIAlertView alloc] initWithTitle:@ "GCD"
                                     message:@ "Great Center Dispatcher"
                                    delegate:nil
                           cancelButtonTitle:@ "OK"
                           otherButtonTitles:nil, nil] show];
     };
     //取得分发队列
     dispatch_queue_t mainQueue = dispatch_get_main_queue();
     //提交任务
     dispatch_async(mainQueue, TaskOne);
}
     //简便写法
    dispatch_async( dispatch_get_main_queue(), ^( void )
     {
        NSLog(@ "Current thread = %@" , [NSThread currentThread]);
        NSLog(@ "Main thread = %@" , [NSThread mainThread]);
       
        [[[UIAlertView alloc] initWithTitle:@ "GCD"
                                    message:@ "Great Center Dispatcher"
                                   delegate:nil
                          cancelButtonTitle:@ "OK"
                          otherButtonTitles:nil, nil] show];
     });
  
//输出结果
//2014-05-02 20:34:27.872 serirl[835:60b] Current thread = <NSThread: 0x8e24540>{name = (null), num = 1}
//2014-05-02 20:34:27.873 serirl[835:60b] Main thread = <NSThread: 0x8e24540>{name = (null), num = 1}

NSOperation 多线程技术

(8). NSBlockOperation 简单使用

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//开发中一般给自定义队列定义为属性
@property (nonatomic, strong) NSOperationQueue *myQueue;
self.myQueue = [[NSOperationQueue alloc] init];
1>在自定义队列
  NSBlockOperation *block = [NSBlockOperation blockOperationWithBlock:^{
         NSLog(@ "%@" , [NSThread currentThread]);
     }]; 
   所有的自定义队列,都是在子线程中运行.
   [self.myQueue addOperation:block];
   或者:
    [self.myQueue addOperationWithBlock:^{
             NSLog(@ "%@" , [NSThread currentThread]);
         }];
  
2>在主队列中执行
     [[NSOperationQueue mainQueue] addOperationWithBlock:^{
         NSLog(@ "%@" , [NSThread currentThread]);
     }];
  
3> NSBlockOperation 的使用例子
  
   NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
         NSLog(@ "下载图片 %@" , [NSThread currentThread]);
     }];
     NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
         NSLog(@ "修饰图片 %@" , [NSThread currentThread]);
     }];
     NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
         NSLog(@ "保存图片 %@" , [NSThread currentThread]);
     }];
     NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
         NSLog(@ "更新UI %@" , [NSThread currentThread]);
     }];
    
     // 设定执行顺序, Dependency依赖,可能会开多个,但不会太多
     // 依赖关系是可以跨队列的!
     [op2 addDependency:op1];
     [op3 addDependency:op2];
     [op4 addDependency:op3];
     // GCD是串行队列,异步任务,只会开一个线程
    
     [self.myQueue addOperation:op1];
     [self.myQueue addOperation:op2];
     [self.myQueue addOperation:op3];
     // 所有UI的更新需要在主线程上进行
     [[NSOperationQueue mainQueue] addOperation:op4];

(9). NSInvocationOperation 简单使用

?
1
2
3
4
5
6
  NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(demoOp:) object:@ "hello op" ];
  
- ( void )demoOp:(id)obj
{
     NSLog(@ "%@ - %@" , [NSThread currentThread], obj);
}

(10). performSelectorOnMainThread 方法使用

?
1
2
3
4
5
6
7
8
9
10
11
12
13
       // 1> 模拟下载,延时
         [NSThread sleepForTimeInterval:1.0];
         // 2> 设置图像,苹果底层允许使用performSelectorInBackground方法
         // 在后台线程更新UI,强烈不建议大家这么做!
         // YES会阻塞住线程,直到调用方法完成
         // NO不会阻塞线程,会继续执行
   [self performSelectorOnMainThread:@selector(setImage:) withObject:[UIImage imageNamed:imagePath] waitUntilDone:NO];
       // 1. 图像
     - ( void )setImage:(UIImage *)image
       {  
         self.imageView.image = image;
         [self.imageView sizeToFit];
       }

(11).提问:代码存在什么问题?如果循环次数非常大,会出现什么问题?应该如何修改?

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 解决办法1:如果i比较大,可以在for循环之后@autoreleasepool
  // 解决方法2:如果i玩命大,一次循环都会造成
           自动释放池被填满,一次循环就@autoreleasepool
     for  ( int  i = 0; i < 10000000; ++i) {
         @autoreleasepool {
             // *
             NSString *str = @ "Hello World!" ;
             // new *
             str = [str uppercaseString];
             // new *
             str = [NSString stringWithFormat:@ "%@ %d" , str, i];
            
             NSLog(@ "%@" , str);
         }
     }

17.App 性能优化

(1). ARC管理内存

ARC(Automatic Reference Counting, 自动引用计数)和iOS5一起发布,它避免了最常见的也就是经常是由于我们忘记释放内存所造成的内存泄露。它自动为你管理retain和release的过程,所以你就不必去手动干预了。

下面是你会经常用来去创建一个View的代码段:

?
1
2
3
4
  UIView *view =   [[UIView alloc] init];
  // ...
  [self.view addSubview:view];
  [view release];


忘掉代码段结尾的release简直像记得吃饭一样简单。而ARC会自动在底层为你做这些工作。

除了帮你避免内存泄露,ARC还可以帮你提高性能,它能保证释放掉不再需要的对象的内存。这都啥年代了,你应该在你的所有项目里使用ARC!

ARC当然不能为你排除所有内存泄露的可能性。由于阻塞, retain 周期, 管理不完善的CoreFoundation object(还有C结构)或者就是代码太烂依然能导致内存泄露。

(2).在正确的地方使用reuseIdentifier

一个开发中常见的错误就是没有给UITableViewCells, UICollectionViewCells,甚至是UITableViewHeaderFooterViews设置正确的reuseIdentifier。

为了性能最优化,table view `tableView:cellForRowAtIndexPath:` rows分配cells的时候,它的数据应该重用自UITableViewCell 一个table view维持一个队列的数据可重用的UITableViewCell对象。

不使用reuseIdentifier的话,每显示一行table view就不得不设置全新的cell。这对性能的影响可是相当大的,尤其会使app的滚动体验大打折扣。

iOS6起,除了UICollectionViewcells和补充views,你也应该在headerfooter views中使用reuseIdentifiers

想要使用reuseIdentifiers的话,在一个table view中添加一个新的cell时在data source object中添加这个方法:

?
1
<span style= "font-size: 18px;" static  NSString   *CellIdentifier = @ "Cell" ;<br> UITableViewCell *cell = [tableView  dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];</span>

这个方法把那些已经存在的cell从队列中排除,或者在必要时使用先前注册的nib或者class创造新的cell。如果没有可重用的cell,你也没有注册一个class或者nib的话,这个方法返回nil

(3).尽量把views设置为不透明

如果你有透明的Views你应该设置它们的opaque属性为YES。

原因是这会使系统用一个最优的方式渲染这些views。这个简单的属性在IB或者代码里都可以设定。

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

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

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

你可以在模拟器中用Debug\Color Blended Layers选项来发现哪些view没有被设置为opaque。目标就是,能设为opaque的就全设为opaque!

(4).避免过于庞大的Xib文件

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

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

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

当加载XIB是,所有图片都被chache

(5).不要阻塞主线程

永远不要使主线程承担过多。因为UIKit在主线程上做所有工作,渲染,管理触摸反应,回应输入等都需要在它上面完成。

一直使用主线程的风险就是如果你的代码真的block了主线程,你的app会失去反应。大部分阻碍主进程的情形是你的app在做一些牵涉到读写外部资源的I/O操作,比如存储或者网络。

你可以使用`NSURLConnection`异步地做网络操作:
+ (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler

或者使用像 AFNetworking这样的框架来异步地做这些操作。如果你需要做其它类型的需要耗费巨大资源的操作(比如时间敏感的计算或者存储读写)那就用 Grand Central Dispatch,或者 NSOperation  NSOperationQueues.

下面代码是使用GCD的模板

?
1
<span style= "font-size: 18px;" >dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,   0), ^{<br>     // switch to a background thread and perform   your expensive operation<br> <br>    dispatch_async(dispatch_get_main_queue(),   ^{<br>        // switch back to the   main thread to update your UI<br> <br>    });<br>});<br></span>


发现代码中有一个嵌套的`dispatch_async`吗?这是因为任何UIKit相关的代码需要在主线程上进行。

(6).Image Views中调整图片大小

如果要在`UIImageView`中显示一个来自bundle的图片,你应保证图片的大小和UIImageView的大小相同。在运行中缩放图片是很耗费资源的,特别是`UIImageView`嵌套在`UIScrollView`中的情况下。

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

(7). 选择正确的Collection

Arrays: 有序的一组值。使用indexlookup很快,使用value lookup很慢, 插入/删除很慢。

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

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

(8). 打开gzip压缩

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

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

减小文档的一个方式就是在服务端和你的app中打开gzip。这对于文字这种能有更高压缩率的数据来说会有更显著的效用。

好消息是,iOS已经在NSURLConnection中默认支持了gzip压缩,当然AFNetworking这些基于它的框架亦然。像Google App Engine这些云服务提供者也已经支持了压缩输出。

(9). 重用和延迟加载(lazy load) Views

更多的view意味着更多的渲染,也就是更多的CPU和内存消耗,对于那种嵌套了很多viewUIScrollView里边的app更是如此。

这里我们用到的技巧就是模仿`UITableView``UICollectionView`的操作不要一次创建所有的subview,而是当需要时才创建,当它们完成了使命,把他们放进一个可重用的队列中。

这样的话你就只需要在滚动发生时创建你的views,避免了不划算的内存分配。

创建views的能效问题也适用于你app的其它方面。想象一下一个用户点击一个按钮的时候需要呈现一个view的场景。有两种实现方法:

 1. 创建并隐藏这个view当这个screen加载的时候,当需要时显示它;

 2. 当需要时才创建并展示。

每个方案都有其优缺点。

用第一种方案的话因为你需要一开始就创建一个view并保持它直到不再使用,这就会更加消耗内存。然而这也会使你的app操作更敏感因为当用户点击按钮的时候它只需要改变一下这个view的可见性。

第二种方案则相反-消耗更少内存,但是会在点击按钮的时候比第一种稍显卡顿。

(10). Cache, Cache, 还是Cache!

一个极好的原则就是,缓存所需要的,也就是那些不大可能改变但是需要经常读取的东西。

我们能缓存些什么呢?一些选项是,远端服务器的响应,图片,甚至计算结果,比如UITableView的行高。

NSURLConnection默认会缓存资源在内存或者存储中根据它所加载的HTTP Headers。你甚至可以手动创建一个NSURLRequest然后使它只加载缓存的值。

下面是一个可用的代码段,你可以可以用它去为一个基本不会改变的图片创建一个NSURLRequest并缓存它:

?
1
2
3
4
5
6
7
8
9
10
+ (NSMutableURLRequest   *)imageRequestWithURL:(NSURL *)url {
     NSMutableURLRequest *request =   [NSMutableURLRequest requestWithURL:url];
  
     request.cachePolicy =   NSURLRequestReturnCacheDataElseLoad; // this will make sure the request always   returns the cached image
     request.HTTPShouldHandleCookies = NO;
     request.HTTPShouldUsePipelining = YES;
     [request   addValue:@ "image/*" forHTTPHeaderField:@ "Accept" ];
  
     returnrequest;
}

注意你可以通过 NSURLConnection 获取一个URL request AFNetworking也一样的。这样你就不必为采用这条tip而改变所有的networking代码了。

如果你需要缓存其它不是HTTP Request的东西,你可以用NSCache

NSCacheNSDictionary类似,不同的是系统回收内存的时候它会自动删掉它的内容。

(11). 权衡渲染方法

iOS中可以有很多方法做出漂亮的按钮。你可以用整幅的图片,可调大小的图片,uozhe可以用CALayerCoreGraphics甚至OpenGL来画它们。

当然每个不同的解决方法都有不同的复杂程度和相应的性能。

简单来说,就是用事先渲染好的图片更快一些,因为如此一来iOS就免去了创建一个图片再画东西上去然后显示在屏幕上的程序。问题是你需要把所有你需要用到的图片放到appbundle里面,这样就增加了体积  这就是使用可变大小的图片更好的地方了你可以省去一些不必要的空间,也不需要再为不同的元素(比如按钮)来做不同的图。

然而,使用图片也意味着你失去了使用代码调整图片的机动性,你需要一遍又一遍不断地重做他们,这样就很浪费时间了,而且你如果要做一个动画效果,虽然每幅图只是一些细节的变化你就需要很多的图片造成bundle大小的不断增大。

总得来说,你需要权衡一下利弊,到底是要性能能还是要bundle保持合适的大小。

(12). 处理内存警告

一旦系统内存过低,iOS会通知所有运行中app。在官方文档中是这样记述:

如果你的app收到了内存警告,它就需要尽可能释放更多的内存。最佳方式是移除对缓存,图片object和其他一些可以重创建的objectsstrong references.

幸运的是,UIKit提供了几种收集低内存警告的方法:

app delegate中使用“applicationDidReceiveMemoryWarning:”的方法

在你的自定义UIViewController的子类(subclass)中覆盖`didReceiveMemoryWarning`

注册并接收 UIApplicationDidReceiveMemoryWarningNotification 的通知

一旦收到这类通知,你就需要释放任何不必要的内存使用。

例如,UIViewController的默认行为是移除一些不可见的view 它的一些子类则可以补充这个方法,删掉一些额外的数据结构。一个有图片缓存的app可以移除不在屏幕上显示的图片。

这样对内存警报的处理是很必要的,若不重视,你的app就可能被系统杀掉。

然而,当你一定要确认你所选择的object是可以被重现创建的来释放内存。一定要在开发中用模拟器中的内存提醒模拟去测试一下。

(13). 重用大开销对象

一些objects的初始化很慢,比如NSDateFormatterNSCalendar。然而,你又不可避免地需要使用它们,比如从JSON或者XML中解析数据。

想要避免使用这个对象的瓶颈你就需要重用他们,可以通过添加属性到你的class里或者创建静态变量来实现。

注意如果你要选择第二种方法,对象会在你的app运行时一直存在于内存中,和单例(singleton)很相似。

下面的代码说明了使用一个属性来延迟加载一个date formatter. 第一次调用时它会创建一个新的实例,以后的调用则将返回已经创建的实例:

?
1
2
3
4
5
6
7
8
9
10
11
// in your .h or   inside a class extension
@property (nonatomic,   strong) NSDateFormatter *formatter;
// inside the   implementation (.m)
// When you need, just   use self.formatter
- (NSDateFormatter   *)formatter {
     if (! _formatter) {
         _formatter = [[NSDateFormatter alloc]   init];
         _formatter.dateFormat = @ "EEE   MMM dd HH:mm:ss Z yyyy" ; // twitter date format
     }
     return_formatter;
}

还需要注意的是,其实设置一个NSDateFormatter的速度差不多是和创建新的一样慢的!所以如果你的app需要经常进行日期格式处理的话,你会从这个方法中得到不小的性能提升。

(14). 避免反复的处理数据

许多应用需要从服务器加载功能所需的常为JSON或者XML格式的数据。在服务器端和客户端使用相同的数据结构很重要。在内存中操作数据使它们满足你的数据结构是开销很大的。

比如你需要数据来展示一个table view,最好直接从服务器取array结构的数据以避免额外的中间数据结构改变。

类似的,如果需要从特定key中取数据,那么就使用键值对的dictionary

(15). 选择正确的数据格式

app和网络服务间传输数据有很多方案,最常见的就是JSONXML。你需要选择对你的app来说最合适的一个。

解析JSON会比XML更快一些,JSON也通常更小更便于传输。从iOS5起有了官方内建的JSON deserialization 就更加方便使用了。

但是XML也有XML的好处,比如使用SAX 来解析XML就像解析本地文件一样,你不需像解析json一样等到整个文档下载完成才开始解析。当你处理很大的数据的时候就会极大地减低内存消耗和增加性能。

(16). 正确设定背景图片

View里放背景图片就像很多其它iOS编程一样有很多方法:

使用UIColor colorWithPatternImage来设置背景色;

view中添加一个UIImageView作为一个子View

如果你使用全画幅的背景图,你就必须使用UIImageView因为UIColorcolorWithPatternImage是用来创建小的重复的图片作为背景的。这种情形下使用UIImageView可以节约不少的内存:

// You could also   achieve the same result in Interface Builder

UIImageView   *backgroundView = [[UIImageView alloc] initWithImage:[UIImage   imageNamed:@"background"]];

[self.view addSubview:backgroundView];

如果你用小图平铺来创建背景,你就需要用UIColorcolorWithPatternImage来做了,它会更快地渲染也不会花费很多内存:

self.view.backgroundColor   = [UIColor colorWithPatternImage:[UIImage   imageNamed:@"background"]];(17). 

(18). 减少使用UIWebView

UIWebView很有用,用它来展示网页内容或者创建UIKit很难做到的动画效果是很简单的一件事。

但是你可能有注意到UIWebView并不像驱动Safari的那么快。这是由于以JIT compilation 为特色的WebkitNitro Engine的限制。

所以想要更高的性能你就要调整下你的HTML了。第一件要做的事就是尽可能移除不必要的javascript,避免使用过大的框架。能只用原生js就更好了。

另外,尽可能异步加载例如用户行为统计script这种不影响页面表达的javascript

(19).  设定Shadow Path

如何在一个View或者一个layer上加一个shadow呢,QuartzCore框架是很多开发者的选择:

?
1
2
3
4
5
6
7
#import   <QuartzCore/QuartzCore.h>
// Somewhere later ...
UIView *view =   [[UIView alloc] init];
// Setup the shadow   ...
view.layer.shadowOffset   = CGSizeMake(-1.0f, 1.0f);
view.layer.shadowRadius   = 5.0f;
view.layer.shadowOpacity   = 0.6;

看起来很简单,对吧。

可是,坏消息是使用这个方法也有它的问题 Core Animation不得不先在后台得出你的图形并加好阴影然后才渲染,这开销是很大的。

使用shadowPath的话就避免了这个问题:

view.layer.shadowPath = [[UIBezierPath bezierPathWithRect:view.bounds] CGPath];

使用shadow path的话iOS就不必每次都计算如何渲染,它使用一个预先计算好的路径。但问题是自己计算path的话可能在某些View中比较困难,且每当viewframe变化的时候你都需要去update shadow path.

(20).  优化Table View

Table view需要有很好的滚动性能,不然用户会在滚动过程中发现动画的瑕疵。

为了保证table view平滑滚动,确保你采取了以下的措施:

·       正确使用`reuseIdentifier`来重用cells

·       尽量使所有的view opaque,包括cell自身

·       避免渐变,图片缩放,后台选人

·       缓存行高

·       如果cell内现实的内容来自web,使用异步加载,缓存请求结果

·       使用`shadowPath`来画阴影

·       减少subviews的数量

·       尽量不适用`cellForRowAtIndexPath:`,如果你需要用到它,只用一次然后缓存结果

·       使用正确的数据结构来存储数据

·       使用`rowHeight`, `sectionFooterHeight`  `sectionHeaderHeight`来设定固定的高,不要请求delegate

(21).  选择正确的数据存储选项

当存储大块数据时你会怎么做?

你有很多选择,比如:

·       使用`NSUerDefaults`

·       使用XML, JSON, 或者 plist

·       使用NSCoding存档

·       使用类似SQLite的本地SQL数据库

·       使用 Core Data

NSUserDefaults的问题是什么?虽然它很nice也很便捷,但是它只适用于小数据,比如一些简单的布尔型的设置选项,再大点你就要考虑其它方式了

XML这种结构化档案呢?总体来说,你需要读取整个文件到内存里去解析,这样是很不经济的。使用SAX又是一个很麻烦的事情。

NSCoding?不幸的是,它也需要读写文件,所以也有以上问题。

在这种应用场景下,使用SQLite 或者 Core Data比较好。使用这些技术你用特定的查询语句就能只加载你需要的对象。

在性能层面来讲,SQLiteCore Data是很相似的。他们的不同在于具体使用方法。Core Data代表一个对象的graph model,但SQLite就是一个DBMSApple在一般情况下建议使用Core Data,但是如果你有理由不使用它,那么就去使用更加底层的SQLite吧。

如果你使用SQLite,你可以用FMDB(https://GitHub.com/ccgus/fmdb)这个库来简化SQLite的操作,这样你就不用花很多经历了解SQLiteC API了。

(22).  加速启动时间

快速打开app是很重要的,特别是用户第一次打开它时,对app来讲,第一印象太太太重要了。

你能做的就是使它尽可能做更多的异步任务,比如加载远端或者数据库数据,解析数据。

还是那句话,避免过于庞大的XIB,因为他们是在主线程上加载的。所以尽量使用没有这个问题的Storyboards吧!

注意,用Xcode debugwatchdog并不运行,一定要把设备从Xcode断开来测试启动速度

(23). 使用Autorelease Pool

`NSAutoreleasePool`负责释放block中的autoreleased objects。一般情况下它会自动被UIKit调用。但是有些状况下你也需要手动去创建它。

假如你创建很多临时对象,你会发现内存一直在减少直到这些对象被release的时候。这是因为只有当UIKit用光了autorelease pool的时候memory才会被释放。

好消息是你可以在你自己的@autoreleasepool里创建临时的对象来避免这个行为:

?
1
2
3
4
5
6
7
8
9
NSArray *urls = <#   An array of file URLs #>;
for (NSURL *url in urls) {
     @autoreleasepool {
         NSError *error;
         NSString *fileContents = [NSString   stringWithContentsOfURL:url
                                            encoding:NSUTF8StringEncoding error:&error];
         /* Process the string,   creating and autoreleasing more objects. */
     }
}


这段代码在每次遍历后释放所有autorelease对象

(24). 选择是否缓存图片

常见的从bundle中加载图片的方式有两种,一个是用`imageNamed`,二是用`imageWithContentsOfFile`,第一种比较常见一点。

既然有两种类似的方法来实现相同的目的,那么他们之间的差别是什么呢?

`imageNamed`的优点是当加载时会缓存图片。`imageNamed`的文档中这么说:
这个方法用一个指定的名字在系统缓存中查找并返回一个图片对象如果它存在的话。如果缓存中没有找到相应的图片,这个方法从指定的文档中加载然后缓存并返回这个对象。

相反的,`imageWithContentsOfFile`仅加载图片。

下面的代码说明了这两种方法的用法:

?
1
2
3
UIImage *img =   [UIImage imageNamed:@ "myImage" ]; // caching
// or
UIImage *img = [UIImage   imageWithContentsOfFile:@ "myImage" ]; // no caching


那么我们应该如何选择呢?

如果你要加载一个大图片而且是一次性使用,那么就没必要缓存这个图片,用`imageWithContentsOfFile`足矣,这样不会浪费内存来缓存它。

然而,在图片反复重用的情况下`imageNamed`是一个好得多的选择。

(25).  避免日期格式转换

如果你要用`NSDateFormatter`来处理很多日期格式,应该小心以待。就像先前提到的,任何时候重用`NSDateFormatters`都是一个好的实践。

然而,如果你需要更多速度,那么直接用C是一个好的方案。Sam Soffes有一个不错的帖子(http://soff.es/how-to-drastically-improve-your-app-with-an-afternoon-and-instruments)里面有一些可以用来解析ISO-8601日期字符串的代码,简单重写一下就可以拿来用了。

嗯,直接用C来搞,看起来不错了,但是你相信吗,我们还有更好的方案!

如果你可以控制你所处理的日期格式,尽量选择Unix时间戳。你可以方便地从时间戳转换到NSDate:

?
1
2
3
-   (NSDate*)dateFromUnixTimestamp:(NSTimeInterval)timestamp {
  return [NSDate   dateWithTimeIntervalSince1970:timestamp];
  }


这样会比用C来解析日期字符串还快!

需要注意的是,许多web API会以微秒的形式返回时间戳,因为这种格式在javascript中更方便使用。

18.线程间怎么通信

1> performSelector:onThread:withObject:waitUntilDone:
2> NSMachPort

(基本机制:A线程(父线程)创建NSMachPort对象,并加入A线程的run loop。当创建B线程(辅助线程)时,将创建的NSMachPort对象传递到主体入口点,B线程(辅助线程)就可以使用相同的端口对象将消息传回A线程(父线程)。


19.网络图片处理问题中怎么解决一个相同的网络地址重复请求的问题?

答:利用字典(图片地址为key,下载操作为value)


20.用NSOpertion和NSOpertionQueue处理A,B,C三个线程,要求执行完A,B后才能执行C,怎么做?

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 创建3个操作
NSOperation *a = [NSBlockOperation blockOperationWithBlock:^{
     NSLog(@”operation1---“);
}];
NSOperation *b = [NSBlockOperation blockOperationWithBlock:^{
     NSLog(@”operation1---“);
}];
NSOperation *c = [NSBlockOperation blockOperationWithBlock:^{
     NSLog(@”operation1---“);
}];
// 添加依赖
[c addDependency:a];
[c addDependency:b];
// 执行操作
[queue addOperation:a];
[queue addOperation:b];
[queue addOperation:c];

21.列举cocoa中常见对几种多线程的实现,并谈谈多线程安全的几种解决办法及多线程安全怎么控制?

NSThread、NSOperation、GCD

1> 只在主线程刷新访问UI

2> 如果要防止资源抢夺,得用synchronized进行加锁保护

3> 如果异步操作要保证线程安全等问题, 尽量使用GCD(有些函数默认就是安全的)


22.GCD内部是怎么实现的

1> 只在主线程刷新访问UI

2> 如果要防止资源抢夺,得用synchronized进行加锁保护

3> 如果异步操作要保证线程安全等问题, 尽量使用GCD(有些函数默认就是安全的)


23.你用过NSOperationQueue么?如果用过或者了解的话,你为什么要使用NSOperationQueue,实现了什么?请描述它和GCD的区别和类似的地方(提示:可以从两者的实现机制和适用范围来描述)。

1> GCD是纯C语言的API,NSOperationQueue是基于GCD的OC版本封装

2> GCD只支持FIFO的队列,NSOperationQueue可以很方便地调整执行顺序、设置最大并发数量 (FIFO 就是先进先出)

NSOperationQueue设置最大并发数量的方法 


3> NSOperationQueue可以在轻松在Operation间设置依赖关系,而GCD需要写很多的代码才能实现

4> NSOperationQueue支持KVO(键值观察者),可以监测operation是否正在执行(isExecuted)、是否结束(isFinished),是否取消(isCanceld)


5> GCD的执行速度比NSOperationQueue(封装GCD,更高层的东西,性能不好(因为还要转换成GCD).快


如何选择两个:

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

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


24.既然提到GCD,那么问一下在使用GCD以及block时要注意些什么?它们两是一回事儿么?block在ARC中和传统的MRC中的行为和用法有没有什么区别,需要注意些什么?

Block的使用注意:

  1. block的内存管理(注意循环引用,默认在栈中(不需要内存管理),通过copy就在在堆中,就要注意内存管理)

  2. 防止循环retian

  • 非ARC(MRC):__block

  • ARC:__weak\__unsafe_unretained



25.在异步线程中下载很多图片,如果失败了,该如何处理?请结合RunLoop来谈谈解决方案.(提示:在异步线程中启动一个RunLoop重新发送网络请求,下载图片)

1> 重新下载图片

2> 下载完毕, 利用RunLoop的输入源回到主线程刷新UIImageView

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值