给出一个单例的代码实现
//.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强制装换后,他们其实就是元类的一个实例了,也就是类本身
- [NSObject class] 执行完之后调用 isKindOfClass
- 第一次,先判断NSObject MetaClass和NSObject是否相等,很明显不相等。
- 第二次,判断NSObject MetaClass 的 superclass 和NSObject是否相等。NSObject MetaClass的 superclass 就是 NSObject,所以第二次循环相等,至此打印出YES
- NSObject MetaClass和NSObject明显不相等, 所以打印出NO
- [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。
- 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无序执行(可以添加多个任务看效果)