iOS常用技巧

1、系统版本判断

#define SYSTEM_VERSION_EQUAL_TO(v)                  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame)
#define SYSTEM_VERSION_GREATER_THAN(v)              ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending)
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v)  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN(v)                 ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v)     ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending)

2、iOS 11后获取导航栏和底部高度的正确姿势

@interface UIWindow (TLCAdd)

/**
 获取安全区域底部的高度

 @return 安全区域底部的高度
 */
- (CGFloat)TLCBottomSpace;

/**
 获取导航栏的高度

 @return 导航栏的高度
 */
- (CGFloat)TLCNavigationBarHeight;

@end
#import "UIWindow+TLCAdd.h"

@implementation UIWindow (TLCAdd)

- (CGFloat)TLCBottomSpace {
    if (@available(iOS 11.0, *)) {
        return self.safeAreaInsets.bottom;
    }
    return 0;
}

- (CGFloat)TLCNavigationBarHeight {
    if (@available(iOS 11.0, *)) {
        return MAX(0, self.safeAreaInsets.top-20) + 64;
        
    }
    return 64;
}

@end

3、在iOS中如何正确的实现行间距与行高

1、直接设置lineSpacing,

NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.lineSpacing = 10;
NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
[attributes setObject:paragraphStyle forKey:NSParagraphStyleAttributeName];
label.attributedText = [[NSAttributedString alloc] initWithString:label.text attributes:attributes]; 

效果图如下:

088E89FA-DCC6-456C-BC70-C74EA6440947.png

红色区域是默认绘制单行文本会占用的区域,可以看到文字的上下是有一些留白的(蓝色和红色重叠的部分)
解决方法:我们需要在设置 lineSpacing 时,减去这个系统的自带边距:

NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.lineSpacing = 10 - (label.font.lineHeight - label.font.pointSize);
NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
[attributes setObject:paragraphStyle forKey:NSParagraphStyleAttributeName];
label.attributedText = [[NSAttributedString alloc] initWithString:label.text attributes:attributes]; 

效果图如下所示:


7924E065-0F70-492B-8953-1CF7BB7ACC24.png

4、iOS使用Instrument-Time Profiler工具排查卡顿问题

1、在TARGETS中将Debug Information Format设置为DWARF with dSYM File

1.png

2、将工程跑到真机上;
3、直接运行instruments
2.png

4、运行后,选择主线程,将系统库相关的调用隐藏掉,就可以找到哪个地方的代码调用比较耗时了。
3.png

4.png

5、添加监听

#import <Foundation/Foundation.h>
@interface IMECSServiceCenter : NSObject

@property(nonatomic, strong) NSHashTable *responders;

- (instancetype)initWithNotifyQueue:(dispatch_queue_t)notifyQueue;

- (void)addDelegate:(id)delegate;

- (void)removeDelegate:(id)delegate;

- (void)notifyServiceDelegate:(SEL)aSelector
                      perform:(void (^)(id responder))perform;

@end
#import "IMECSServiceCenter.h"
#import <MUPFoundation/MUPFoundation.h>

@interface IMECSServiceCenter()

@property(nonatomic, strong) dispatch_queue_t notifyQueue;

@end

@implementation IMECSServiceCenter

- (instancetype)init {
    self = [super init];
    if (self) {
        self.responders = [NSHashTable weakObjectsHashTable];
        self.notifyQueue = dispatch_get_main_queue();
    }
    return self;
}

- (instancetype)initWithNotifyQueue:(dispatch_queue_t)notifyQueue {
    self = [super init];
    if (self) {
        self.responders = [NSHashTable weakObjectsHashTable];
        self.notifyQueue = notifyQueue;
    }
    return self;
}

- (void)addDelegate:(id)delegate {
    @synchronized(self) {
        [self.responders addObject:delegate];
    }
}

- (void)removeDelegate:(id)delegate {
    @synchronized(self) {
        [self.responders removeObject:delegate];
    }
}

- (void)notifyServiceDelegate:(SEL)aSelector
                      perform:(void (^)(id responder))perform {
    dispatch_async(self.notifyQueue, ^{
        NSArray *responders = self.responders.allObjects;
        for (id responder in responders) {
            if ([responder respondsToSelector:aSelector]) {
                @try {
                    perform(responder);
                }
                @catch (NSException *exception) {
                    MUPLogWarn(@"catch notifyServiceDelegate exception: %@", exception);
                }
            }
        }
    });
}

@end

6、关于屏幕旋转 IOS8上面有个坑 枚举值跟9以上不同

NSNumber *value = @(UIDeviceOrientationLandscapeRight);
float systemVersion = [[UIDevice currentDevice].systemVersion floatValue];
if ( systemVersion >= 8.0 && systemVersion < 9 ) { //ios8 遇到的一个坑 要用这个值才能与 UIInterfaceOrientationMaskLandscapeRight匹配
    value = @(UIDeviceOrientationLandscapeLeft);
}
[[UIDevice currentDevice] setValue:value forKey:@"orientation"];

7、atomic无法解决线程安全问题

atomic 的内存管理语义是原子性的,仅保证了属性的setter和getter方法是原子性的,是线程安全的,但是属性的其他方法,如数组添加/移除元素等并不是原子操作,所以不能保证属性是线程安全的 如下面的代码会发生异常:

@interface ViewController ()
@property (atomic, strong) NSMutableDictionary *dictionary;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.dictionary = [NSMutableDictionary dictionary];
    for (int i = 0; i < 1000000; i++) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [self.dictionary setObject:@"1" forKey:[NSString stringWithFormat:@"%d",i]];
        });
        NSString *result = [self.dictionary objectForKey:[NSString stringWithFormat:@"%d",i]];
        NSLog(@"thread safe %@", result);
    }

8、在block执行过程中正确使用weakSelf、strongSelf

image.png
  • 用weakSelf保证了Self在Block里不会引发循环引用;
  • 在Block内的strongSelf保证在Block运行过程中Self不会被释放掉;
  • 如果在block被执行的时候self已经被释放,strongSelf仍然为空,因此仍然需要对strongSelf进行nil判断
    很多情况下我们没有对strongSelf判空也没有出现问题并不是因为它不为空,而是即使为空执行方法也不会报错,但仍需要理解,在调用非自己持有的block时,strongSelf并不能保证不为空。

9、危险的UITableView-reloadData

在某些情况下tableView:numberOfRowsInSection可能插在tableView:cellForRowAtIndexPath的中间,当tableView:numberOfRowsInSection的数据变小时,则可能造成tableView:cellForRowAtIndexPath获取数组时越界crash,所以UITableView的刷新是危险的。
解决方式:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row > self.arr.count - 1 || indexPath.row == 0) {
return [UITableViewCell new];
}
//...
NSNumber* content = _arr[indexPath.row];
//...
}

10、网络请求流程

image.png

为了保证服务端能收接受到客户端的信息并能做出正确的应答而进行前两次(第一次和第二次)握手,为了保证客户端能够接收到服务端的信息并能做出正确的应答而进行后两次(第二次和第三次)握手。
TCP建立连接为什么是三次握手?

这个问题的本质是, 信道不可靠, 但是通信双发需要就某个问题达成一致. 而要解决这个问题, 无论你在消息中包含什么信息, 三次通信是理论上的最小值. 所以三次握手不是TCP本身的要求, 而是为了满足"在不可靠信道上可靠地传输信息"这一需求所导致的. 请注意这里的本质需求,信道不可靠, 数据传输要可靠. 三次达到了, 那后面你想接着握手也好, 发数据也好, 跟进行可靠信息传输的需求就没关系了. 因此,如果信道是可靠的, 即无论什么时候发出消息, 对方一定能收到, 或者你不关心是否要保证对方收到你的消息, 那就能像UDP那样直接发送消息就可以了.”

image.png

通俗大白话来理解TCP协议的三次握手和四次分手

11、网络安全

一条网络请求需要经过的流程

1、DNS 解析,请求DNS服务器,获取域名对应的 IP 地址。
2、通过ARP解析,获取对应IP的mac地址。
3、与服务端建立连接,包括 tcp 三次握手,安全协议同步流程。
4、连接建立完成,发送和接收数据,解码数据。

DNS劫持
  • 劫持流程:DNS劫持又称域名劫持,是指在劫持的网络范围内拦截域名解析的请求,分析请求的域名,把审查范围以外的请求放行,否则返回假的IP地址或者什么都不做使请求失去响应,其效果就是对特定的网络不能访问或访问的是假网址。其实本质就是对DNS解析服务器做手脚,或者是使用伪造的DNS解析服务器可以通过下图来展示。


    image.png
  • 解决办法:我们可以不用运营商的DNS解析而使用自己的解析服务器或者是提前在自己的App中将解析好的域名以IP的形式发出去就可以绕过运营商DNS解析,这样一来也避免了DNS劫持的问题。可以通过下图来展示
    image.png

    iOS网络请求优化之DNS映射
HTTP内容劫持
  • 劫持流程:以举例子来说,A给B写信,写完邮寄给B,但是在邮寄的过程中,邮递员可能会偷偷的把信取出来看完后再放回去,收发室大爷可能也会偷偷取出来看一眼,B收到信的时候,可能在传输过程中已经被很多人看过了。如果信件中有一些银行卡账号密码什么的,可能在传输过程中已经被别人悄悄记下来了。可以通过下图来展示:


    image.png
  • 解决办法:A和B发现了这个问题,于是他们约定了下次写信要加密,A写信的时候加密,B收到信再解密,加密解密的方法只有A和B知道。同样在寄信的过程中,邮递员、收发室大爷偷偷看了信,但是完全看不懂,信件的内容不会泄露,就安全的多。即HTTPS。
  • HTTPS = HTTP+加密+认证+完整性保护


    image.png
  • 客户端发出握手请求(Client Hello),包含以下信息:
    • 支持的协议版本,比如TLS 1.0版。
    • 一个客户端生成的随机数(random_1),这个随机数既需要客户端保存又需要发送给服务器。
    • 支持的加密方法,比如RSA公钥加密。
    • 支持的压缩方法。
    • 服务器回复(Server Hello),包含以下信息:
      • 确认使用的加密通信协议版本,比如TLS 1.0版本。如果浏览器与服务器支持的版本不一致,服务器关闭加密通信。
      • 一个服务器生成的随机数(random_2)。
      • 确认使用的加密方法,比如RSA公钥加密。
      • 服务器证书。
      • 如果服务器需要确认客户端的身份,就会再包含一项请求,要求客户端提供”客户端证书”。比如,金融机构往往只允许认证客户连入自己的网络,就会向正式客户提供USB密钥,里面就包含了一张客户端证书。
    • 客户端回应,包含以下步骤:
      • 验证服务器证书的合法性,证书合法性包括:证书是否过期,发行服务器证书的 CA 是否可靠,发行者证书的公钥能否正确解开服务器证书的“发行者的数字签名”,服务器证书上的域名是否和服务器的实际域名相匹配。如果合法性验证没有通过,通讯将断开;
      • 客户端使用一些加密算法(例如:RSA,Diffie-Hellman)产生一个48个字节的Key,这个Key叫PreMaster Secret。该PreMaster Secret用服务器公钥加密传送,防止被窃听。
      • 编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送。
      • 客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供服务器校验。
      • 如果前一步,服务器要求客户端证书,客户端会在这一步发送证书及相关信息。
    • 服务器回应,服务器通过上面的三个随机数(random1,random2,PreMaster Secret),计算出本次会话的『会话密钥(session secret)』,然后向客户端发送下面信息
      • 编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送。
      • 服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供客户端校验。


        image.png
  • AFNetworking对服务端的身份进行认证有三种可选方案:
    • AFSSLPinningModeNone(Default):客户端自己不会对服务端证书进行自主校验,而是直接默认信任,将证书交给系统来进行校验,判断返回的证书是否是官方机构颁发的,如果是则信任,这个应该也是普通开发者采用最多的方案。这种认证方案如果被劫持后返回的证书也是官方机构颁发的,那么客户端就会正常进行网络访问,但是返回的数据就不是想要的数据,比如被塞广告等现象的出现。
    • AFSSLPinningModePublicKey:代表客户端会将服务器端返回的证书与本地保存的证书中的 PublicKey 部分进行自主校验。
    • AFSSLPinningModeCertificate:代表客户端会将服务器端返回的证书和本地保存的证书中的所有内容,包括 PublicKey 和证书部分全部进行自主校验。如果校验成功,才继续进行系统验证等后续行为。

12、Responder chains in an app

image.png

13、优化tableview滑动性能

  • 避免图层混合
    1、确保控件的opaque属性设置为true,确保backgroundColor和父视图颜色一致且不透明
    2、如无特殊需要,不要设置低于1的alpha值
    3、确保UIImage没有alpha通道

  • 避免图片的临时转换
    1、确保图片大小和frame一致,不要在滑动时缩放图片
    2、确保图片颜色格式被GPU支持,避免频繁CPU转换

  • 慎用离屏渲染
    1、绝大多数时候离屏渲染会影响性能
    2、重写drawRect方法,设置圆角、阴影、模糊效果,光栅化都会导致离屏渲染
    3、设置阴影效果是加上阴影路径

imgView.layer.shadowPath = UIBezierPath(rect: imgView.bounds).cgPath;

4、滑动时若需要圆角效果,开启光栅化。

// 设置圆角
label.layer.masksToBounds = true  
label.layer.cornerRadius = 8  
label.layer.shouldRasterize = true  
label.layer.rasterizationScale = layer.contentsScale  

14、OC中的load和initialize

1、load和initialize方法都会在实例化对象之前调用,以main函数为分水岭,前者在main函数之前调用,后者在之后调用。这两个方法会被自动调用,不能手动调用它们。
2、load和initialize方法都不用显式的调用父类的方法而是自动调用,即使子类没有initialize方法也会调用父类的方法,而load方法则不会调用父类。
3、load方法通常用来进行Method Swizzle,initialize方法一般用于初始化全局变量或静态变量。
4、load和initialize方法内部使用了锁,因此它们是线程安全的。实现时要尽可能保持简单,避免阻塞线程,不要再使用锁。

15、后台保活

利用后台下载任务实现。
关键在于:在handleEventsForBackgroundURLSession:completionHandler:方法不要执行completionHandler()

- (NSURLSession *)backgroundURLSession {
    __weak typeof(self) weak_self = self;

    static NSURLSession *session = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSString *identifier = @"io.objc.backgroundTransferExample";
        NSURLSessionConfiguration* sessionConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:identifier];
        session = [NSURLSession sessionWithConfiguration:sessionConfig
        delegate:weak_self
        delegateQueue:[NSOperationQueue mainQueue]];
    });

    return session;
}

- (void)didEnterBackgroundNotification:(NSNotification *)notification {
    NSString *downloadURLString = @"https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3588772980,2454248748&fm=27&gp=0.jpg";
    NSURL* downloadURL = [NSURL URLWithString:downloadURLString];

    NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL];
    NSURLSessionDownloadTask *task = [[self backgroundURLSession] downloadTaskWithRequest:request];
    task.taskDescription = [NSString stringWithFormat:@"Podcast Episode %d", 123];
    [task resume];
}

AppDelegate.m中实现以下代理方法

- (void) application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(nonnull void (^)(void))completionHandler {
    // 不要调用completionHandler!
}

此方案的优点:
1、不受系统权限控制;
2、可以运行很久,除非app被杀死。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容

  • iOS 常用小技巧大杂烩(上) 2016-06-02 iOS大全 (点击上方公众号,可快速关注) 来源:品味_生活...
    鬣狗赛跑阅读 290评论 0 1
  • 记得大学刚开始填报社团申请的时候,很多人都填了舞蹈,唱歌,绘画。可是我一脸茫然,我不知道我自己的优势在哪里,我没有...
    阿宝的育儿宝典阅读 97评论 0 0
  • 夜从未有过痴情的时刻 为你,点亮满天繁星的 是一池春水的柔波 是波中明月的笑 是桨的唇轻吻,一支白玉的笛 为你,铺...
    我本白丁阅读 83评论 0 0
  • 有些善人、有些善事、有些善知识,遇见是一辈子的暖,无关仰望、无关欣赏,是心与心的碰撞。累了,诉诉苦;烦了,缓...
    姐书的简书阅读 162评论 0 1