一,int和NSInteger的区别。
int没有根据当前系统的位数分配字节数,不管32位系统还是64位,它都是4个字节。
NSInteger在32位系统占4个字节,64位系统中占8个字节。
二,类在编译后能否增加实例变量,那么在运行的时候能否增加实例变量,如果可以,分别怎么添加?
首先,类的实例变量指对象类型的成员变量,也就是包含了isa指针的成员变量,例如:
@interface MethodObject ()
{
NSObject *_a;//a就是当前类的实例变量。
{
@end
- (void)createMemberVar { Class Myclass = objc_allocateClassPair([NSString class],"Myclass",0); //以NSString*为例 //变量size sizeof(NSString) //对齐 指针类型的为log2(sizeof(NSString*)) //类型 @encode(NSString*) BOOL flag = class_addIvar(Myclass,"location",sizeof(NSString *),0,"@"); objc_registerClassPair(Myclass); if(flag){ NSLog(@"location添加成功"); }else{ NSLog(@"location添加失败"); } id methObj = [[Myclass alloc]init]; Ivar locationIvar = class_getInstanceVariable(Myclass, "location"); [methObj setValue:@"我的地址" forKey:@"location"]; NSLog(@"%@",object_getIvar(methObj, locationIvar)); }
重点,类在编译后能不能够动态增加实例变量,在运行的时候可以动态增加实例变量。
因为编译后的类已经注册在 Runtime 中,类结构体中的 objc_ivar_list 实例变量的链表和 instance_size 实例变量的内存大小结构已经确定,内存结构完全确定⽆法修改,所以不能够动态添加;
运行时创建的类是可以添加实例变量,调用class_addIvar函数。但是的在调用 objc_allocateClassPair之后,objc_registerClassPair 之前,objc_allocateClassPair表示为该变量分配内存,objc_registerClassPair标示将要注册到Runtime中。
延展:
编译时和运行时的消息发送(方法)机制:
1,编译阶段确定消息接受者receiver和要去执行的方法selector,这时候不会去确定方法是否实现,不去确定参数是否传递。
不带参数:objc_msgSend(receiver,selector)
带参数:objc_msgSend(recevier,selector,org1,org2,…)
2,在运行阶段,如果当前的接受者(target)和消息(selector)是有效的,那么此消息会在对象的结构体的cache方法中去寻找,如果找不到,则会在其methodlists(方法列表中)去寻找,如果在其所有父类中都找不到,会调用Runtime的三个方法去处理,动态方法解析、消息接受者重定向、消息重定向这三个去处理,具体参考文章中第三十三项:
http://www.iashes.com/2020/11/10-318.html
三,自动释放池的原理
AutoreleasePool(自动释放池)是OC中的一种内存自动回收机制,加入到自动释放池中的对象,它不会立即被释放,会等到一次Runloop结束或者作用域超出时去释放。
当App启动时,会创建为主线程创建一个Runloop,当Runloop运行时,会创建一个Autoreleasepool对象,AutoreleasePool是按线程一一对应的。
1,程序Runloop在每次开始事件循环(Event loop)的时候开始主线程上自动创建一个自动释放池,并在每次事件循环(Event loop)休眠时或者结束时向池内对象发送release消息进行释放;
手动创建的Autorelease pool中的对象在block外被释放掉。
2,AutoreleasePool在初始化的时候会调用AutoreleasePoolPush方法,将需要池中的对象加入到AutoreleasePoolPage中保存;
当一个对象被调用了autorelease方法的时候,该对象才会被加入到Autoreleasepool中;
3,当出了自动释放池的作用域时,autoreleasePoolPop会被调用,对象引用计数变成0,然后释放。
四,NSTimer循环引用。
1,首先,NSTimer不能用weak进行修饰,因为创建的NSTimer已经被加到了当前的Runloop里边,RunLoop持有当前Timer,RunLoop会对timer有强引用,因此,timer修饰符是weak,timer还是不能释放,timer的target也就不能释放。
2,NSTimer的target也不能用weak进行修饰,Timer对target会有一个强引用,如果target用__weak去修饰,Timer背部也会强引用当前target,引用计数会加1。
直到timer is invalidated。也就是说,在timer调用 invalidate方法之前,timer对target一直都有一个强引用。
所以控制器的dealloc 方法不会被调用。
invalidate方法是将Timer从Runloop中移除,从而使Runloop不再强引用Timer;
解决方案1,使用NStimer自身作为中间件,也就是target,去执行timer事件,然后通过block传递给消息使用着。
#import <Foundation/Foundation.h> @interface NSTimer (BlockTimer) + (NSTimer *)bl_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void (^)(NSTimer *timer))block repeats:(BOOL)repeats; @end //------------------------- #import "NSTimer+BlockTimer.h" @implementation NSTimer (BlockTimer) + (NSTimer *)bl_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void (^)(NSTimer *timer))block repeats:(BOOL)repeats { return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(bl_blockSelector:) userInfo:[block copy] repeats:repeats]; //这里block作为函数参数是在栈里,为了不让他在当前作用域下消失,调用了copy,复制到了堆里。 } + (void)bl_blockSelector:(NSTimer *)timer { //当用户传递的block作为timer的userInfo信息 void(^block)(NSTimer *tm) = timer.userInfo; if (block) { block(timer); } } @end
解决方案2,用NSProxy作为中间件,作为一个中间人将消息重定向。
NSProxy其实它就是一个消息重定向封装的一个抽象类,类似一个代理人、中间件。可以通过继承它,并重方法签名和重定向两个方法来实现消息转发到另一个实例。具体代码如下:
#import <Foundation/Foundation.h> @interface ASProxy : NSProxy + (instancetype)ASProxyWithTarget:(id)target; @end //-------------------------------------------- #import "ASProxy.h" @interface ASProxy() @property (nonatomic,weak)id target; @end @implementation ASProxy + (instancetype)ASProxyWithTarget:(id)target { ASProxy *proxy = [ASProxy alloc]; proxy.target = target; return proxy; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { //注册到target中,去接受当前的消息(执行当前的方法) return [self.target methodSignatureForSelector:sel]; } - (void)forwardInvocation:(NSInvocation *)invocation { //唤醒target接受消息 [invocation invokeWithTarget:self.target]; } @end //------------------------------------- self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[ASProxy ASProxyWithTarget:self] selector:@selector(timerEvent:) userInfo:@"timerValue" repeats:YES]; //-------------------------------------
解析,methodSignatureForSelector和forwardInvocation方法是NSObject的方法,也就是说只要继承与NSObject的类都可以去重写这两方法,与NSProxy的不同是,NSProxy对象的结构体中只有一个isa指针,没有objc_class其他结构体元素,个人理解是在创建的时候系统为其分配的内存比其他类小,从而用NSProxy类作为中间件。
以上ASProxy也可以继承NSObject类去完成中间件,但是鉴于NSProxy的特殊,所以用了NSProxy类。
以上,为NSProxy增加一个weak修饰的实例变量target,让其弱引用,然后将消息转发给target去执行selector,所以持有关系是:
1,controller -strong- NSTimer;
2,NSTimer -strong- ASProxy;
3,ASProxy -weak – controller。
从而防止了循环引用。
3,网上还发现了一个方法,链接:https://www.jianshu.com/p/ce81d957c2ac
//定义个中间件属性 @property (nonatomic, strong) id target; _target = [NSObject new]; class_addMethod([_target class], @selector(testTimer), (IMP)timerIMP, "v@:"); //这里换成_target 不用self了。 这就没有了循环引用了 self.myTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:_target selector:@selector(testTimer) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:self.myTimer forMode:NSDefaultRunLoopMode]; void timerIMP(id self, SEL _cmd) { NSLog(@"Do some things"); } //停止Timer - (void)dealloc { [self.myTimer invalidate]; self.myTimer = nil; NSLog(@"Timer dealloc"); }
五,NSArray的遍历方法enumerateObjectsUsingBlock会引起循环引用吗?
不会,因为当前对象没有持有此遍历的block,所以不会引起循环引用。
六,以下会不会造成循环引用?
@interface S_ViewController () @property (strong, nonatomic, nullable) dispatch_queue_t ioQueue; @end // - (void)viewDidLoad { [super viewDidLoad]; _ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL); dispatch_async(_ioQueue, ^{ NSLog(@"%@",self.description); }); }
会造成循环引用,因为当前asyncblock强应用了_ioQueue成员变量,其实是强引用了当前self,然后_ioQueue调用当前异步执行时,等于_ioQueue(self)持有当前block,应该修改如下:
_ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL); __weak typeof(self) weakSelf = self; dispatch_async(_ioQueue, ^{ __strong typeof(weakSelf) strongSelf = weakSelf; NSLog(@"%@",strongSelf.description); });
七,以下代码输出顺序是什么?
//DISPATCH_QUEUE_CONCURRENT为并发 dispatch_queue_t userQueue = dispatch_queue_create("ashes.com.queue", DISPATCH_QUEUE_CONCURRENT); NSLog(@"1"); dispatch_async(userQueue, ^{ NSLog(@"2"); dispatch_sync(userQueue, ^{ NSLog(@"3"); }); NSLog(@"4"); }); NSLog(@"5"); //================输出================ 2020-11-21 17:06:39.012820+0800 xxxxDemo[42032:2601365] 1 2020-11-21 17:06:39.012983+0800 xxxxDemo[42032:2601365] 5 2020-11-21 17:06:39.013000+0800 xxxxDemo[42032:2601455] 2 2020-11-21 17:06:39.013331+0800 xxxxDemo[42032:2601455] 3 2020-11-21 17:06:39.013434+0800 xxxxDemo[42032:2601455] 4
解析:
首先1和5是在主线程,它两是并行,所以5一定在1后边,那么按照顺序创建线程,2一定在1后边,因为2和3,4都是并行,所以4在3后边,3在2后边,现在能确定的是1,2,3,4这个顺序,不确定5应该如何执行。现在两个线程是一个队列,是嵌套关系,所以1和5并行,所以1执行完等待的时间少于嵌套线程的时间,所以先执行5,最终输出的是1,5,2,3,4.
如果将当前队列改成飞并发DISPATCH_QUEUE_SERIAL,会如何执行?
//会输出 2020-11-21 17:12:28.993125+0800 xxxxDemo[42071:2604704] 1 2020-11-21 17:12:28.993304+0800 xxxxDemo[42071:2604704] 5 2020-11-21 17:12:28.993304+0800 xxxxDemo[42071:2604704] 2
解析:最终会输出1,5,2。
1和5是并行,在主线程,一定会执行,然后在非并发队列中嵌套了一个同步线程去执行操作,他们属于并行,先输出2,然后3会等待4,4会等待3,所以3和4造成了死锁,所以不会去输出3,4。
延展:
用户队列分为并发队列和非并发队列,具体创建的时候根据传递的参数决定,DISPATCH_QUEUE_CONCURRENT,并发队列;
DISPATCH_QUEUE_SERIAL,非并发(串行队列)队列;
并发队列执行异步操作,会创建子线程;并发队列执行同步操作,不会创建子线程;
非并发队列执行异步操作,会创建子线程;非并发队列执行同步操作,不会创建主线程。
八,imagedNamed,imageWithContentsOfFile的区别。
imagedNamed获取的图片,系统会在内存当中缓存当前图片,下次调用会去内存中直接拿,找不到才回去磁盘中拿。
imagedNamed在重复使用当前图片的时候适宜使用。
imageWithContentsOfFile不会去做缓存,如果当前图片只会调用一次且不会反复调用的时候,适宜用当前方法。
九,TCP四次挥手,最后一次挥手是客户端发起的还是服务器端发起的?为什么接受方要等待一段时间?
TCP断开连接最后一次挥手是由客户端发起的。
等待一段时间的原因是,因为网络有事会有一些误差或者不稳定因素,所以TIME_WAIT状态(等待时间)就是用来重发可能丢失的ACK报文。
十,MRC和ARC做一个解释。
MRC,手动内存管理,需要开发者手动分配,销毁对象,直到当前对象的引用计数为0。
ARC,自动内存管理,不需要开发者手动增加或者减少引用计数,自动适当的位置自行销毁对象。
十一,block和代理的区别。
1,block是一个对象,代理(Delegate)是一种设计模式;
2,block是一种轻量级的回调,可以直接访问上下文,Delegate要声明协议、声明代理属性、遵守协议、实现协议里的方法;
3,block运行成本很高,对于内存来说,因为它会将block内部引用的外部变量拷贝一份在堆里。block容易造成循环引用,delegate只是保存了一个对象的weak指针,不会造成循环引用;
十二,定机器有几种,哪种比较准确,为什么?
1,NSTimer,不是很准,如果当前定时器是在主线程中添加,那么当前定时器运行在主线程中的Runloop中,那么主线程中的Runloop进行,UI界面的操作,复杂的运算,这样就会造成Timer的阻塞,导致没有按照准备的时间去执行消息;
解决:
①,将当前模式更换为共享模式NSRunLoopCommonModes;
②,在子线程中创建定时器,并且开启子线程的Runloop进行消息接收。
2,CADisplayLink,也是讲当前定时器加到了Runloop种,RunLoop选择其他模式或被耗时操作过多时,仍旧会造成延迟。
CADisplayLink也会受到CPU负载的影响,造成不准确。
CADisplayLink默认每秒刷新60次,基于屏幕刷新率进行刷新的,所以每1/60之一秒执行一次,如果要修改,通过方法preferredFramesPerSecond。
使用:
@property (nonatomic)CADisplayLink *disLink; // self.disLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(disEvent)]; self.disLink.preferredFramesPerSecond = 1; [self.disLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; // - (void)dealloc { [self.disLink invalidate]; self.disLink = nil; }
CADisplayLink也容易造成循环引用,所以以上代码会有内存泄露,最好用中间件。
3,dispatch_source_t,GCD定时器,dispatch_source_t可以使用子线程进行事件发送执行,并且可以在设置的时候设置最大误差,所以比较准时。
@property (nonatomic,strong)dispatch_source_t dptTimer; //创建 self.dptTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)); //设置 dispatch_source_set_timer(self.dptTimer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0.0 * NSEC_PER_SEC); dispatch_source_set_event_handler(self.dptTimer, ^{ NSLog(@"%@",@"dispatch_source_set_event_handler"); }); //启动 dispatch_resume(self.dptTimer); //终止 dispatch_suspend(self.dptTimer);
十三,http和https的区别,s是在哪一层加上去的?
1,HTTPS是HTTP的安全版,在HTTP的基础上加入SSL层(Secure Socket Layer 安全套接字),对数据传输(客户端发送数据和服务器响应数据)进行加密和身份验证;Http是明文进行传输;
2,Https因为在请求连接的时候进行了加密,所以请求速度比Http慢;
3,HTTP 默认端口是 80 ,而 HTTPS 默认端口是 443;
4,Https在服务器端必须保存有一公钥证书,来验证当前连接是否安全。
十四,coredata和sqlite的区别。
Coredata是苹果对Sqlite进行的封装;
1,因为做了封装,CoreData使用起来比较方法,Sqlite比较直观,因为自己手动写Sql语句;
2,数据量比较大的时候CoreData不管是处理迁移还是增删改查操作比较方便,Sql比较繁琐;
3,查询效率和存储效率来讲,不相上下。
十五,两个有序数组,怎么进行合并并且合并后的排序(考时间复杂度)?
NSInteger f_arr[6] = {1,2,3,4,5,10}; NSInteger s_arr[4] = {0,2,5,11}; NSInteger e_array[10]; NSInteger f_count = sizeof(f_arr) / sizeof(f_arr[0]); NSInteger s_count = sizeof(s_arr) / sizeof(s_arr[0]); NSInteger f_in = 0,s_in = 0,e_in = 0; while (f_in < f_count && s_in < s_count) { if(f_arr[f_in] < s_arr[s_in]){ e_array[e_in++] = f_arr[f_in++]; }else{ e_array[e_in++] = s_arr[s_in++]; } } while (f_in < f_count) { e_array[e_in++] = f_arr[f_in++]; } while (s_in < s_count) { e_array[e_in++] = s_arr[s_in++]; } NSInteger e_count = sizeof(e_array) / sizeof(e_array[0]); for(NSInteger i = 0; i < e_count; i++){ printf("%ld-",e_array[i]); }
时间复杂度为:O(n),n为最大数组的长度。这里可理解为6次。
十六,masonry布局时,约束优先级。
设置优先级,告诉系统,约束的时候哪个控件的内容是必须全部展示,哪个不需要全部展示,根据方法setContentCompressionResistancePriority进行设置,参数:
UILayoutPriorityDefaultLow 代表优先级比较低,不需要全部进行显示,Low,低级的。
UILayoutPriorityRequired 优先级比较高,若当前控件内容比较多,先考虑全部展示此控件的内容,然后去考虑其他控件,Required必须的。
参数:
UILayoutConstraintAxisHorizontal,横向;
UILayoutConstraintAxisVertical, 纵向;
[label1 mas_makeConstraints:^(MASConstraintMaker *make) { make... }]; [label2 mas_makeConstraints:^(MASConstraintMaker *make) { make... }]; [label1 setContentCompressionResistancePriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisHorizontal]; [label2 setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
十七,NSOperationQueue的依赖关系。
1,NSOperationQueue依赖关系,addOperation;
2,NSOperationQueue最大并发数:maxConcurrentOperationCount
3,等待执行完后:
添加到队列中 waitUntilFinished:是否等待,会卡主当前线程:
[self.opQueue addOperations:@[op1,op2] waitUntilFinished:YES];
主线程通知用户:[[NSOperationQueue mainQueue] addOperation:op3];
NSOperation和GCD的区别:
1,NSOperation是对GCD的一种封装;
2,NSOperation可以去设置任务间的依赖关系,GCD无法设置依赖关系,GCD要达到效果只能通过栅栏函数实现,比较复杂的任务GCD设置起来比较费事儿;
3,NSOperationQueue支持KVO,可以监测任务是否执行,取消,结束;GCD不行;
4,一般的需求比较简单的多线程操作,用GCD简单高效。各个操作之间有依赖关系并且比较复杂的任务关系,用NSOperation更方便管理。
十八,多个子线程,按照顺序执行,用什么方法,例如A执行完执行B,B执行完执行C,以此类推。
示例,首先通过栅栏函数dispatch_barrier_async实现多线程有序执行:
NSMutableDictionary *mDic = [NSMutableDictionary dictionary]; for (NSInteger i = 0; i < 10; i++) { dispatch_group_enter(group); dispatch_async(userQueue, ^{ //这里可以做异步处理 NSLog(@"%ld - %@",i,[NSThread currentThread].description); dispatch_group_leave(group); }); dispatch_barrier_async(userQueue, ^{ //这里做异步处理完之后的操作 [mDic setValue:[NSString stringWithFormat:@"%ld - %@",i,[NSThread currentThread].description] forKey:[NSString stringWithFormat:@"%ld",i]]; }); } //最后所有的异步线程执行完,再汇总给notify dispatch_group_notify(group, userQueue, ^{ NSLog(@"%@",mDic); }); //输出 2020-11-25 21:46:19.888135+0800 xxxyDemo[64734:3784183] 0 - <NSThread: 0x6000003d7300>{number = 7, name = (null)} 2020-11-25 21:46:19.888341+0800 xxxyDemo[64734:3784183] 1 - <NSThread: 0x6000003d7300>{number = 7, name = (null)} 2020-11-25 21:46:19.888518+0800 xxxyDemo[64734:3784183] 2 - <NSThread: 0x6000003d7300>{number = 7, name = (null)} 2020-11-25 21:46:19.888672+0800 xxxyDemo[64734:3784183] 3 - <NSThread: 0x6000003d7300>{number = 7, name = (null)} 2020-11-25 21:46:19.888824+0800 xxxyDemo[64734:3784183] 4 - <NSThread: 0x6000003d7300>{number = 7, name = (null)} 2020-11-25 21:46:19.888992+0800 xxxyDemo[64734:3784183] 5 - <NSThread: 0x6000003d7300>{number = 7, name = (null)} 2020-11-25 21:46:19.889147+0800 xxxyDemo[64734:3784183] 6 - <NSThread: 0x6000003d7300>{number = 7, name = (null)} 2020-11-25 21:46:19.889330+0800 xxxyDemo[64734:3784184] 7 - <NSThread: 0x6000003ac8c0>{number = 8, name = (null)} 2020-11-25 21:46:19.889598+0800 xxxyDemo[64734:3784184] 8 - <NSThread: 0x6000003ac8c0>{number = 8, name = (null)} 2020-11-25 21:46:19.890265+0800 xxxyDemo[64734:3784184] 9 - <NSThread: 0x6000003ac8c0>{number = 8, name = (null)} 2020-11-25 21:46:19.890951+0800 xxxyDemo[64734:3784183] { 0 = "0 - <NSThread: 0x6000003d7300>{number = 7, name = (null)}"; 1 = "1 - <NSThread: 0x6000003d7300>{number = 7, name = (null)}"; 2 = "2 - <NSThread: 0x6000003d7300>{number = 7, name = (null)}"; 3 = "3 - <NSThread: 0x6000003d7300>{number = 7, name = (null)}"; 4 = "4 - <NSThread: 0x6000003d7300>{number = 7, name = (null)}"; 5 = "5 - <NSThread: 0x6000003d7300>{number = 7, name = (null)}"; 6 = "6 - <NSThread: 0x6000003d7300>{number = 7, name = (null)}"; 7 = "7 - <NSThread: 0x6000003ac8c0>{number = 8, name = (null)}"; 8 = "8 - <NSThread: 0x6000003ac8c0>{number = 8, name = (null)}"; 9 = "9 - <NSThread: 0x6000003ac8c0>{number = 8, name = (null)}"; }
第二种,用户串行队列,通过队列组进行顺序执行:
dispatch_queue_t userQueue_2 = dispatch_queue_create("com.iashes.com", DISPATCH_QUEUE_SERIAL); dispatch_group_t group_1 = dispatch_group_create(); for(NSInteger i = 0; i < 1000; i++){ dispatch_group_enter(group_1); dispatch_async(userQueue_2, ^{ NSLog(@"-%@-",@(i)); dispatch_group_leave(group_1); }); } dispatch_group_notify(group_1, userQueue_2, ^{ NSLog(@"全部结束"); });
第三种,如有更复杂的队列,用NSOperation。
十九,对称加密和非对称加密,以及MD5加密做下介绍。
对称加密:采用了对称密码编码技术,它的特点是文件加密和解密使用相同的密钥;
非对称加密:公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。
对称加密常用的有:AES;
非对称加密常用的有:RSA;
MD5是什么加密?
MD5用的是Hash算法,它是一种单向算法,通过目标信息获取一个唯一的hash值,而且这个hash值不能反向获得目标信息。
MD5长用于用户输入的密码进行加密后上传至服务器。
HTTPS分别用了对称加密和非对称加密。
非对称加密只作用在证书验证阶段,传输后续数据的一些加密信息,客户端校验证书是否合法等。
首先请求阶段,会使用非对称加密传输一个对称密钥。
对称加密用在数据传输阶段:
①,首先,客户端通过请求阶段传过来的秘钥进行数据加密传输到服务端;
②,服务端通过秘钥进行解密,再做其他处理;
③,服务端再通过秘钥将客户端需要的数据进行加密,然后传输给客户端。
传输数据为什么要用对称加密,因为非对称加密比较耗时,对称机密对于数据来说传输速度块。
二十,对图片如何进行压缩。
延展:
图片显示占内存大小 = 图片的宽度 X 图片的高度 X 颜色 RGBA 占用的字节数,RGBA占用的字节数是4,那么计算显示内存的大小为:
DisplayMemorySize = ImageWidth X ImageHeight X RGBA(4byte)。
二十一,weak底层。
weak,哈希表,key保存当前被weak修饰的对象的地址,value保存weak指针地址,也就是指向对象的weak指针地址,并不是被weak修饰的当前对象地址。
二十二,@sychronized原理及性能。
使用@synchronized会创建一个递归(recursive)互斥(mutex)的锁与 object参数进行关联。递归锁是为了同步锁嵌套使用不让其出现死锁现象。
sychronized 的每个对象,Runtime 都会为其分配一个递归锁并存储在哈希表中,哈希中key为传入的object的内存地址,value为一个结构体,保存了当前被转换后的:
id object,
recursive_mutex_t mutex; 此对象关联的锁;
struct SyncData* nextData; 包含了指向像一个节点的结构体,防止多个线程并发调用,所以是一个递归锁,在运行objc_sync_enter的时候,会进行对象的转换:SyncData* data = id2data(obj, ACQUIRE);
int threadCount; 线程数;
然后内部通过调用:
objc_sync_enter(obj)进行上锁;
objc_sync_exit(obj)进行解锁;
性能图,引用:https://juejin.cn/post/6844904099159212039
可看出来@synchronized性能是最差的,所以可以用NSLock去代替他,NSLock内部其实是用pthread进行实现的。
二十三,A类是B类的父类,运行时,B类的方法和A类的方法进行调换,会不会对A类造成影响,如果会,则会有什么影响?(防止数组越界)