IOS面试集锦

面试-看这里就足够了-直通BAT

Posted by JT on October 23, 2017

1. 架构

1.1 架构

1.1.1 架构

2. 网络

2.1 网络

参考

2.1.1 NSURLRequest

常见的请求头设置属性:

Host: 目标服务器的网络地址
Accept: 让服务端知道客户端所能接收的数据类型,如text/html
Content-Type: body中的数据类型,如application/json; charset=UTF-8
Accept-Language: 客户端的语言环境,如zh-cn
Accept-Encoding: 客户端支持的数据压缩格式,如gzip
User-Agent: 客户端的软件环境,我们可以更改该字段为自己客户端的名字,比如QQ music v1.11,比如浏览器Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/600.8.9 (KHTML, like Gecko) Maxthon/4.5.2
Connection: keep-alive,该字段是从HTTP 1.1才开始有的,用来告诉服务端这是一个持久连接,“请服务端不要在发出响应后立即断开TCP连接”。关于该字段的更多解释将在后面的HTTP版本简介中展开。
Content-Length: body的长度,如果body为空则该字段值为0。该字段一般在POST请求中才会有。
NSMutableURLRequest *mulrequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http"] cachePolicy:(NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:10];
    [mulrequest setHTTPMethod:@"POST"];

    NSString *TWITTERFON_FORM_BOUNDARY = @"12344321";

    NSString *content = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",TWITTERFON_FORM_BOUNDARY];
    [mulrequest setValue:content forHTTPHeaderField:@"Content-Type"];

    NSString *boundary = [NSString stringWithFormat:@"--%@",TWITTERFON_FORM_BOUNDARY];
    NSString *endBoundary = [NSString stringWithFormat:@"--%@--",TWITTERFON_FORM_BOUNDARY];
    
    NSDictionary *param = @{@"name":@"mike"};
    NSMutableString *body = [NSMutableString string];
    NSArray *keys = [param allKeys];
    for (NSString *key in keys) {
        [body appendFormat:@"%@\r\n",boundary];
        [body appendFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n",key];
        [body appendFormat:@"%@\r\n",[dic objectForKey:key]];
    }

    [body appendFormat:@"%@\r\n",boundary];
    [body appendFormat:@"Content-Disposition:form-data; name=\"pic\"; filename=\"myPic.jpg\"\r\n"];
    [body appendFormat:@"Cotent-Type: image/png"];

    UIImage *image = [UIImage imageNamed:@"pic"];
    NSData *imageData = UIImageJPEGRepresentation(image, 0.3);

    NSMutableData *requestData = [NSMutableData data];
    [requestData appendData:[body dataUsingEncoding:NSUTF8StringEncoding]];
    [requestData appendData:imageData];
    [requestData appendData:[[NSString stringWithFormat:@"\r\n%@",endBoundary] dataUsingEncoding:NSUTF8StringEncoding]];

    [mulrequest setValue:[NSString stringWithFormat:@"%lu", [requestData length]] forHTTPHeaderField:@"Content-Length"];

    [mulrequest setHTTPBody:requestData];
    
    NSURLSessionConfiguration *sessionCon = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionCon];
    NSURLSessionDataTask *task = [session dataTaskWithRequest:mulrequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSLog(@"complite");
    }];
    [task resume];

4. 信息安全

4.1 (防止反编译、APP安全)

HTTPS配置

HTTPS双向配置

HTTPS配置

HTTPS配置

本地数据加密

对NSUserDefaults,sqlite存储文件数据加密,保护帐号和关键信息

  • FMDB数据库加密
  • 对数据库内容加密,存的时候加密,用得时候解密。
  • 直接对数据库文件加密(pod ‘FMDB/SQLCipher’)FMDB FMDB加密

URL编码加密

对程序中出现的URL进行编码加密,防止URL被静态分析。(YYKit 分类)

admin.php?act=zhongguo&tx=123 编码 admin.php%3Fact%3Dzhongguo%26tx%3D123

?=> %3F
= => %3D
% => %25
& => %26
\ => %5C

网络传输数据加密

防抓包

对客户端传输数据提供加密方案,有效防止通过网络接口的拦截获取数据。

签名实现方案:

#pragma mark - HeaderAndSign

/**
 设置header字段

 @param parameters body中的参数
 */
- (void)setHTTPHeaderWithBodyParameters:(NSDictionary *)parameters {
    NSDictionary *HTTPHeaderField = [self theHTTPHeaderFieldNoSign];
    NSDictionary *unsignedParameters = [self theUnsignedParameters:parameters];
    NSString *sign = [self signStringWithParameters:unsignedParameters];
    [HTTPHeaderField enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        [self.requestSerializer setValue:obj forHTTPHeaderField:key];
    }];
    [self.requestSerializer setValue:kSafeString(sign) forHTTPHeaderField:@"wkch-sign"];
}

/**
 获取不包括sign的header参数

 @return header参数
 */
- (NSDictionary *)theHTTPHeaderFieldNoSign {

    NSString *uniqueId = [JGKeyChainDataManager readUUID];
    if (isEmpty(uniqueId)) {
        uniqueId = [JGKeyChainDataManager createUUID];
        [JGKeyChainDataManager saveUUID:uniqueId];
    }
    NSString *version = ([[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"]);
    NSString *timestamp = [NSString stringWithFormat:@"%.f", [[NSDate date] timeIntervalSince1970]];
    NSString *mobileAccessToken = kSafeString(kUserInfo.userInfoModel.accessToken);
    NSString *uIdString = isEmpty(kUserInfo.userInfoModel.id) ? @"0" : kSafeString(kUserInfo.userInfoModel.id);
    // deviceType 设备:1:安卓 2:iOS
    NSDictionary *dictionary = @{
                                 @"deviceType" : @"2",
                                 @"uniqueId" : kSafeString(uniqueId),
                                 @"version" : kSafeString(version),
                                 @"app" : @"bundle-id",
                                 @"timestamp" : kSafeString(timestamp),
                                 @"uId" : uIdString,
                                 @"mobileAccessToken" : kSafeString(mobileAccessToken)
                                 };
    return dictionary;
}

/**
 获取未签名的所有参数

 @param params 参数
 @return 未签名的所有参数
 */
- (NSDictionary *)theUnsignedParameters:(NSDictionary *)params {
    NSDictionary *headerDictionary = [self theHTTPHeaderFieldNoSign];
    NSMutableDictionary *muDictionary = [NSMutableDictionary dictionaryWithDictionary:headerDictionary];
    if (params) {
        [muDictionary addEntriesFromDictionary:params];
    }
    return muDictionary;
}

/**
 获取加密后的sign字符串

 @param parameters 所有参数
 @return sign字符串
 */
- (NSString *)signStringWithParameters:(NSDictionary *)parameters {
    
    NSStringCompareOptions comparisonOptions = (NSCaseInsensitiveSearch |
                                                NSNumericSearch |
                                                NSWidthInsensitiveSearch |
                                                NSForcedOrderingSearch);
    NSComparator sort = ^(NSString *obj1, NSString *obj2) {
        NSRange range = NSMakeRange(0, obj1.length);
        return [obj1 compare:obj2 options:comparisonOptions range:range];
    };
    NSArray *allKeys = parameters.allKeys;
    NSArray *sortedAllKeys = [allKeys sortedArrayUsingComparator:sort];
    NSMutableArray *keyValues = [[NSMutableArray alloc] initWithCapacity:sortedAllKeys.count];
    for (NSString *key in sortedAllKeys) {
        NSString *keyValue = [NSString stringWithFormat:@"%@=%@",key, parameters[key]];
        [keyValues addObject:keyValue];
    }
    
    NSString *sign = [keyValues componentsJoinedByString:@"&"];
    if (isEmpty(sign)) {
        return @"";
    }
   
    return [sign md5Hash];
}

方法体,方法名高级混淆

对应用程序的方法名和方法体进行混淆,保证源码被逆向后无法解析代码。

程序结构混排加密

对应用程序逻辑结构进行打乱混排,保证源码可读性降到最低

借助第三方APP加固,例如:网易云易盾

4.2 加密解密

  • 对称加密是最快速、最简单的一种加密方式,加密(encryption)与解密(decryption)用的是同样的密钥(secret key)。
  • 非对称加密算法需要两个密钥:公开密钥(publickey)和私有密钥(privatekey)。公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。安全性高,但加密与解密速度慢

Base64(对称)

大致以“=”号结尾

AES(对称)

高级加密标准(英语:Advanced Encryption Standard,缩写:AES),是一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。

那么为什么原来的DES会被取代呢,,原因就在于其使用56位密钥,比较容易被破解。而AES可以使用128、192、和256位密钥,并且用128位分组加密和解密数据,相对来说安全很多。完善的加密算法在理论上是无法破解的,除非使用穷尽法。使用穷尽法破解密钥长度在128位以上的加密数据是不现实的,仅存在理论上的可能性。统计显示,即使使用目前世界上运算速度最快的计算机,穷尽128位密钥也要花上几十亿年的时间,更不用说去破解采用256位密钥长度的AES算法了。

目前世界上还有组织在研究如何攻破AES这堵坚厚的墙,但是因为破解时间太长,AES得到保障,但是所用的时间不断缩小。随着计算机计算速度的增快,新算法的出现,AES遭到的攻击只会越来越猛烈,不会停止的。

AES现在广泛用于金融财务、在线交易、无线通信、数字存储等领域,经受了最严格的考验,但说不定哪天就会步DES的后尘。

MD5(非对称)

历史版本有MD2、MD4、MD5,消息摘要算法各个版本间的结果是不一样的。MD5是目前广泛使用的版本,不过其安全性多年前就开始被质疑(碰撞算法)。于是在2008年提出了MD6算法,其后MD6历经数次改进,目前还是试行方案阶段,未被正式使用。

消息摘要算法,32位=16个2位16进制。

SHA(非对称)

安全散列算法,包括SHA-1、SHA-224、SHA-256、SHA-384,和SHA-512,和MD5类似,但安全性要高于MD5,运算性能有低于MD5。

RSA(非对称)

  • 加密/解密和签名/验签过程

A->B:

  1. A提取消息m的消息摘要h(m),并使用自己的私钥对摘要h(m)进行加密,生成签名s
  2. A将签名s和消息m一起,使用B的公钥进行加密,生成密文c,发送给B

B:

  1. B接收到密文c,使用自己的私钥解密c得到明文m和数字签名s
  2. B使用A的公钥解密数字签名s解密得到H(m)
  3. B使用相同的方法提取消息m的消息摘要h(m)
  4. B比较两个消息摘要。相同则验证成功;不同则验证失败

例:PHP后台,邀请码,对用户敏感信息进行加密

5. 语法

5.1 const常量与define宏定义

5.1.1 const与#define的区别

#define RADIUS 100;
const  float   RADIUS = 100;
  1. 编译器处理方式不同
    • define宏是在预处理阶段展开。
    • const常量是编译运行阶段使用。
  2. 类型和安全检查不同
    • define宏没有类型,不做任何类型检查,仅仅是展开。
    • const常量有具体的类型,在编译阶段会执行类型检查。
  3. 存储方式不同
    • define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存。(宏定义不分配内存,变量定义分配内存。)
    • const常量会在内存中分配(可以是堆中也可以是栈中)。
  4. const可以节省空间,避免不必要的内存分配.

    ‘#define PI 3.14159 //常量宏
    const doulbe Pi=3.14159; //此时并未将Pi放入ROM中
    double i=Pi; //此时为Pi分配内存,以后不再分配!
    double I=PI; //编译期间进行宏替换,分配内存
    double j=Pi; //没有内存分配
    double J=PI; //再进行宏替换,又一次分配内存
    const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝(因为是全局的只读变量,存在静态区),而 #define定义的常量在内存中有若干个拷贝。

  5. 提高了效率

    编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。

  6. 宏替换只作替换,不做计算,不做表达式求解
    • 宏预编译时就替换了,程序运行时,并不分配内存。

5.1.2 const与#define的比较

C++ 语言可以用const来定义常量,也可以用 #define来定义常量。但是前者比后者有更多的优点:

  1. const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误(边际效应)。
  2. 有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。

5.2 数组(字典)中添加弱引用;NSPointerArray(NSArray)、NSHashTable(NSSet)、NSMapTable(NSDictionary)

自定义弱引用:

  • 通过NSValue的方法+ valueWithNonretainedObject: nonretainedObjectValue存取对象
- (nullable id)objectForKey:(id<NSCopying>)aKey {
    NSValue *value = [self objectForKey:aKey];
    
    return value.nonretainedObjectValue;
}

- (void)fm_setObject:(id)anObject forKey:(id <NSCopying>)aKey {
    NSValue *value = [NSValue valueWithNonretainedObject:anObject];
    [self setObject:value forKey:aKey];
}

- (void)fm_setDictionary:(NSDictionary *)otherDictionary {
    [otherDictionary enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key,
                                                         id  _Nonnull obj,
                                                         BOOL * _Nonnull stop) {
        [self fm_setObject:obj forKey:key];
    }];
}
  • 用block封装与解封
typedef id (^WeakReference)(void);

WeakReference makeWeakReference(id object) {
    __weak id weakref = object;
    return ^{
        return weakref;
    };
}

id weakReferenceNonretainedObjectValue(WeakReference ref) {
    return ref ? ref() : nil;
}
- (void)weak_setObject:(id)anObject forKey:(NSString *)aKey {
    [self setObject:makeWeakReference(anObject) forKey:aKey];
}

- (void)weak_setObjectWithDictionary:(NSDictionary *)dic {
    for (NSString *key in dic.allKeys) {
        [self setObject:makeWeakReference(dic[key]) forKey:key];
    }
}

- (id)weak_getObjectForKey:(NSString *)key {
    return weakReferenceNonretainedObjectValue(self[key]);
}
  • 使用NSProxy 的子类,像YYKit 这套框架就是用的这种方法
@interface YYWeakProxy : NSProxy

/**
 The proxy target.
 */
@property (nullable, nonatomic, weak, readonly) id target;

/**
 Creates a new weak proxy for target.
 @param target Target object.
 @return A new proxy object.
 */
- (instancetype)initWithTarget:(id)target;

+ (instancetype)proxyWithTarget:(id)target;

@end
@implementation YYWeakProxy

- (instancetype)initWithTarget:(id)target {
    _target = target;
    return self;
}

+ (instancetype)proxyWithTarget:(id)target {
    return [[YYWeakProxy alloc] initWithTarget:target];
}

- (id)forwardingTargetForSelector:(SEL)selector {
    return _target;
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    void *null = NULL;
    [invocation setReturnValue:&null];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
    return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}

- (BOOL)respondsToSelector:(SEL)aSelector {
    return [_target respondsToSelector:aSelector];
}

//... 

@end
@implementation MyView {
    NSTimer *_timer;
}

- (void)initTimer {
    YYWeakProxy *proxy = [YYWeakProxy proxyWithTarget:self];
    _timer = [NSTimer timerWithTimeInterval:0.1
                                     target:proxy
                                   selector:@selector(tick:)
                                   userInfo:nil
                                    repeats:YES];
}

- (void)tick:(NSTimer *)timer {...}
@end

5.3 @private @protected @public @package

@private 私有的,也就是只有自己有,别人谁都不可用,哪怕亲如自己的孩子。

@protected 受保护的,他自己可以用,自己的孩子也是可以共享的。

@public 公共的,谁都可以用,只要你有这个类的对象,就可以拿到public下的变量。

@package 这个主要是用于框架类。

5.5 valueForKey与valueForKeyPath

不同点:valueForKeyPath的使用更加广泛,功能也更加强大:

  • 1.如:sum/average/max/min
NSArray *array1 = @[@1, @3, @5, @7, @9,@11, @13];
NSInteger sum = [[array2 valueForKeyPath:@"@sum.floatValue"] integerValue];
NSInteger avg = [[array2 valueForKeyPath:@"@avg.floatValue"] integerValue];
NSInteger max = [[array2 valueForKeyPath:@"@max.floatValue"] integerValue];
NSInteger min = [[array2 valueForKeyPath:@"@min.floatValue"] integerValue];
  • 2.删除数组中重复的数据
NSArray *array2 = @[@1, @3, @5, @7, @9,@11, @13, @7, @9,@11];
NSLog(@"deleteKeyPath---%@",[array2 valueForKeyPath:@"@distinctUnionOfObjects.self"]);
  • 3.深层次取字典中出子属性
valueForKeyPath:可以深层次取到子属性,不管隐藏的多深
valueForKey:无法取到深层次子属性
NSDictionary *dic = @{@"dic1":@{@"dic2":@{@"name":@"zhangsanfeng",@"info":@{@"age":@"13"}}}};
NSLog(@"KeyPath---%@",[dic valueForKeyPath:@"dic1.dic2.info.age"]);///可以深层次的取到子层级属性
NSLog(@"Key---%@",[dic valueForKey:@"dic1.dic2.info.age"]);///无法深层次取到子层级属性

相同点:

  • 1.快速找到字典数组中key所对应的值
NSArray *dicArray = @[
       @{@"company":@"baidu",@"person":@{@"name":@"zhangsanfeng"}},
       @{@"position":@"chengdu"}];
NSLog(@"keyPath---%@", [dicArray valueForKeyPath:@"company"]);
NSLog(@"key-- -%@", [dicArray valueForKey:@"company"]);
  • 2.大小写字母转换
NSArray *arra3 = @[@"name",@"w",@"b",@"h",@"g",@"d",@"r",@"p"];
///打印由小写字母--->大写字母
NSLog(@"KeyPath---%@",[array valueForKeyPath:@"uppercaseString"]);
NSArray *newArray = [array valueForKey:@"uppercaseString"];
NSLog(@"newArray---%@",newArray);

5.5 weak 弱引用的实现方式

参考