iOS常用技巧

2017-12-26  本文已影响10人  LeverTsui

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

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劫持
HTTP内容劫持

12、Responder chains in an app

image.png

13、优化tableview滑动性能

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被杀死。

上一篇 下一篇

猜你喜欢

热点阅读