面试亲历一些题 | 三

一,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类造成影响,如果会,则会有什么影响?(防止数组越界)

Leave a Reply

Required fields are marked *