美文网首页Interview
【潮汐】iOS面试题总结一下,持续更新中 (2019年)(3)

【潮汐】iOS面试题总结一下,持续更新中 (2019年)(3)

作者: 爱恨的潮汐 | 来源:发表于2019-06-14 18:18 被阅读0次
    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表中删除,最后清理对象的记录。

    参考

    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<str.length; i++) {
            
            for (int j=1; j<str.length+1-i; j++){
                
                NSString *substr = [str substringWithRange:NSMakeRange(i, j)];
                
                BOOL isRepeatStr = NO;
                NSMutableArray *tempArr = [NSMutableArray array];
                for (int k=0; k<substr.length; k++) {
                    
                    NSString *subsubStr = [substr substringWithRange:NSMakeRange(k, 1)];
                    if ([tempArr containsObject:subsubStr]) {
                        isRepeatStr = YES;
                        break;
                    }else{
                        [tempArr addObject:subsubStr];
                    }
                }
                
                if (isRepeatStr == NO) {
                    if (substr.length > 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 <Foundation/Foundation.h>
    @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"];
        
        //解释
        id sss = [userDefaults objectForKey:@"boolFlag"];//sss值打印为:0
        BOOL yyyy = sss;//yyyy值打印为YES
        
        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<NSKeyValueChangeKey,id> *)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] 输出什么?
    //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 = <ToolsEntController: 0x7fb1a5508d50>
    如果name有值(如值为1111)就打印:self.name = 1111

    相关文章

      网友评论

        本文标题:【潮汐】iOS面试题总结一下,持续更新中 (2019年)(3)

        本文链接:https://www.haomeiwen.com/subject/oqtmfctx.html