iOS 知识大全

面试题整理(一)

2017-03-24  本文已影响24人  Mominglaile

1.堆和栈的区别

栈,是由编译器自动管理,无需我们手工控制;
堆,释放工作由程序员控制,容易产生memory leak(内存泄漏)。

申请大小:
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。

分配方式:

1. 堆都是动态分配的,没有静态分配的堆。
2. 栈有2种分配方式:静态分配和动态分配。

2.死锁问题

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"11111");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"22222");
    });
    NSLog(@"33333");
}

//死锁原因

  1. dispatch_sync在等待block语句执行完成,而block语句需要在主线程里执行,所以 dispatch_sync 如果在主线程调用就会造成死锁

  2. 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()插入同步threadsync会等到后面的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中几种数据持久化方案

假如你不熟练:面试官问常用哪种,就回答SQLite . 问详细的话,就回答上GitHub上面找封装好的工具类来实现存储...

9.iOS传参数的几种方案

UIViewController *B = [UIViewController new];

B.title = @"B的标题";

[A.navigationController pushViewController:B animated:YES];

通常用于正向传值,适用于A和B相互具有一定关联性。不能用于隔页面传值。而且,需要传值的属性不能是私有属性,也就是说在.h中声明出来的属性才可以传值。

//首先在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这样

10.iOS的APP实现相互调起和参数的传值

一、首先为要跳转的App,添加自定义URL协议的Schemesid,很多成熟的App都有固定的Schemesid,下面再说,首先添加自定义URL协议,添加方法这里介绍两种:

14902868774681.png

在这里,URL Schemesitem的值是APP跳转过程中的key,也就是自定义的url协议向iphone注册的key,URL identifier就相当于参数,你可以跳转到你的app的某一个具体功能页面,甚至事件。这里也可以不填写

14902869569022.png

二、实现跳转的代码,在这里使用openURL来实现APP之间的跳转,随着xcode的更新,目前需要添加白名单,过程如下:

无参数的打开url schemestest02app
代码如下:

- (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 Schemestest02时,将指定的数据传送过去,url Schemestest02的程序在对数据处理(常用的参数传输为: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可以改为你指定的appid,当然你也可以将指定app的下载地址的https改为itms就可以了。

11.自动释放池,原理以及如何工作的

关于自动释放池的内容,后续如果有内容的增加会补上
上一篇 下一篇

猜你喜欢

热点阅读