iOS面试题收纳-代码题

168 阅读8分钟

给出一个单例的代码实现

//.h 宏
#define SINGLETON_DEF(_type_)\
+ (_type_ * _Nullable)sharedInstance;\
+(instancetype _Nullable) alloc __attribute__((unavailable("call sharedInstance instead")));\
+(instancetype _Nullable) new __attribute__((unavailable("call sharedInstance instead")));\
-(instancetype _Nullable) copy __attribute__((unavailable("call sharedInstance instead")));\
-(instancetype _Nullable) mutableCopy __attribute__((unavailable("call sharedInstance instead")));\

//.m 实现宏
#define SINGLETON_IMP(_type_)\
+ (_type_ * _Nullable)sharedInstance{\
static _type_ *theSharedInstance = nil;\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
theSharedInstance = [[super alloc] init];\
});\
return theSharedInstance;\
}

[self class] 与 [super class]解析

@implementation Son : Father
 - (id)init {
     self = [super init];
     if (self) {
         NSLog(@"%@", NSStringFromClass([self class])); // Son
         NSLog(@"%@", NSStringFromClass([super class])); // Son
     }
     return self;
 }
 @end

self 是隐藏参数,指向当前调用方法的这个实例对象

super 本质是一个编译器标示符,和 self 指向的是同一个消息接受者。

不同点在于:super 会告诉编译器,当调用方法时,起始的查找位置是父类的方法列表,而不是本类的方法列表

在调用[super class]的时候,runtime会去调用objc_msgSendSuper方法,而不是objc_msgSend;

OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )

/// Specifies the superclass of an instance. 
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained id receiver;

    /// Specifies the particular superclass of the instance to message. 
#if !defined(__cplusplus)  &&  !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained Class class;
#else
    __unsafe_unretained Class super_class;
#endif
    /* super_class is the first class to search */
};

在objc_msgSendSuper方法中,第一个参数是一个objc_super的结构体,这个结构体里面有两个变量,一个是接收消息的receiver,在本例中是self,一个是当前类的父类super_class。

objc_msgSendSuper的工作原理是这样的: 从objc_super结构体指向的superClass父类的方法列表开始查找selector,找到后以objc->receiver去调用父类的这个selector。注意,最后的调用者是objc->receiver,而不是super_class!

那么objc_msgSendSuper最后就转变成:

// 注意这里是从父类开始msgSend,而不是从本类开始
objc_msgSend(objc_super->receiver, @selector(class))
// 由于是实例调用,所以是减号方法
- (Class)class {
    return object_getClass(self);
}

由于找到了父类NSObject里面的class方法的IMP,又因为传入的入参objc_super->receiver = self。self就是son,调用class,所以父类的方法class执行IMP之后,输出还是son,最后输出两个都一样,都是输出son

isMemberOfClass与isKindOfClass解析

@interface Sark : NSObject
@end
@implementation Sark
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...

        NSLog(@"%@", [NSObject class]);
        NSLog(@"%@", [Sark class]);

        BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
        BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
        BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
        BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
        NSLog(@"%d--%d--%d--%d", res1, res2, res3, res4);
    }
    return 0;
}

结果: 1--0--0--0

首先,我们先去查看一下题干中两个方法的源码:

- (Class)class {
    return object_getClass(self);
}

//  返回的直接是 是否是当前的类
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

// for循环查找 , 会根据当前类和 当前类的父类去逐级查找 ,
- (BOOL)isKindOfClass:(Class)cls {
  for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
      if (tcls == cls) return YES;
  }
  return NO;
}

可以得知:

  • isKindOfClass 的执行过程是拿到自己的 isa 指针和自己比较,若不等则继续取 isa 指针所指的 super class 进行比较。如此循环。
  • isMemberOfClass 是拿到自己的 isa 指针和自己比较,是否相等。
  • 首先,无论是[NSObject class]还是[Sark class],经过id强制装换后,他们其实就是元类的一个实例了,也就是类本身
  1. [NSObject class] 执行完之后调用 isKindOfClass
    • 第一次,先判断NSObject MetaClass和NSObject是否相等,很明显不相等。
    • 第二次,判断NSObject MetaClass 的 superclass 和NSObject是否相等。NSObject MetaClass的 superclass 就是 NSObject,所以第二次循环相等,至此打印出YES
  2. NSObject MetaClass和NSObject明显不相等, 所以打印出NO
  3. [Sark class] 执行完之后调用 isKindOfClass
    • 第一次 for 循环,Sark 的 MetaClass 与 SarkClass 不等
    • 第二次 for 循环,Sark MetaClass 的 super class 指向的是 NSObject MetaClass, 和 SarkClass 不相等
    • 第三次 for 循环,NSObject MetaClass 的 super class 指向的是 NSObjectClass,和 SarkClass 不相等。
    • 第四次循环,NSObject Class 的super class 指向 nil, 和 Sark Class 不相等。第四次循环之后,退出循环,所以第三行的输出为 NO。
  4. Sark 的 MetaClass 与 SarkClass 不等,所以打印出NO

带有afterdelay的performSelector

NSLog(@"1");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
  NSLog(@"2");
  [self performSelector:@selector(test) withObject:nil afterDelay:10];
  NSLog(@"3");
});
NSLog(@"4");

- (void)test {
  NSLog(@"5");
}
/*
1423,test方法并不会执行。

原因:如果是带afterDelay的延时函数,会在内部创建一个 NSTimer,然后添加到当前线程的RunLoop中。
但是如果当前线程没有开启RunLoop,该方法会失效
*/

那么我们改成

dispatch_async(dispatch_get_global_queue(0, 0), ^{        
  NSLog(@"2");
  [[NSRunLoop currentRunLoop] run];
  [self performSelector:@selector(test) withObject:nil afterDelay:10];
  NSLog(@"3");
});
/*
test方法依然不执行。

原因:如果RunLoop的mode中一个item都没有,RunLoop会退出。
在调用RunLoop的run方法后,由于其mode中没有添加任何item去维持RunLoop的事件循环,RunLoop随即会退出。
所以test方法无法执行
*/

要启动RunLoop,一定要在添加item后

dispatch_async(dispatch_get_global_queue(0, 0), ^{        
  NSLog(@"2");
  [self performSelector:@selector(test) withObject:nil afterDelay:10];
  [[NSRunLoop currentRunLoop] run];
  NSLog(@"3");
});

// 这样就会执行test方法了

TaggedPointer与自动释放池

for (int i= 0; i< 1000000; i++) {
      NSString * str = [NSString stringWithFormat:@"abcdefghijklmn1234566!@#$"];
}
    
for (int i= 0; i< 1000000; i++) {
       NSString * str = [NSString stringWithFormat:@"abc!"];
 }
  • 第一个内存会暴涨,str对象会不停的创建。由于是类方法,会加入到自动释放池,会延迟释放造成内存暴涨,使用@autoreleasepool 解决
  • 第二个内存固定,会使用 Tagged Pointer 将值存在str 变量地址中,存在栈空间地址中,,出了作用域会自动销毁

GCD队列和同步异步

- (void)interview01 {
    // 会发生死锁
    NSLog(@"任务1");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"任务2");
    });
    NSLog(@"任务3");
  	//输出 任务1 后卡死
  
    // dispatch_sync将任务添加到队列之后,会阻塞当前线程,然后在对应线程执行
    // 但是这次添加到的是主队列,而主队列的任务在主线程执行,此时dispatch_sync阻塞了当前线程(主线程),而又需要在主线程执行block中的任务
		//最终造成了死锁
}

- (void)interview02{
    // 不会发生死锁
    NSLog(@"任务1");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        NSLog(@"任务2");
    });
    NSLog(@"任务3");
   //输出任务1 任务3 任务2
  
   // dispatch_asyn将任务添加到队列之后,不会阻塞当前线程
   // 它会等当前线程执行完毕时,再从队列里取出任务,放到对应的线程执行
   // 所以这里会输出任务1 任务3 任务2
}

- (void)interview03{
    // 会产生死锁
    NSLog(@"任务1");
    dispatch_queue_t queue = dispatch_queue_create("muqueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"任务2");
        dispatch_sync(queue, ^{  //死锁
            NSLog(@"任务3");
        });
        NSLog(@"任务4");
    });
    NSLog(@"任务5");
   // 输出 任务1 任务5 任务2 后卡死
  
   // 这里和interview01类似
   // 任务2添加到队列异步执行,会单独开一条线程执行任务,但是任务3添加进入之后,会阻塞住这条线程
   // 而它的任务又需要在这条线程执行,从而造成了死锁
}

- (void)interview04 {
    // 不会产生死锁
    NSLog(@"任务1");
    dispatch_queue_t queue = dispatch_queue_create("muqueue", DISPATCH_QUEUE_SERIAL); 
    dispatch_queue_t queue2 = dispatch_queue_create("muqueue2", DISPATCH_QUEUE_SERIAL); 
    dispatch_async(queue, ^{
        NSLog(@"任务2");
        dispatch_sync(queue2, ^{  //死锁
            NSLog(@"任务3");
        });
        NSLog(@"任务4");
    });
    NSLog(@"任务5");
    // 输出 任务1 任务5 任务2 任务3 任务4
    //不会产生死锁   因为两个任务不在同一个队列之中, 所以不存在互相等待的问题。
}

- (void)interview05 {
    // 不会产生死锁
    NSLog(@"任务1 thread:%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("muqueue2", DISPATCH_QUEUE_CONCURRENT); // 并发;
    dispatch_async(queue, ^{
        NSLog(@"任务2 thread:%@",[NSThread currentThread]);
        dispatch_sync(queue, ^{
            NSLog(@"任务3 thread:%@",[NSThread currentThread]);
        });
        NSLog(@"任务4 thread:%@",[NSThread currentThread]);
    });
    NSLog(@"任务5 thread:%@",[NSThread currentThread]);
    /*
    任务1 thread:<NSThread: 0x600000ce5740>{number = 1, name = main}
		任务5 thread:<NSThread: 0x600000ce5740>{number = 1, name = main}
		任务2 thread:<NSThread: 0x600000c864c0>{number = 6, name = (null)}
		任务3 thread:<NSThread: 0x600000c864c0>{number = 6, name = (null)}
		任务4 thread:<NSThread: 0x600000c864c0>{number = 6, name = (null)}
    */
    //不会产生死锁   因为两个任务不在同一个队列之中, 所以不存在互相等待的问题。
}



- (void)interview06 {
dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_async(serialQueue, ^{
     NSLog(@"2");
});

NSLog(@"3");
dispatch_sync(serialQueue, ^{
    NSLog(@"4");
});

NSLog(@"5");
}

打印顺序是13245
1.首先先打印1
2 将任务2其添加至串行队列上,由于任务2是异步,不会阻塞线程,继续向下执行,打印3
3.将任务4添加至串行队列上,因为任务4和任务2在同一串行队列,根据队列先进先出原则,任务4必须等任务2执行后才能执行,又因为任务4是同步任务,会阻塞线程,只有执行完任务4才能继续向下执行打印5
 所以最终顺序就是13245。
  
 这里的任务4在主线程中执行,而任务2在子线程中执行。
 如果任务4是添加到另一个串行队列或者并行队列,则任务2和任务4无序执行(可以添加多个任务看效果)