1.堆和栈的区别
栈,是由编译器自动管理,无需我们手工控制;
堆,释放工作由程序员控制,容易产生memory leak(内存泄漏)。
申请大小:
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。
分配方式:
1. 堆都是动态分配的,没有静态分配的堆。
2. 栈有2种分配方式:静态分配和动态分配。
2.死锁问题
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"11111");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"22222");
});
NSLog(@"33333");
}
//死锁原因
dispatch_sync
在等待block
语句执行完成,而block
语句需要在主线程里执行,所以dispatch_sync
如果在主线程调用就会造成死锁dispatch_sync
是同步的,本身就会阻塞当前线程,也即主线程。而又往主线程里塞进去一个block
,所以就会发生死锁。
/**正确方法**/
//async 在主线程中 创建了一个异步线程 加入 全局并发队列,async 不会等待block 执行完成,立即返回
dispatch_async(dispatch_get_global_queue(), ^{
NSLog(@2);//不会造成死锁;
});
分析这段代码:view DidLoad
在主线程中,也即dispatch_get_main_queue()
中,执行到sync
时向dispatch_get_main_queue()
插入同步thread
,sync
会等到后面的block
执行完成才返回。
sync
又在主队列里面,是个串行队列,sync
是后面才加入的,前面一个是主线程,所以sync
想执行block
必须等待前一个主线程执行完成,而主线程却在等待sync
返回,去执行后续工作,从而造成死锁。
注意: dispatch_sync 和 dispatch_async 区别:
dispatch_async(queue,block) async
异步队列,dispatch_async
函数会立即返回, block
会在后台异步执行。
dispatch_sync(queue,block) sync
同步队列,dispatch_sync
函数不会立即返回,即阻塞当前线程,等待 block
同步执行完成。
GCD Queue 分为三种:
1.The main queue:
主队列,主线程就是在个队列中。
2.Global queues:
全局并发队列。
3.用户队列:是用函数dispatch_queue_create
创建的自定义队列
3.UIImage初始化方法的区别
- 方法一:
UIImage *image = [UIImage imageNamed:@"test.png"];
这个方法创建的图片是从缓存里面获取的,先在缓存里查看,看是不是有这个图片,没有的话见图片添加到缓存再使用。有的话直接使用缓存里面的。在程序中,如果这个图片要在多个地方使用的话,建议使用这个方法。缺点是:一旦加入到缓存中就一直占用内存,不能被释放掉。
- 方法二:
//读取本地图片路径
NSString *imagePath=[NSString stringWithFormat:@"%@/Documents/
%@.jpg",NSHomeDirectory(),@"test"];
[UIImage imageWithContentsOfFile:imagePath];
从手机本地读取,比较第一种方式,这个是直接加载图片的,图片不需要的时候,可以release掉。所以建议在使用重复率低的地方使用这种方法。
- 方法三:
// 下面的这种方式会出现卡线程的情况,所以建议在子线程中操作
// imageWithData: data
NSURL *url = [NSURL URLWithString:@“http://e.picphotos.baidu.com/album/abc.jpg"];
UIImage *image2 = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
需要注意的是,如果imageWithData:是同步网络请求,如果在主线程直接使用的话,会卡主线程,因此一般不会在主线程中直接使用,而是采用异步网络请求获得data的值。
4. iOS中self.和下划线的区别
1.首先通过self.xxx 通过访问的方法的引用:包含了set和get方法。而通过下划线是获取自己的实例变量,不包含set和get的方法。(回答面试官这一句就行了)
2.
self.xxx
是对属性的访问;而_xxx
是对局部变量的访问。所有被声明为属性的成员,再ios5之前需要使用编译指令@synthesize
来告诉编译器帮助生成属性的getter和setter方法,之后这个指令可以不用认为的指定了,默认情况下编译器会帮助我们生成。
编译器在生成getter,setter方法时是有优先级的,他首先查找当前的类中用户是否定义属性的getter,setter方法,如果有,则编译器会跳过,不会再生成,使用用户定义的方法。也就是说你在使用self.xxx时是调用一个getter方法。
会使引用计数加一,而_xxx不会使用引用技术加一的。
3.所有使用self.xxx是更好的选择,因为这样可以兼容懒加载,同时也避免了使用下滑线的时候忽略了self这个指针,后者容易在BLock中造成循环引用。同时,使用 _是获取不到父类的属性,因为它只是对局部变量的访问。
最后总结:self方法实际上是用了get和set方法间接调用,下划线方法是直接对变量操作。
5. 总结UITableViewCell重用机制
/*
UITableView内部定义了两种数据结构:
NSMutableArray: visiableCells
NSMutableDictionary:reuseTableCells
其中 visiableCells 保存屏幕上可见的 cell ,而 reuseTableCells 保存可重用的 cells.
*/
NSString* cellIdentifier = @"cellid"
/*
1.在tableView显示之初, reuseTableCells为空。
那么[tableView dequeueReusableCellWithIdentifier:cellIdentifier]返回nil。
*/
/*
2.开始时的cell都是通过
[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]
来创建,而且cellForRowAtIndexPath:只是调用最大显示cell数的次数。
比如:有100条数据,iPhone一屏最多显示10个cell。
程序最开始显示TableView的情况是:
创建10次cell,并给cell指定同样的重用标识(当然,可以为不同显示类型的cell指定不同的标识)。
并且10个cell全部都加入到visiableCells数组,reusableTableCells为空。
*/
[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
//向下拖动tableView,当cell1完全移出屏幕,并且cell11(它也是alloc出来的,原因同上)完全显示出来的时候。
//cell11加入到visiableCells,cell1移出visiableCells,cell1加入到reusableTableCells。
/*
3.接着向下拖动tableView,因为reusableTableCells中已经有值,所以,当需要显示新的cell,cellForRowAtIndexPath再次被调用的时候,
[tableView dequeueReusableCellWithIdentifier:CellIdentifier]返回cell1。
cell1加入到visiableCells,cell1移出reusableTableCells;
cell2移出visiableCells,cell2加入到reusableTableCells。
之后再需要显示的Cell就可以正常重用了。
*/
所以整个过程并不难理解,但需要注意正是因为这样的原因:配置Cell的时候一定要注意,对取出的重用的cell做重新赋值,不要遗留老数据。
6.iOS多线程的四种技术方案
-
如图
7. 假设有一个字符串aabcad,请写一段程序,去掉字符串中不相邻的重复字符串,即上述字符串处理之后的输出结果为:aabcd
NSMutableString * str1 = [[NSMutableString alloc] initWithFormat:@"aabcad"];
for (int i = 0; i < str1.length - 1; i++) {
for (int j = i + 1; j < str1.length ; j++) {
// 由于字符的特殊性 无法使用 字符串 isEqualToString 进行比较 只能转化为ASCII 值进行比较 所以 需要加 unsigined 修饰
unsigned char a = [str1 characterAtIndex:i];
unsigned char b = [str1 characterAtIndex:j];
if (a == b) {
if (j - i > 1) {
// NSRange: 截取字符串 {j, 1} j: 第一个字符开始 1: 截取几个字符
NSRange range = {j, 1};
[str1 deleteCharactersInRange:range];
j = i--;
}
}
}
}
NSLog(@"------ %@-------", str1);
8. iOS中几种数据持久化方案
- plist文件(属性列表)
- preference(偏好设置)
- NSKeyedArchiver(归档)
- SQLite 3
- CoreData
假如你不熟练:面试官问常用哪种,就回答SQLite . 问详细的话,就回答上GitHub上面找封装好的工具类来实现存储...
9.iOS传参数的几种方案
- 1.属性传值
UIViewController *B = [UIViewController new];
B.title = @"B的标题";
[A.navigationController pushViewController:B animated:YES];
通常用于正向传值,适用于A和B相互具有一定关联性。不能用于隔页面传值。而且,需要传值的属性不能是私有属性,也就是说在.h中声明出来的属性才可以传值。
- 2.Block传值
使用场景:
常用于回调,简单的说就是B有一个按钮,当按钮被点击时把点击事件传传给A,并传一个字符串"B被点了"。
//首先在B控制器中声明一个block,参数是一个字符串
@property (nonatomic,copy) void(^block)(NSString *title);
//传值
- (void)buttonClick:(UIButton *)sender
{
self.block(@"B被点了");
}
//回调代码块
BController *B = [BController new];
B.block = ^(NSString *title) {
//do someThing
A.title = title;
};
[A.navigationController pushViewController:B animated:YES];
同样的Block在这里作为属性存在,同属性传值一样,需要两个控制器间具有一定关联性。不能跨页面传值。
如果一定要跨,就要像接力赛一样,A传给B,B传给C这样
3.代理传值
代理传值和block传值相似,都是将事件分发出去。但是与block的不同在于,代理具有松耦合性,谁想处理事件成为代理即可。4.通知中心传值
上面说的代理属于一对一的关系,就好像一夫一妻制。你有需求只能找你的代理(你老婆)。而通知中心属于一对多的,就像村头的喇叭一喊,全村人都能听到。5.单例传值
iOS系统中常间的单例模式莫过于UIApplication、 NSNotificationCenter、 NSUserDefaults(常用)6.数据库传值
数据库无非在于打开数据库、建表以及基于数据库表的增删改查操作
这里有一个唐巧大大的FMDB的demo自行学习吧demo点这里7.NSFileManager
跟数据库类似,只是将数据写成文件保存在沙盒中。
需要注意的:
( 1 ) 文件路径是否正确
( 2 ) 不能保存复杂对象
相关篇幅还是有点长度的,这里推荐个链接详细了解点这里8.全局变量传值
在某个文件的.m文件创建一个全局变量,其他文件只要引用该变量,即可对该变量值进行修改和使用。需要注意,变量名要保证全局唯一。
10.iOS的APP实现相互调起和参数的传值
一、首先为要跳转的App,添加自定义URL协议的Schemes的id,很多成熟的App都有固定的Schemes的id,下面再说,首先添加自定义URL协议,添加方法这里介绍两种:
- 第一种:直接在Info.plist里面添加,如图:
在这里,URL Schemes
的item
的值是APP跳转过程中的key
,也就是自定义的url
协议向iphone注册的key
,URL identifier
就相当于参数,你可以跳转到你的app的某一个具体功能页面,甚至事件。这里也可以不填写
- 第二种:可以直接在
info
的URL types
中添加如图:
二、实现跳转的代码,在这里使用openURL来实现APP之间的跳转,随着xcode的更新,目前需要添加白名单,过程如下:
- 第一:添加白名单,在自己的的应用程序的的
info.plist
中添加LSApplicationQueriesSchemes
属性,其类型为数组,然后在数组的下面添加要跳转的app
的URL Schemes
的key
。
添加白名单如图:
- 第二:编写代码实现跳转,这里跳转用的是
openurl
,跳转的test02
的app
如图:
无参数的打开url schemes
为test02
的app
代码如下:
- (IBAction)skipOtherApp:(UIButton *)sender {
NSURL* open_URL_A = [NSURL URLWithString:@"test02://"];
//判断是否是否有can打开应用程序,如果成功就打开
if ([[UIApplication sharedApplication] canOpenURL:open_URL_A]) {
NSLog(@"可以打开");
[[UIApplication sharedApplication] openURL:open_URL_A];
}
}
三:app
在跳转过程中的参数传输,当跳转到url Schemes
为test02
时,将指定的数据传送过去,url Schemes
为test02
的程序在对数据处理(常用的参数传输为:test02
登录数据,test02
跳转到指定界面等)
(1)参数传递
将上面的代码:
NSURL* open_URL_A = [NSURL URLWithString:@"test02://"];
修改成为:
NSURL* open_URL_A = [NSURL URLWithString:@"test02://name=test01&password=123456"];
这样test01
跳转到test02
时的传输数据为"name=test01&password=123456"
,而在test02
中处理传递数值的位置为appdelegate.m
,在这里添加方法如下:
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation{
/*
*sour ceAppl i cat i on 从那个app跳转的
*url 跳转时,openurl中的数据
*str url 为 test02://name=test01&password=123456 然后对字符串处理
*/
NSString* str_url = [NSString stringWithlContentsOfURL:url
encoding:kCFStringEncodingUTF8
error:nil] ;
return YES;
}
test02
接受参数
(2)从自己的app
跳转到AppStore
下载指定的app
,具体代码如下:
NSString* urlString = @"itms://itunes.apple.com/gb/app/id391945719?mt=8";
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlString]];
其中id391945719
可以改为你指定的app
的id
,当然你也可以将指定app
的下载地址的https
改为itms
就可以了。
11.自动释放池,原理以及如何工作的
- 1.什么是自动释放池
自动释放池autorelease pool
是OC
的一种内存自动回收机制.
当你向一个对象发送一个autorelease
消息的时候,cocoa
就会将对象的一个引用放入
到最新的自动释放池中(当前线程栈顶位置),它任然是一个正当的对象,因此自动释放池
定义的作用域内的其他对象都可以向他发送消息. - 2.如何工作
objective-C
是通过一种referring counting(引用计数)
的方式管理内存的
对象在开始分配内存alloc
的时候引用计数为1
,以后如果有copy
,retain
的时候
都会加1
,每当release
和autorelease
的时候引用计数就会减1
,如果一个对象的引
用计数为0
,就会被系统销毁.
NSAutoreleasePool
就是用来做引用计数的管理工作的,这个东西一般不用你管的
autorelease
和release
没什么区别,只是引用计数减1
的时机不同而已.autorelease
会在对象的使用真正结束的时候才做引用计数减1
. - 3.自动释放池的实现原理
实现原理:自动释放池以栈的形式实现:当你创建一个新的自动释放池时,它将被添加到
栈顶.当一个对象收到autorelease消息的时候,它被添加到当前线程的处于
栈顶的的自动释放池中,当自动释放池被回收时,他们就从栈中被删除,并且会
给池子里面的所有对象都会做一次release操作.