【潮汐】iOS面试题总结,持续更新中......

1、属性默认关键词是那些,NSString常用什么修饰,为什么?

对应基本数据类型默认关键字是:atomic, readwrite, assign。
对于普通的 Objective-C 对象默认关键字是:atomic, readwrite, strong。

NSString用copy修饰。为了安全,一般情况下,我们都不希望字串的值跟着字符串变化,所以我们一般用copy来设置string的属性。

@property (strong,nonatomic)  NSString *rStr;
@property (copy, nonatomic)  NSString *cStr;

- (void)test{
    NSMutableString *mStr = [NSMutableString stringWithFormat:@"abc"];
    self.rStr = mStr;
    self.cStr = mStr;
    NSLog(@"mStr:%p,%p",  mStr,&mStr);
    NSLog(@"strongStr:%p,%p", _rStr, &_rStr);
    NSLog(@"copyStr:%p,%p",   _cStr, &_cStr);       
}

假如,mStr对象的地址为0x11,也就是0x11是@“abc”的首地址,mStr变量自身在内存中的地址为0x123;
当把mStr赋值给strong的rStr时,rStr对象的地址为0x11,rStr变量自身在内存中的地址为0x124;rStr与mStr指向同样的地址,他们指向的是同一个对象@“abc”,这个对象的地址为0x11,所以他们的值是一样的。
当把mStr赋值给copy的cStr时,cStr对象的地址为0x22,cStr变量自身在内存中的地址0x125;cStr与mStr指向的地址是不一样的,他们指向的是不同的对象,所以copy是深复制,一个新的对象,这个对象的地址为0x22,值为@“abc”。

如果现在改变mStr的值:
//注意mStr如果这里是不可变字符串,那么这里无法改变,是浅拷贝。会崩溃

   [mStr appendString:@"de"];        
   NSLog(@"strongStr:%@",  _rStr);        
   NSLog(@"copyStr:%@",    _cStr);

结果:
使用strong的字串rStr的值:@"abcde",
而使用copy的字串cStr的值:@"abc",

如果是不可变字符串

  NSString * str = @"abc";
    self.rStr = str;
    self.cStr = str;
    str = @"66666666";
    NSLog(@"%@===%@",self.rStr,self.cStr);//打印结果都是abc
2、nullable、nonnull 、NS_ASSUME_NONNULL_BEGIN 和 NS_ASSUME_NONNULL_END 的含义和用途?

__nullable指代对象可以为NULL或者为NIL
__nonnull指代对象不能为null
当我们不遵循这一规则时,编译器就会给出警告。
事实上,在任何可以使用const关键字的地方都可以使用__nullable和__nonnull,不过这两个关键字仅限于使用在指针类型上。而在方法的声明中,我们还可以使用不带下划线的nullable和nonnull,如下所示:

- (nullable id)itemWithName:(NSString * nonnull)name

在属性声明中,也增加了两个相应的特性,因此上例中的items属性可以如下声明:

@property (nonatomic, copy, nonnull) NSArray * items;

当然也可以用以下这种方式:

@property (nonatomic, copy) NSArray * __nonnull items;

推荐使用nonnull这种方式,这样可以让属性声明看起来更清晰。

如果需要每个属性或每个方法都去指定nonnull和nullable,是一件非常繁琐的事。苹果为了减轻我们的工作量,专门提供了两个宏:NS_ASSUME_NONNULL_BEGIN和NS_ASSUME_NONNULL_END。在这两个宏之间的代码,所有简单指针对象都被假定为nonnull,因此我们只需要去指定那些nullable的指针。如下代码所示:

NS_ASSUME_NONNULL_BEGIN
@interface TestNullabilityClass ()
@property (nonatomic, copy) NSArray * items;
- (id)itemWithName:(nullable NSString *)name;
@end
NS_ASSUME_NONNULL_END

本题参考

3、BAD_ACCESS在什么情况下出现?

访问了悬垂指针,比如对一个已经释放的对象执行了release、访问已经释放对象的成员变量或者发消息。 死循环。

4、以下代码输出什么?

- (void)deadLockCase1 {
    NSLog(@"1"); // 任务1
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2"); // 任务2
    });
    NSLog(@"3"); // 任务3
}

控制台输出:1 ,后面就崩溃了。

5、isKindOfClass和isMemberOfClass的区别?selector的作用?

相同点:
都是NSObject的比较Class的方法.
不同点:
isKindOfClass:确定一个对象是否是一个类的成员,或者是派生自该类的成员。或者是继承自某类。
isMemberOfClass:确定一个对象是否是当前类的成员.

selector:通过方法名,获取在内存中的函数的入口地址。

6、使用NSOperationQueue的addOperationWithBlock要考虑循环引用吗,为什么?
7、为什么标准头文件都有类似以下的结构?
#ifndef __INCvxWorksh 
#define __INCvxWorksh  
#ifdef __cplusplus 

extern"C"{ 
#endif  
/*...*/
#ifdef __cplusplus 
} 
#endif  
#endif /* __INCvxWorksh */

答案:

#ifndef __INCvxWorksh
#define __INCvxWorksh
#endif 
这一段是用来防止头文件重复引用,vs可以使用#pragma once。但是推荐使用宏定义来防止重复包含,其跨平台,兼容性好。 

如果是C++文件,以C的方式编译,并执行{}内的指令。其目的是为了兼容C代码,常出现在动态链接库的代码中。

#ifdef _cplusplus  //这句表示如果是c++文件
extern"C"{  //用extern "C"把一段代码包起来
#endif
#ifdef _cplusplus
}
#endif

8、HTTP七层协议

TCP协议对应于传输层
网络七层协议由下往上分别为物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。其中物理层、数据链路层和网络层通常被称作媒体层,是网络工程师所研究的对象;传输层、会话层、表示层和应用层则被称作主机层,是用户所面向和关心的内容。

HTTP协议对应于应用层,TCP协议对应于传输层,IP协议对应于网络层,HTTP协议是基于TCP连接的,三者本质上没有可比性。 TCP/IP是传输层协议,主要解决数据如何在网络中传输;而HTTP是应用层协议,主要解决如何包装数据。Socket是应用层与TCP/IP协议族通信的中间软件抽象层,是它的一组接口。

本题参考

9、Objective-C 消息发送与转发机制原理

消息发送和转发流程可以概括为:
消息发送(Messaging)是 Runtime 通过 selector 快速查找 IMP 的过程,有了函数指针就可以执行对应的方法实现;

另外:(Objective-C动态性的根源在方法的调用是通过message来实现的,一次发送message的过程就是一次方法的调用过程。发送message只需要指定对象和SEL,Runtime的objc_msgSend会根据在信息在对象isa指针指向的Class中寻找该SEL对应的IMP,从而完成方法的调用。)

消息转发(Message Forwarding)是在查找 IMP 失败后执行一系列转发流程的慢速通道,如果不作转发处理,则会打日志和抛出异常。

消息查找过程IMP
(1)缓存查找
(2)继续在类的继承体系中查找

10、底层解析weak的实现原理?

weak 实现原理的概括
Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。
weak 的实现原理可以概括一下三步:
1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
2、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

参考

runtime如何实现weak变量的自动置nil?
runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。

11、Block 底层原理总结,有几种类型的Block,Block的循环引用原理?

(1)Block可以简单总结:
block本质上也是一个OC对象,它内部也有个isa指针;
block是封装了函数调用以及函数调用环境的OC对象.

(2)Block 有三种类型:
NSGlobalBlock 全局区的Block
NSStackBlock 栈区的Block
NSMallocBlock 堆区的Block
(3)Block的循环引用原理?
对象、变量、Block的相互持有

循环引用原理

那么如何解决这个问题呢?
通常我们ARC环境下面的解决办法是通过__weak指针来解决这个问题,通过上面讲的Block里面的变量是通过访问的外部变量是否是strong或weak指针来进行内部对象进行相应修饰的,所以如果访问的外部对象是weak指针时,他们的引用关系就会如下图:

解决

参考

12、YTCar *car 实现对象car的手动内存管理模式下的set方法

//是一个不断去掉旧值赋新值的过程
- (void)setCar:(YTCar *)car{   
    if (_car != car) {  //判断新旧值是否相等
        //release掉旧值
        [_car release]; 
        //retain新值
        _car = [car retain];  
    }   
}

13、NSThread、 GCD、NSOperation的区别?

1)NSThread

优点:NSThread 比其他两个轻量级。
缺点:需要自己管理线程的生命周期,线程同步。
线程同步对数据的加锁会有一定的系统开销。

3)GCD

替代NSThread等线程,自动管理生命周期。

2)NSOperation

优点:不需要关心线程管理, 数据同步的事情,可以把精力放在自己需要执行的操作上。
基于GCD底层,使用起来更加面向对象。比GCD多了一些简单实用的功能。

多线程

14、autorelease 自动释放池的释放时机,autoreleasepool的实现原理?

(1)runloop就是iOS中的消息循环机制,当一个runloop结束时系统才会一次性清理掉被autorelease处理过的对象,其实本质上说是在本次runloop迭代结束时清理掉被本次迭代期间被放到autorelease pool中的对象的。至于何时runloop结束并没有固定的duration!
(2)当一个autorelease pool被drain 的时候,会对pool里的每一个对象发送一个release消息;
(3)每一个线程(包括主线程)都有一个AutoreleasePool栈。当一个新的池子被创建的时候,push进栈,当池子被释放内存时,pop出栈。对象调用autorelease方法进入栈顶池子中。当线程结束的时候,会自动地销毁所有跟它有关联的池子。

使用容器的block版本的枚举器时,内部会自动添加一个AutoreleasePool:

[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// 这里被一个局部@autoreleasepool包围着
}];

在普通for循环和for in 循环中没有,当for循环中便利产生大量autorelease变量时,就需要手动加局部AutoreleasePool。

autoreleasepool的实现原理?
AutoreleasePool(自动释放池)是OC中的一种内存自动回收机制,它可以延迟加入AutoreleasePool中的变量release的时机。在正常情况下,创建的变量会在超出其作用域的时候release,但是如果将变量加入AutoreleasePool,那么release将延迟执行。

AutoreleasePool底层实现原理
autoreleasepool的实现原理

15、OC中如何判断两个对象完全相同?

(1)isEqual和hash

- (BOOL)isEqual:(id)object {
  if (self == object) {
      return YES;
  }
  if (![self class] == [object class]) {
      return NO;
  }
//此处还需判断对象中的各个属性是否相同
...
//若所有属性都相同则返回
  return YES;
}

首先判断两个指针是否相等,若相等,则均指向同一对象,所以受测的对象也必定相等。接下来判断两对象所属的类,若属于同一类。

hash:比较得时候最好是先计算其哈希码,再进行比较。

参考:OC中 判断2个对象相等(isEqual和hash)

(2)==
比较的是两个对象的指针本身,有时候返回的结果并不是我们想要的结果。
(3)isEqualToString
用于判断两个字符串是否相等的方法,当然还有isEqualToArray: isEqualToDictionary:

(4)NSSet中可变类的等同性比较【数组去重】
OC 判断两个对象是否相等

16、retain一个NSTime类型成员变量会有什么问题?

使用NSTimer可能会碰到循环引用的问题。特别是当类具有NSTimer类型的成员变量,并且需要反复执行计时任务时。

_timer = [NSTimer scheduledTimerWithTimeInterval:5.0
                                          target:self
                                        selector:@selector(startCounting) userInfo:nil
                                         repeats:YES];

类有一个成员变量_timer,给_timer设置的target为这个类本身。这样类保留_timer,_timer又保留了这个类,就会出现循环引用的问题,最后导致类无法正确释放。

解决这个问题的方式也很简单,当类的使用者能够确定不需要使用这个计时器时,就调用

[_timer invalidate];
_timer = nil;
17、Category的使用及原理?与Runtime有关吗?

Category的使用及原理

18、数组去重(顺序不会变,去重后数组为listAry,并找出重复的元素(listAryCF)?
/**
 数组去重,顺序不会变

 @param array 传入的数组
 @return 得到去重后的可变数组
 */
+(NSMutableArray *)arrayDataDeleteChongFuWithArray:(NSArray *)array{
    NSMutableArray *listAry = [[NSMutableArray alloc]init];//去重后的数组
//     NSMutableArray *listAryCF = [[NSMutableArray alloc]init];//检出重复的元素,放入数组
    for (NSString *str in array) {
        //        containsObject 判断数组是否包含某个元素
        if (![listAry containsObject:str]) {
            [listAry addObject:str];
        }else{
            //附加
            //重复元素加入新数组
//            [listAryCF addObject:str];
//            NSLog(@"重复的元素:%@",str);
        }
    }
    return listAry;
}
19、【重】监听一组异步任务是否都执行结束,如果都执行结束就能够得到统一的通知.
    // 监听一组异步任务是否执行结束,如果执行结束就能够得到统一的通知.
    // 创建默认优先级的全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
    // 创建调度组
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, queue, ^{
        NSLog(@"下载图片A");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"下载图片B");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"下载图片C");
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"以上异步任务都处理下载完成图片了");
    });
20、masonry为什么不会造成循环引用的问题?

答案:虽然block持有self,但是self并没有持有block,显然block跟self并没有相互持有,所以不会循环引用。

[self.view mas_makeConstraints:^(MASConstraintMaker *make) {
    make.centerY.equalTo(self.otherView.mas_centerY);
}];

并不是 block 就一定会造成循环引用,是不是循环引用要看是不是相互持有强引用。block 里用到了 self,那 block 会保持一个 self 的引用,但是 self 并没有直接或者间接持有 block,所以不会造成循环引用。
block中持有了self,但是self.view并没有持有这个block,因为看到Masonry的源码是这样的:

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
    self.translatesAutoresizingMaskIntoConstraints = NO;
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    block(constraintMaker);
    return [constraintMaker install];
}
21、以下每行代码执行后,person对象的retain count分别是多少?
Person *person = [[Person alloc] init];
[person retain];
[person release];
[person release];

答案
Person *person = [[Person alloc] init]; =1
[person retain]; +1 = 2
[person release]; -1 = 1
[person release]; -1 = 0

22、如何用GCD同步若干个异步调用(根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图)
// 使用Dispatch Group追加block到Global Group Queue,这些block如果全部执行完毕,就会执行Main Dispatch Queue中的结束处理的block。

// 创建队列组

dispatch_group_t group = dispatch_group_create();

// 获取全局并发队列

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_group_async(group, queue, ^{ /*加载图片1 */ });

dispatch_group_async(group, queue, ^{ /*加载图片2 */ });

dispatch_group_async(group, queue, ^{ /*加载图片3 */ });

// 当并发队列组中的任务执行完毕后才会执行这里的代码

dispatch_group_notify(group, dispatch_get_main_queue(), ^{

        // 合并图片

});

23、Objc中向一个nil对象发送消息会怎样?

oc中向一个nil对象发送消息,首先在寻找对象的isa指针时就是0地址返回了,所以不会出现任何错误。也不会崩溃。

24、简单描述客户端的缓存机制?(Describe the cache mechanism of the client?)

缓存分为:内存数据缓存、数据库缓存、文件缓存。

每次想获取数据的时候:
1)先检测内存中有无缓存;
2)再检测本地数据缓存(数据库、文件);
3)最终发送网络请求;
4)将网络数据进行缓存(内存、数据库、文件),以便下次读取。

25.1、下面线程输出顺序?
 dispatch_queue_t queue = dispatch_queue_create("com.taikang.com", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        
        NSLog(@"1------%@",[NSThread currentThread]);
        
    });
    
    dispatch_async(queue, ^{
        
        NSLog(@"2------%@",[NSThread currentThread]);
        
    });
    
    dispatch_sync(queue, ^{
        
        NSLog(@"3------%@",[NSThread currentThread]);
        
    });
    
    dispatch_async(dispatch_get_main_queue(), ^{
        
          NSLog(@"4------%@",[NSThread currentThread]);
    });
    
      NSLog(@"5------%@",[NSThread currentThread]);
    
    答案:输出顺序:1 2 3 5 4

输出顺序:1 2 3 5 4

25.2、执行这个方法需要几秒?打印顺序是啥?
- (void)testGCD
{
    dispatch_queue_t queue = dispatch_queue_create("test", NULL);
    dispatch_async(queue, ^(void){
        NSLog(@"1");
        sleep(1);
    });
    dispatch_async(queue, ^(void){
        NSLog(@"2");
        sleep(1);
    });
    dispatch_sync(queue, ^(void){
        NSLog(@"3");
        sleep(1);
    });
}

答案:调用该方法要3秒,因为sleep是休眠,打印顺序:123。

26、给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度?

示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

测试输入:abcabcdefbbkcghghjkla

-(void)stringMaxLength{
    
     //    NSString *str = @"abcabcbb";
    NSString *str = @"abcabcdefbbkcghghjkla";
    
    NSInteger maxLenth = 0;//最大长度
    NSString *targetStr = @"";//最大长度对应的字符串
    for (int i=0; i maxLenth) {
                    maxLenth = substr.length;
                    targetStr = substr;
                }
            }
            
        }
        
    }
    
    NSLog(@"最大长度无重复字符串长度为:%ld  ==  字符串为:%@",(long)maxLenth,targetStr);
}
//输出结果为:abcdef

输出结果为:abcdef

27、 iOS runloop与线程的关系

runloop是每一个线程一直运行的一个对象,它主要用来负责响应需要处理的各种事件和消息。每一个线程都有且仅有一个runloop与其对应,没有线程,就没有runloop。

在所有线程中,只有主线程的runloop是默认启动的,main函数会设置一个NSRunLoop对象。而其他的线程runloop默认是没有启动的,可以通过[NSRunLoop currentRunLoop]来启动。

当线程的 RunLoop 开启后,线程就会在执行完任务后,处于休眠状态,随时等待接受新的任务,而不是退出。

28、什么是isa指针?

isa是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass).根元类的isa指针指向本身,这样形成了一个封闭的内循环。

29、Block 为什么用 Copy 修饰?

对于这个问题,得区分 MRC 环境 和 ARC 环境;当Block 引用了普通外部变量时,都是创建在栈区的;对于分配在栈区的对象,我们很容易会在释放之后继续调用,导致程序奔溃,所以我们使用的时候需要将栈区的对象移到堆区,来延长该对象的生命周期。
对于 MRC 环境,使用 Copy 修饰 Block,会将栈区的 Block 拷贝到堆区。
对于 ARC 环境,使用 Strong、Copy 修饰 Block,都会将栈区的 Block 拷贝到堆区。
所以,Block 不是一定要用 Copy 来修饰的,在 ARC 环境下面 Strong 和 Copy 修饰效果是一样的。

30、解释static、self、super关键字的作用?

static:函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值.
在模块内的 static 全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问.
在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明.
在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝。
self:当前消息的接收者。
super:向父类发送消息。

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

答:self.name =”object”:会调用对象的setName()方法;
name = “object”:会直接把object赋值给当前对象的name属性。

32、这段代码有什么问题吗?

-(void)setAge:(int)newAge{
self.age = newAge;
}

答案
死循环,应该修改为:_age = newAge;

33、这段代码有什么问题,如何修改?
 for (int i = 0; i < someLargeNumber; i++) {
        NSString *string = @"Abc";
        string = [string lowercaseString];
        string = [string stringByAppendingString:@"xyz"];
        NSLog(@“%@”, string);
    }

答案
打印结果:string = abcxyz
大次数循环会出现内存泄露,加入自动释放池@autoreleasepool{};在变量使用结束后立刻释放。

 for (int i = 0; i < someLargeNumber; i++) {
        @antoreleasepool {
            NSString *string = @”Abc”;
            string = [string lowercaseString];//字母全部转小写
            string = [string stringByAppendingString:@"xyz"];//字符串追加
            NSLog(@“%@”, string);
        }
    }
35、直接修改成员变量或属性会触发KVO吗?

答案:都不会执行。观察者观察的是属性,只有遵循 KVO 变更属性值的方式才会执行KVO的回调方法,例如是否执行了setter方法、或者是否使用了KVC赋值。如果赋值没有通过setter方法或者KVC,而是直接修改属性对应的成员变量,例如:仅调用_name = @"newName",这时是不会触发kvo机制,更加不会调用回调方法的。
所以使用KVO机制的前提是遵循 KVO 的属性设置方式来变更属性值。

36、Block是如何捕获Object-C的对象的?

答案:block里捕获的是该变量指向的内存地址,而不是直接把当前的对象的地址。

可能是指针拷贝。Block会对外部的变量进行一次"临时“的”拷贝“.产生一个新的指针,对象地址不变。

37、当数据库中的某项数据为null时,通过FMDB取出的数据为?

答案:nil

38、下面代码打印什么?
//在NSObject+Test.h中
#import 
@interface NSObject (Test)
-(void)test;
+(void)test1;
@end
@interface ClassA : NSObject
@end

//在NSObject+Test.m中
#import "NSObject+Test.h"
@implementation NSObject (Test)
-(void)test{
    NSLog(@"aaa");
}
+(void)test1{
    NSLog(@"bbb");
}
@end
@implementation  ClassA
@end

答案:在调用的控制器里调用打印如下

    [NSObject test1];//打印bbb
     [ClassA test1];//打印bbb
    [[[NSObject alloc]init]  test];//打印aaa
    [[[ClassA alloc]init]  test];//打印aaa

39、对于宏定义:#define MIN(A,B) (A) < (B) ? (A) : (B)

 float a = 1.0;
 float b = MIN(a++, 1.5);
 NSLog(@"a===%f,b==%f",a,b);

答案: a===3.000000,b==2.000000

因为运算符优先级从高到底:() ++ < ? :

然后a++是先赋值在加1,++a是先加1在赋值。

在(a++) < (1.5) ? (a++) : (1.5)中,第一次:

算式里第一个a为1,第二个a为2。

(1) < (1.5) ? (a++) : (1.5)//第一个赋完值后才开始加1,此时a为2,拿着去后面用。
(1) < (1.5) ? (2) : (1.5) 所以在三目运算时第二个a为2。所以最终b为2。

最终因为a被++了两次,所以a为3。

40、Protocol(协议)可以添加@property属性吗? Category可以添加@property属性吗?

答案
(1)在protocol中使用property只会生成setter和getter方法声明,我们使用属性的目的,是希望遵守我协议的对象能实现该属性。
(2)category使用@property也是只会生成setter和getter方法的声明,如果我们真的需要给category增加属性的实现,需要借助于运行时的两个函数:
obj_setAssociatedObject
obj_getAssociatedObject

41、在MRC下,如下代码:

NSString * a = @"abc";
NSString * b = [a retain];
NSString * c = [b copy];
NSString * d = [c mutableCopy];
NSString * e = [d copy];
请写出a、b、c、d、e的引用计数各是多少?

答案
2、2、2、3、3

42、下面代码输出什么?
 NSUserDefaults *  userDefaults = [NSUserDefaults standardUserDefaults];
    BOOL boolFlag = NO;
    [userDefaults setObject:@(boolFlag) forKey:@"boolFlag"];//这里储存的NSNumber类型
    
    //解释
    id sss = [userDefaults objectForKey:@"boolFlag"];//sss值打印为:0 是NSNumber类型
    BOOL yyyy = sss;//yyyy值打印为YES(NSNumber类型转BOOL类型)
//补充:BOOL值进行转换时,其基准是判断对象是否存在,如果对象存在的时候即为1,不存在则为0; 而 NSNumber 恰好就是一个对象,所以即使它为0的情况下,在编译器的眼里依然视为对象存在,被转换过成1。如果在这里要想将0转换成 NO,必须先将 NSNumber 类型的IntegerValue取出来,然后赋测能得到1。
    
    if ([userDefaults objectForKey:@"boolFlag"]) {//有值就走这里==0
        BOOL eqByPass = [userDefaults objectForKey:@"boolFlag"];//为YES
        if (eqByPass) {//
            NSLog(@"A");
        }else{
             NSLog(@"B");
        }
    }else{
        BOOL eqByPass = [userDefaults objectForKey:@"boolFlag"];
        if (eqByPass) {
            NSLog(@"C");
        }else{
            NSLog(@"D");
        }
    }
    

答案
输出A

43、以下代码打印顺序?
//执行顺序
- (void)syncMain{
    dispatch_queue_t queue = dispatch_queue_create("serial", nil);
    dispatch_async(queue, ^(void){
        NSLog(@"1");
    });
   
    dispatch_sync(queue, ^(void){
        NSLog(@"2");
    });
    dispatch_async(queue, ^(void){
        NSLog(@"3");
        dispatch_sync(queue, ^(void){
            NSLog(@"4");
        });

    });
   
}

答案
1、2、3 ,走完3的时候就崩溃了,不会打印4,线程互斥。

44、如何手动触发一个value的KVO?

答案

自动触发场景:在注册 KVO 之前设置一个初始值,注册之后,设置一个不一样的值,这样就可以触发了。

手动触发:
键值观察通知依赖于 NSObject 的两个方法: willChangeValueForKey:和 didChangevlueForKey:。在一个被观察属性发生改变之前,willChangeValueForKey: 一定会被调用,这就会记录旧的值。而当改变发生后,observeValueForKey:ofObject:change:context: 和 didChangeValueForKey:也会被调用。如果可以手动实现这些调用,就可以实现“手动触发”了。

.m文件
//手动触发 value 的 KVO ,最后两行代码缺一不可

@property (nonatomic, strong) NSDate *now;

- (void)viewDidLoad{
  [super viewDidLoad];
  _now = [NSDate date];
  [self addObserver:self forKeyPath:@"now" options:NSKeyValueObservingOptionNew context:nil];
  NSLog(@"1");
  [self willChangeValueForKey:@"now"];//手动触发self.now的KVO,必写。
  NSLog(@"2");
  [self didChangeValueForKey:@"now"];//手动触发self.now的KVO,必写。
  NSLog(@"4");
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
  NSLog(@"3");
}

//打印顺序是:1 2 3 4。

45、下面代码有什么问题?如果有请指出?

-(NSString *)getString{
    return (__bridge NSString*)CFStringCreateWithCString(NULL, "h", kCFStringEncodingUTF8);
}

答案
NSLog(@"得到字符串:%@",[self getString]);//打印:h
没毛病啊,老铁。

46、下面代码里 [(__bridge id)obj print] 输出什么?(2019)
//MNPerson.h
@interface MNPerson : NSObject

@property(nonatomic,copy)NSString * name;

-(void)print;

@end

//MNPerson.m

#import "MNPerson.h"

@implementation MNPerson

-(void)print{
//    self.name = @"1111";
    NSLog(@"self.name = %@",self.name);
}

@end

//在ToolsEntController控制器的viewDidLoad里打印

 NSLog(@"得到字符串:%@",[self getString]);//打印:h
    id cls = [MNPerson class];
    void * obj = &cls;
    [(__bridge id)obj print];

答案
[(__bridge id)obj print]打印:self.name =
如果name有值(如值为1111)就打印:self.name = 1111

47、iOS重写单例,防止通过alloc]init或new、copy的方式开辟新空间。(20200831)
#import "NetWorkTools.h"

@implementation NetWorkTools
static id _instanceType = nil;
//自定义类方法
+(instancetype)sharadNetWorkTools{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instanceType = [[self alloc]init];
    });
    return _instanceType;
}
//重写父类方法,防止通过alloc]init或new的方式开辟新空间
+ (instancetype)allocWithZone:(struct _NSZone *)zone{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instanceType = [super allocWithZone:zone];
    });
    return _instanceType;
}
//防止对象copy操作
-(id)copyWithZone:(NSZone *)zone{
    return _instanceType;
}
@end
48、iOS App推送流程,APNS-苹果服务器。(20200831)

ios 设备 app在安装完成后,要完成消息的推送,需要以下步骤:
(1) app 将设备的 UUID(苹果设备的唯一标识) 和 app 的 bundled(项目的唯一标识)发送到 APNS(苹果服务器),请求 deviceToken。
(2)APNS 在收到 app 发送的请求后,会将加密后的 deviceToken 发送给 app。
(3)app 拿到 deviceToken 后,将 deviceToken 传到运营商服务器上,比如极光。这样以来,运营商就获取了推送权限。
(4)当有消息需要推送给某一位用户时,运营商就可以通过客户端对应的 deviceToken 选择推送对象。将推送消息和 deviceToken 一并发送给 APNS。
(5)APNS 根据发送过来的 deviceToken ,将推送消息发送到指定的设备上,完成消息的推送工作。

你可能感兴趣的:(【潮汐】iOS面试题总结,持续更新中......)