将来跳槽用iOSiOS面试&笔试

iOS-XL公司面试题

2019-06-20  本文已影响55人  路飞_Luck
目录
  • 对象引用计数放哪里?
  • MVVM和MVC的区别
  • UIButton防止多次点击
  • 如何监听弱网
  • 卡顿检测
  • NSCache,NSDictionary,NSArray的区别
  • SDWebImage里面用了哪种缓存策略?
  • self + weakSelf + strongSelf ?
一 对象引用计数放哪里?

我们先看看 struct objc_class 的结构

image.png

再看看 isa结构

image.png
/** isa_t 结构体 */
union isa_t {
    Class cls;
    uintptr_t bits;
    struct {
        uintptr_t nonpointer        : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 33;
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 19;
    };
};

对象引用计数就存放在extra_rc

相关参考

iOS-底层原理(13)-runtime之isa详解
iOS-底层原理(14)isa-Class的结构详解

二 MVVM和MVC的区别
2.1 MVC
MVC.png

MVC的弊端

你可能试着把它放在Model对象里,但是也会很棘手,因为网络调用应该使用异步,这样如果一个网络请求比持有它的model生命周期更长,事情将变的复杂。显然View里面做网络请求那就更格格不入了,因此只剩下Controller了。若这样,这又加剧了Massive View Controller的问题。若不这样,何处才是网络逻辑的家呢?

由于View Controller混合了视图处理逻辑和业务逻辑,分离这些成分的单元测试成了一个艰巨的任务。

2.2 MVVM

一种可以很好地解决Massive View Controller问题的办法就是将 Controller 中的展示逻辑抽取出来,放置到一个专门的地方,而这个地方就是 viewModel 。MVVM衍生于MVC,是对 MVC 的一种演进,它促进了 UI 代码与业务逻辑的分离。它正式规范了视图和控制器紧耦合的性质,并引入新的组件。他们之间的结构关系如下:

MVVM.png
2.2.1 MVVM 的基本概念
2.2.2 MVVM 的注意事项
2.2.3 MVVM 的优势
2.2.4 MVVM 的弊端
2.3 总结

相关参考
iOS 关于MVC和MVVM设计模式的那些事

三 UIButton防止多次点击
3.1 设置enabled或userInteractionEnabled属性

通过UIButtonenabled属性和userInteractionEnabled属性控制按钮是否可点击。此方案在逻辑上比较清晰、易懂,但具体代码书写分散,常常涉及多个地方。

- (void)tapBtn:(UIButton *)btn {
    NSLog(@"按钮点击...");
    btn.enabled = NO;
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        btn.enabled = YES;
    });
}
3.2 借助cancelPreviousPerformRequestsWithTarget:selector:object实现

通过 NSObject 的两个方法
实现步骤如下:

// 此方法会在连续点击按钮时取消之前的点击事件,从而只执行最后一次点击事件
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument;
// 多长时间后做某件事情
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
/** 方法一 */
- (void)tapBtn:(UIButton *)btn {
    NSLog(@"按钮点击了...");
    // 此方法会在连续点击按钮时取消之前的点击事件,从而只执行最后一次点击事件
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(buttonClickedAction:) object:btn];
    // 多长时间后做某件事情
    [self performSelector:@selector(buttonClickedAction:) withObject:btn afterDelay:2.0];
}

- (void)buttonClickedAction:(UIButton *)btn {
    NSLog(@"真正开始执行业务 - 比如网络请求...");
}
3.3 通过runtime交换方法实现
@interface UIButton (Extension)

/** 时间间隔 */
@property(nonatomic, assign)NSTimeInterval cs_eventInterval;

@end
#import "UIButton+Extension.h"
#import <objc/runtime.h>

static char *const kEventIntervalKey = "kEventIntervalKey"; // 时间间隔
static char *const kEventInvalidKey = "kEventInvalidKey";   // 是否失效

@interface UIButton()

/** 是否失效 - 即不可以点击 */
@property(nonatomic, assign)BOOL cs_eventInvalid;

@end

@implementation UIButton (Extension)

+ (void)load {
    // 交换方法
    Method clickMethod = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
    Method cs_clickMethod = class_getInstanceMethod(self, @selector(cs_sendAction:to:forEvent:));
    method_exchangeImplementations(clickMethod, cs_clickMethod);
}

#pragma mark - click

- (void)cs_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    if (!self.cs_eventInvalid) {
        self.cs_eventInvalid = YES;
        [self cs_sendAction:action to:target forEvent:event];
        [self performSelector:@selector(setCs_eventInvalid:) withObject:@(NO) afterDelay:self.cs_eventInterval];
    }
}

#pragma mark - set | get

- (NSTimeInterval)cs_eventInterval {
    return [objc_getAssociatedObject(self, kEventIntervalKey) doubleValue];
}

- (void)setCs_eventInterval:(NSTimeInterval)cs_eventInterval {
    objc_setAssociatedObject(self, kEventIntervalKey, @(cs_eventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (BOOL)cs_eventInvalid {
    return [objc_getAssociatedObject(self, kEventInvalidKey) boolValue];
}

- (void)setCs_eventInvalid:(BOOL)cs_eventInvalid {
    objc_setAssociatedObject(self, kEventInvalidKey, @(cs_eventInvalid), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

推荐参考 iOS-UIButton防止重复点击(三种办法)

四 如何监听弱网

查阅了一些资料,暂时还没有编码实现,后期会更新

参考文章
iOS下的实际网络连接状态检测
[前端测试] 弱网测试方法整理

五 卡顿检测

参考以下文章

质量监控-卡顿检测

简单监测iOS卡顿的demo

六 NSCache,NSDictionary,NSArray的区别
6.1 NSArray

NSArray作为一个存储对象的有序集合,可能是被使用最多的集合类。

性能特征
在数组的开头和结尾插入/删除元素通常是一个O(1)操作,而随机的插入/删除通常是 O(N)的。

有用的方法
NSArray的大多数方法使用isEqual:来检查对象间的关系(例如containsObject:)。有一个特别的方法

indexOfObjectIdenticalTo:

用来检查指针相等,如果你确保在同一个集合中搜索,那么这个方法可以很大的提升搜索速度。

更多相关资料参考

6.2 NSDictionary

一个字典存储任意的对象键值对。 由于历史原因,初始化方法使用相反的对象到值的方法,

[NSDictionary dictionaryWithObjectsAndKeys:object, key, nil]

而新的快捷语法则从key开始

@{key : value, ...}

NSDictionary中的键是被拷贝的并且需要是恒定的。如果在一个键在被用于在字典中放入一个值后被改变,那么这个值可能就会变得无法获取了。一个有趣的细节,在NSDictionary中键是被拷贝的,而在使用一个toll-free桥接的CFDictionary时却只被retain。CoreFoundation类没有通用对象的拷贝方法,因此这时拷贝是不可能的(*)。这只适用于使用CFDictionarySetValue()的时候。如果通过setObject:forKey使用toll-free桥接的CFDictionary,苹果增加了额外处理逻辑来使键被拷贝。反过来这个结论则不成立 — 转换为CFDictionary的NSDictionary对象,对其使用CFDictionarySetValue()方法会调用回setObject:forKey并拷贝键。

6.3 NSCache

NSCache是一个非常奇怪的集合。在iOS 4/Snow Leopard中加入,默认为可变并且线程安全的。这使它很适合缓存那些创建起来代价高昂的对象。它自动对内存警告做出反应并基于可设置的成本清理自己。与NSDictionary相比,键是被retain而不是被拷贝的。

NSCache的回收方法是不确定的,在文档中也没有说明。向里面放一些类似图片那样比被回收更快填满内存的大对象不是个好主意。(这是在PSPDFKit中很多跟内存有关的crash的原因,在使用自定义的基于LRU的链表的缓存代码之前,我们起初使用NSCache存储事先渲染的图片。)

NSCache可以设置撑自动回收实现了NSDiscardableContent协议的对象。实现该属性的一个比较流行的类是同时间加入的NSPurgeableData,但是在OS X 10.9之前,是非线程安全的(没有信息表明这是否也影响到iOS或者是否在iOS 7中被修复了)。

NSCache性能

那么NSCache如何承受NSMutableDictionary的考验?加入的线程安全必然会带来一些消耗。

6.4 iOS 构建缓存时选 NSCache 而非NSDictionary

相关资料参考
iOS7: 漫谈基础集合类(NSArray,NSSet,NSOrderedSet,NSDictionary,NSMapTable,NSHashTable, NSPointerArray, NSIndexSet,NSCache, NSFastEnumeration)

七 SDWebImage里面用了哪种缓存策略?

使用了NSCache做缓存策略,具体的参考下面的文章,写的挺详细的

推荐参考文章

iOS缓存 NSCache详解及SDWebImage缓存策略源码分析

八 self + weakSelf + strongSelf ?
__weak __typeof(self)weakSelf = self;    //1

[self.context performBlock:^{      
    [weakSelf doSomething];          //2
     __strong __typeof(weakSelf)strongSelf = weakSelf;  //3
    [strongSelf doAnotherSomething];        
}];
1.使用__weak __typeof是在编译的时候,另外创建一个局部变量weak对象来操作self,引用计数不变。
block 会将这个局部变量捕获为自己的属性,
访问这个属性,从而达到访问 self 的效果,因为他们的内存地址都是一样的。

2.因为weakSelf和self是两个变量,doSomething有可能就直接对self自身引用计数减到0了.
  所以在[weakSelf doSomething]的时候,你很难控制这里self是否就会被释放了.weakSelf只能看着.

3.__strong __typeof在编译的时候,实际是对weakSelf的强引用.
  指针连带关系self的引用计数会增加.但是你这个是在block里面,生命周期也只在当前block的作用域.
  所以,当这个block结束, strongSelf随之也就被释放了.不会影响block外部的self的生命周期.

总结

  • 在 Block 内如果需要访问 self 的方法、变量,建议使用 weakSelf。
  • 如果在 Block 内需要多次 访问 self,则需要使用 strongSelf。

推荐参考文章
iOS-Block 中 为何使用 weakSelf 和 strongSelf

上一篇 下一篇

猜你喜欢

热点阅读