1.堆和栈的区别
1.内存管理范围
只有oc对象需要进行内存管理
2.内存管理本质
因为:Objective-C的对象在内存中是以堆的方式分配空间的,并且堆内存是由你释放的,就是release
OC对象存放于堆里面(堆内存要程序员手动回收)
非OC对象一般放在栈里面(栈内存会被系统自动回收)
堆里面的内存是动态分配的,所以也就需要程序员手动的去添加内存、回收内存
3.内存分配以及管理方式
按分配方式分
堆是动态分配和回收内存的,没有静态分配的堆
栈有两种分配方式:静态分配和动态分配
静态分配是系统编译器完成的,比如局部变量的分配
动态分配是有alloc函数进行分配的,但是栈的动态分配和堆是不同的,它的动态分配也由系统编译器进行释放,不需要程序员手动管理
按管理方式分
对于栈来讲,是由系统编译器自动管理,不需要程序员手动管理
对于堆来讲,释放工作由程序员手动管理,不及时回收容易产生内存泄露
堆:是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程 初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。堆里面一般 放的是静态数据,比如static的数据和字符串常量等,资源加载后一般也放在堆里面。一个进程的所有线程共有这些堆 ,所以对堆的操作要考虑同步和互斥的问题。程序里面编译后的数据段都是堆的一部分。
栈:是个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此 ,栈是 thread safe的。每个c++对象的数据成员也存在在栈中,每个函数都有自己的栈,栈被用来在函数之间传递参数。操作系统在切换线程的时候会自动的切换栈,就是切换ss/esp寄存器。栈空间不需要在高级语言里面显式的分配 和释放。
一句话总结就是 堆:由程序员分配和释放,如果不释放可能会引起内存泄漏 栈:由编译器自动分配和释放,一般存放参数值,局部变量
2.KVC和KVO
KVC是指NSKeyValueCoding(键值编码),提供一种机制来间接访问对象的属性。KVC 就是基于KVO技术来实现的。
KVO,提供了一种观察者的机制,通过对某个对象的某个属性添加观察者,当该属性改变,就会调用"observeValueForKeyPath:"方法。
3.OC中创建线程的方法是什么?如果在主线程中执行代码, 方法是什么?如果想延时执行代码、方法是什么?
1、线程创建有三种方法:使用NSThread创建、使用GCD的dispatch、使用子类化的NSOperation,然后将其加入NSOperationQueue;
2、在主线程执行代码,方法是performSelectorOnMainThread:withObject:waitUntilDone:;
3、如果想延时执行代码可以用performSelector:onThread:withObject:waitUntilDone:;
4.指针与数组名的关系?
int arrayName[4] = {10, 20, 30, 40};
int *p = (int *)(&arrayName + 1);
NSLog(@"%d", (&arrayName - 1));
1.(&arrayName + 1):&arrayName是数组的地址(等价于指向arrayName数组的指针)
2.增加 1 会往后移动16个字节,开始是4个字节的位置,移动后就是16个字节后面的位置(也 就是目前位置是20个字节)
3.最后又赋值给,int类型的指针p(int类型占4个字节)
4.所以(p - 1)就是减去4个字节,变成为16个字节的位置,输出的
(p - 1)值为40
int *p = (int *)(&arrayName + 1);
NSLog(@"%d", *(p - 1));//输出结果为 40
5..#import 和#include有 么区别?@class呢?#import <>和 #import"" 有 么区别?
import是OC导入头文件的关键字,#include是C/C++导入头文件的关键字
使用#import只导入一次不会重复导入,相当于#include和#pragma once;(使用#include可能会出现循环引用,使用#pragma once消除这种可能)
@class一般用于声明某个字符串作为类名使用,它只是声明了一个类名,没有导入.h文件中的内容,不会引起交叉编译问题
import< >代表导入系统自带的框架
import" "代表导入我们自己创建的文件,导入的使我们.h文件,也就是头文件
6.属性readwrite.readonly,assign,retain,copy,nonatomic 什么作用 ? 在哪种情况下 ?
readwrite 是可读可写特性;需要生成getter方法和setter方法时(补充:默认属性,将生成不带额外参数的getter和setter方法(setter方法只有一个参数))
readonly 是只读特性 只会生成getter方法 不会生成setter方法 ;不希望属性在类外改变
assign 是赋值特性,setter方法将传入参数赋值给实例变量;仅设置变量时;
retain 表示持有特性,setter方法将传入参数先保留,再赋值,传入参数的retaincount会+1;
copy 表示拷贝特性,setter方法将传入对象复制一份;需要完全一份新的变量时。
nonatomic 非原子操作,决定编译器生成的setter getter是否是原子操作,atomic表示多线程安全,一般使用nonatomic
7.对程序性能的优化你有什么建议?
1.使用复用机制
2.尽可能设置 View 为不透明
3.避免臃肿的 XIB 文件
4.不要阻塞主线程
5.图片尺寸匹配 UIImageView
6.选择合适的容器
7.启用 GZIP 数据压缩
8.View 的复用和懒加载机制
9、缓存
服务器的响应信息(response)。
图片。
计算值。比如:UITableView 的 row heights。
10.关于图形绘制
11.处理 Memory Warnings
在 AppDelegate 中实现 - [AppDelegate applicationDidReceiveMemoryWarning:] 代理方法。
在 UIViewController 中重载 didReceiveMemoryWarning 方法。
监听 UIApplicationDidReceiveMemoryWarningNotification 通知。
12.复用高开销的对象
13.减少离屏渲染(设置圆角和阴影的时候可以选用绘制的方法)
14.优化 UITableView
通过正确的设置 reuseIdentifier 来重用 Cell。
尽量减少不必要的透明 View。
尽量避免渐变效果、图片拉伸和离屏渲染。
当不同的行的高度不一样时,尽量缓存它们的高度值。
如果 Cell 展示的内容来自网络,确保用异步加载的方式来获取数据,并且缓存服务器的 response。
使用 shadowPath 来设置阴影效果。
尽量减少 subview 的数量,对于 subview 较多并且样式多变的 Cell,可以考虑用异步绘制或重写 drawRect。
尽量优化 - [UITableView tableView:cellForRowAtIndexPath:] 方法中的处理逻辑,如果确实要做一些处理,可以考虑做一次,缓存结果。
选择合适的数据结构来承载数据,不同的数据结构对不同操作的开销是存在差异的。
对于 rowHeight、sectionFooterHeight、sectionHeaderHeight 尽量使用常量。
15.选择合适的数据存储方式
在 iOS 中可以用来进行数据持有化的方案包括:
NSUserDefaults。只适合用来存小数据。
XML、JSON、Plist 等文件。JSON 和 XML 文件的差异在「选择正确的数据格式」已经说过了。
使用 NSCoding 来存档。NSCoding 同样是对文件进行读写,所以它也会面临必须加载整个文件才能继续的问题。
使用 SQLite 数据库。可以配合 FMDB 使用。数据的相对文件来说还是好处很多的,比如可以按需取数据、不用暴力查找等等。
使用 CoreData。也是数据库技术,跟 SQLite 的性能差异比较小。但是 CoreData 是一个对象图谱模型,显得更面向对象;SQLite 就是常规的 DBMS。
16.减少应用启动时间
快速启动应用对于用户来说可以留下很好的印象。尤其是第一次使用时。
保证应用快速启动的指导原则:
尽量将启动过程中的处理分拆成各个异步处理流,比如:网络请求、数据库访问、数据解析等等。
避免臃肿的 XIB 文件,因为它们会在你的主线程中进行加载。重申:Storyboard 没这个问题,放心使用。
注意:在测试程序启动性能的时候,最好用与 Xcode 断开连接的设备进行测试。因为 watchdog 在使用 Xcode 进行调试的时候是不会启动的。
17.使用 Autorelease Pool (内存释放池)
18.imageNamed 和 imageWithContentsOfFile
8.runloop 和线程有什么关系?
runloop与线程是一一对应的,一个runloop对应一个核心的线程,为什么说是核心的,是因为runloop是可以嵌套的,但是核心的只能有一个,他们的关系保存在一个全局的字典里。
runloop是来管理线程的,当线程的runloop被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务。
runloop在第一次获取时被创建,在线程结束时被销毁。
对于主线程来说,runloop在程序一启动就默认创建好了。
对于子线程来说,runloop是懒加载的,只有当我们使用的时候才会创建,所以在子线程用定时器要注意:确保子线程的runloop被创建,不然定时器不会回调。
9.为什么说OC是一门动态语言
动态类型识别(Dynamic typing):最终判定该类的实例类型是在运行期间
动态绑定(Dynamic binding):在运行时确定调用的方法
动态加载(Dynamic loading):在运行期间可添加模块(类、方法)
程序在运行时可以改变其结构,新的函数可以被引进,已有的函数可以被删除等在结构上的变化
所谓的动态类型语言,就是类型的检查是在运行时做的。
10.NSString为什么要用copy关键字,如果用strong会有什么问题?
所以,如果一般情况下,我们都不希望字串的值跟着str变化,所以我们一般用copy来设置string的属性。
如果希望字串的值跟着赋值的字串的值变化,可以使用strong。
网友评论