iOS开发项目小结

文章首发于个人blog

欢迎指正补充,可联系lionsom_lin@qq.com

原文地址:iOS项目总结-CPR

拓展:iOS开发经验总结

目录

日期:2018.10

  • 零、页面卡顿处理
  • 一、Masonry 布局 1/5处
  • 二、masonry 不立刻生成frame
  • 三、UIButton中image和title的 UIEdgeInsets
  • 四、YYModel使用
  • 五、ipad横竖屏,固定竖屏
  • 六、启动页黑屏
  • 七、数组谓词排序、倒序
  • 八、tableview的cell点击 与 手势点击冲突
  • 九、cell 上的 定时器
  • 十、float 四舍五入
  • 十一、tableview 删除某一行cell使用 deleteRowsAtIndexPaths
  • 十二、Objective-C 同步锁
  • 十三、线程同步
  • 十四、NSURL地址有中文
  • 十五、全局隐藏状态栏
  • 十六、设备适配,区分iphoneX XR XS等
  • 十七、GCD定时器,暂停状态,直接关闭定时器,崩溃
  • 十八、Validate APP 报错
  • 十九、iOS中延迟执行和取消的几种方式
  • 二十、NSArray 的 copy 和 multecopy
  • 二十一、在Controller中监听APP进入后台与APP被杀死状态
  • 二十二、设备适配,区分iphoneX XR XS等
  • 二十三、dealloc中只释放引用和解除监听,不作其他操作
  • 二十四、@aotureleasepool降低内存峰值
  • 二十五、数组遍历。

更新日期:2018.11

更新日期:2018.12

更新日期:2019.01

  • 三十三、内存泄露检测和解决
  • 三十四、崩溃 -[UITableView _contentOffsetForScrollingToRowAtIndexPath:atScrollPosition:usingPresentationValues:]: row (16) beyond bounds (16) for section (0).

更新日期:2019.02

  • 三十五、iOS12上获取不到wifi名字的问题

零、页面卡顿处理

iOS 保持界面流畅的技巧
IOS开发CPU/GPU消耗原因分析和解决办法
iOS性能优化
Instruments性能优化-Core Animation

一、Masonry 布局 1/5处

参考文档

长度关系和位置关系,不能做比例运算。

举个例子
我现在希望子视图的横向中心线(centerY)在高度的1/5处,一般的想法是
make.centerY.mas_equalTo(self.height).multipliedBy(0.2);
运行出错。

正确的做法是
make.centerY.mas_equalTo(self.bottom).multipliedBy(0.2);
同理,我想子视图的垂直中心线(centerX)在宽的1/5处,应为
make.centerX.mas_equalTo(self.right).dividedBy(5);
最后,高度和宽度的比例那就更容易理解了.
make.height.mas_equalTo(self.height).dividedBy(5);(子视图高度是父视图高度的1/5)

二、masonry 不立刻生成frame

iOS开发笔记 | 由使用Masonry布局不能立即获取到frame想到的一些问题

三、UIButton中image和title的 UIEdgeInsets

iOS UIButton之UIEdgeInsets详解

四、YYModel使用

4.1、json -> NSDictionary
// NSObject+YYModel.m

+ (NSDictionary *)_yy_dictionaryWithJSON:(id)json {
    if (!json || json == (id)kCFNull) return nil;
    NSDictionary *dic = nil;
    NSData *jsonData = nil;
    if ([json isKindOfClass:[NSDictionary class]]) {
        dic = json;
    } else if ([json isKindOfClass:[NSString class]]) {
        jsonData = [(NSString *)json dataUsingEncoding : NSUTF8StringEncoding];
    } else if ([json isKindOfClass:[NSData class]]) {
        jsonData = json;
    }
    if (jsonData) {
        dic = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL];
        if (![dic isKindOfClass:[NSDictionary class]]) dic = nil;
    }
    return dic;
}

五、ipad横竖屏,固定竖屏

参考

1、要勾选requeires full screen,同时不选择 upside down 、landscape left 、landscape right

样例

2、然后在AppDelegate.m中修改

// APPdelegate.m

- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)nowWindow {
    return UIInterfaceOrientationMaskPortrait; 
} 

六、启动页黑屏

启动页加载的图片命名不能包含 launch、Launch Images Source与Launch Screen File 等(改了图片名还真解决了黑屏)

iOS8.0 以后,系统默认优先选用Launch Screen.storyboard作为启动图,Launch screen interface file base name 需要填写对应的storyboard.

如果实在没办法解决,可以修改图片名称,再试试,虽然不知道为什么,我就是这么干成功的。

七、数组谓词排序、倒序

https://blog.csdn.net/hbblzjy/article/details/73232275

7.1、数组谓词排序

// Key:关键字    ASC:正序
NSSortDescriptor *desc = [NSSortDescriptor sortDescriptorWithKey:@"correctrate" ascending:YES];
// 直接排序完成  
[self.dataSource sortUsingDescriptors:@[desc]];
7.2、数组倒序
// 倒序
NSArray *copyArr  = (NSArray *)[[ARR reverseObjectEnumerator] allObjects];

八、tableview的cell点击 与 手势点击冲突

iOS基础补完计划透过堆栈看事件响应机制

iOS点击事件和手势冲突

iOS触摸事件那点儿事

iOS 解决tableView和点击手势冲突问题

判断如果点击的是tableView的cell,关闭手势 不是点击cell手势开启

-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {

    if ([NSStringFromClass([touch.view class]) isEqualToString:@"UITableViewCellContentView"]) {   // uitableview
        return NO;
    }
    return YES;
}

九、cell 上的 定时器

YSTimeCountDown

十、float 四舍五入

NSString *new = [NSString stringWithFormat:@"%.1f",correctRate];  // 不保留小数,它会自动四舍五入

// 举例
NSString *new = [NSString stringWithFormat:@"%.1f",2.33345633444];
NSString *new1 = [NSString stringWithFormat:@"%.1f",2.34345633444];
NSString *new2 = [NSString stringWithFormat:@"%.1f",2.35345633444];
NSString *new3 = [NSString stringWithFormat:@"%.1f",2.36345633444]; 
// 输出
2.3
2.3
2.4
2.4

十一、tableview 删除某一行cell使用 deleteRowsAtIndexPaths

了解了上面几个函数,我们来看什么是操作刷新块:
当我们调用的上面的函数时,tableView会立刻调用代理方法进行刷新,如果其中我们所做的操作是删除某行,而然数据源数组我们可能并没有刷新,程序就会崩溃掉,原因是代理返回的信息和我们删除后不符。

iOS为我们提供了下面两个函数解决这个问题:
开始块标志 、结束快标志

- (void)beginUpdates;  
- (void)endUpdates;  

我们可以将我们要做的操作全部写在这个块中,那么,只有当程序执行到结束快标志后,才会调用代理刷新方法。代码示例如下:

    [tab beginUpdates];
    [tab deleteRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:1 inSection:0]] withRowAnimation:UITableViewRowAnimationLeft];
    [dataArray removeObjectAtIndex:1];
    [tab endUpdates];
//注意:不要在这个块中调用reloadData这个方法,它会使动画失效。

十二、Objective-C 同步锁

Effective Objective-C Notes:GCD 实现同步锁

iOS中保证线程安全的几种方式与性能对比

十三、线程同步

iOS多线程:『GCD』详尽总结

/* 创建并发队列 */
dispatch_queue_t concurrentQueue = dispatch_queue_create("test.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);

/* 添加两个并发操作A和B,即A和B会并发执行 */
dispatch_async(concurrentQueue, ^(){
    [NSThread sleepForTimeInterval:1];              // 模拟耗时操作
    NSLog(@"OperationA");
});
dispatch_async(concurrentQueue, ^(){
   [NSThread sleepForTimeInterval:1];              // 模拟耗时操作
   NSLog(@"OperationB");
});
/* 添加barrier障碍操作,会等待前面的并发操作结束,并暂时阻塞后面的并发操作直到其完成 */
dispatch_barrier_async(concurrentQueue, ^(){
   [NSThread sleepForTimeInterval:1];              // 模拟耗时操作
    NSLog(@"OperationBarrier!");
    });
/* 继续添加并发操作C和D,要等待barrier障碍操作结束才能开始 */
dispatch_async(concurrentQueue, ^(){
    [NSThread sleepForTimeInterval:1];              // 模拟耗时操作
    NSLog(@"OperationC");
});
dispatch_async(concurrentQueue, ^(){
    [NSThread sleepForTimeInterval:1];              // 模拟耗时操作
    NSLog(@"OperationD");
});

十四、NSURL地址有中文

// 地址有中文,URL=nil
URL = [NSURL URLWithString:str];  

iOS中NSURL一路踩坑

十五、全局隐藏状态栏

第一步:target -> Hide status bar 勾选
第二步:Info.plist 设置 View controller-based status bar appearance,把键值设置为NO。

十六、设备适配,区分iphoneX XR XS等

史上最全的iOS各种设备信息获取总结(iPhone XS Max/XR 详细信息已更新)

// 判断是否为iPhoneX - 5.8 inch
#define iPhoneX ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1125, 2436), [[UIScreen mainScreen] currentMode].size) : NO)

// 判断是否为iPhoneXR - 5.8 inch
#define iPhoneXR ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(828, 1792), [[UIScreen mainScreen] currentMode].size) : NO)

// 判断是否为iPhoneXS - 6.1 inch
#define iPhoneXS ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1125, 2436), [[UIScreen mainScreen] currentMode].size) : NO)

// 判断是否为iPhoneXS MAX - 6.5 inch
#define iPhoneXSMax ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1242, 2688), [[UIScreen mainScreen] currentMode].size) : NO)

// 主要是用于区分是否是 刘海屏
#define LiuHaiPhone \
({BOOL isLiuHaiPhone = NO;\
if (@available(iOS 11.0, *)) {\
isLiuHaiPhone = [[UIApplication sharedApplication] delegate].window.safeAreaInsets.bottom > 0.0;\
}\
(isLiuHaiPhone);})

十七、GCD定时器,暂停状态,直接关闭定时器,崩溃

2018-07-26 GCD定时器的释放与程序崩溃的问题

如果timer暂停状态,需要先恢复,在进行Cancel

if(_timer){
    // 此刻被暂停了,先恢复,再销毁
    dispatch_resume(_timer);
    dispatch_source_cancel(_timer);
    _timer = nil;
}

十八、Validate APP 报错

验证包 报错

程序中包含 1024 * 1024 icon的问题;icon应该是直角,且alpha不设置

十九、iOS中延迟执行和取消的几种方式

iOS中延迟执行和取消的几种方式

  • 方法一、performSelector 方法
[self performSelector:@selector(delayMethod) withObject:nil afterDelay:2.0];

[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(delayMethod) object:nil];
[NSObject cancelPreviousPerformRequestsWithTarget:self];
  • 方法二、NSTimer 定时器
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(delayMethod) userInfo:nil repeats:NO];

[timer invalidate];
  • 方法三、NSThread线程的sleep
注:此方法是一种阻塞执行方式,建议放在子线程中执行,否则会卡住界面。但有时还是需要阻塞执行,如进入欢迎界面需要沉睡3秒才进入主界面时。

取消延迟执行?
sleep放入子线程,可以通过处理取消。

[NSThread sleepForTimeInterval:2.0];
[self delayMethod];
  • 方法四、GCD
__weak typeof(self)weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    [weakSelf delayMethod];
});

二十、NSArray 的 copy 和 multecopy

copy与mutablecopy使用对比

二十一、在Controller中监听APP进入后台与APP被杀死状态

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(comeHome:) name:@"UIApplicationDidEnterBackgroundNotification" object:nil];

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillTerminate:) name:UIApplicationWillTerminateNotification object:nil];


- (void)comeHome:(UIApplication *)application {
    NSLog(@"进入后台");
}

- (void)applicationWillTerminate:(UIApplication *)application {
    NSLog(@"程序被杀死");
}

二十二、设备适配,区分iphoneX XR XS等

史上最全的iOS各种设备信息获取总结(iPhone XS Max/XR 详细信息已更新)

iphone尺寸
// 判断是否为iPhoneX - 5.8 inch
#define iPhoneX ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1125, 2436), [[UIScreen mainScreen] currentMode].size) : NO)
// 判断是否为iPhoneXR - 5.8 inch
#define iPhoneXR ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(828, 1792), [[UIScreen mainScreen] currentMode].size) : NO)
// 判断是否为iPhoneXS - 6.1 inch
#define iPhoneXS ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1125, 2436), [[UIScreen mainScreen] currentMode].size) : NO)
// 判断是否为iPhoneXS MAX - 6.5 inch
#define iPhoneXSMax ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1242, 2688), [[UIScreen mainScreen] currentMode].size) : NO)
// 主要是用于区分是否是 刘海屏
#define LiuHaiPhone \
({BOOL isLiuHaiPhone = NO;\
if (@available(iOS 11.0, *)) {\
isLiuHaiPhone = [[UIApplication sharedApplication] delegate].window.safeAreaInsets.bottom > 0.0;\
}\
(isLiuHaiPhone);})

二十三、dealloc中只释放引用和解除监听,不作其他操作

二十四、@aotureleasepool降低内存峰值

NSArray * dataRecords = ....;
NSMutableArray * peopleArr = [NSMutableArray new];
for(NSDictionary *record in dataRecords) {
    @aotureleasepool {
        EOCPerson * person = [[EOCPerson alloc] initWithRecord:record];
        [peopleArr addobject:person];
    }
}

二十五、数组遍历。

iOS开发中数组常用的五种遍历方式

  • for循环
  • for in 快速枚举
  • NSEnumerator
  • 快速遍历 - enumerateObjectsUsingBlock
  • 快速迭代 - dispatch_apply
  • RAC遍历
    [array.rac_sequence.signal subscribeNext:^(id _Nullable x) {
        NSLog("%@", x);
    }];
    

/**************/

更新日期:2018.11

/**************/

二十六、MBProgressHUD显示图层探究

视图层次结构
第一层:UIWindow ,即 self.view.window;
第二层:UINavigationController 的 view ,即我们前面提到的 self.navigationController.view;
第三层:self.view;
第四层:导航栏 UINavigationBar ,即 self.navigationController.navigationBar。

总结:

当你需要让导航栏上的按钮不可点击的时候,可以选择使用 self.view.windowself.navigationController.view显示 MBProgressHUD 。
反之,可以选择 self.view

二十七、获取当前显示的ViewController探究

+(UIViewController *)getCurrentVC {
    UIViewController * result = nil;
    
    // 获取默认的window,如果有多个window情况下,获取最上层的window
    UIWindow * window = [[UIApplication sharedApplication] keyWindow];
    if (window.windowLevel != UIWindowLevelNormal) {
        NSArray * windows = [[UIApplication sharedApplication] windows];
        for (UIWindow * tmpWindow in windows) {
            if (tmpWindow.windowLevel == UIWindowLevelNormal) {
                window = tmpWindow;
                break;
            }
        }
    }
    
    // 获取window的rootViewController
    result = window.rootViewController;
    
    while (result.presentedViewController) {
        result = result.presentedViewController;
    }
    if ([result isKindOfClass:[UITabBarController class]]) {
        result = [(UITabBarController *)result selectedViewController];
    }
    if ([result isKindOfClass:[UINavigationController class]]) {
        result = [(UINavigationController *)result visibleViewController];
    }
    return result;
}

二十八、int64_t 与 int32_t 与 NSInteger

在32位系统中

int 占4个字节 
long 占4个字节 
NSInteger 是int的别名,占4个字节 
long long 占8个字节 
int32_t 是int的别名,占4个字节 
int64_t 是long long的别名,占8个字节


在64位系统中

int 占4个字节 
long 占8个字节 
NSInteger 是long的别名,占8个字节 
long long 占8个字节 
int32_t 是int的别名,占4个字节 
int64_t 是long long的别名,占8个字节

二十九、时间戳 NSInteger 32位机型范围越界

29.1、如何判断32位、64位?

iOS-基本数据类型、大小端、电脑位

  • 方案一:利用long 在不同操作系统中所占字节不同。
    long在32位操作系统中占4Byte
    long在64位操作系统中占8Byte
    size_t longSize = sizeof(long);
    NSLog(@"longSize = %zu", longSize);
    
  • 方案二:读取寄存器长度
    register read
    
29.2、NSInteger取值范围?
#if __LP64__ || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64
typedef long NSInteger;
typedef unsigned long NSUInteger;
#else
typedef int NSInteger;
typedef unsigned int NSUInteger;
#endif

32位下,NSInteger是int
取值范围:-2(32-1) ~~~ 2(32-1)-1
-2147483648 ~~~ 2147483647

64位下,NSInteger是long
取值范围:-2(64-1) ~~~ 2(64-1)-1
-9223372036854775808 ~~~ 9223372036854775807

三十、dealloc崩溃:-[ViewController .cxx_destruct]

原因:GCD定时器 dispatch_source_t 处于 暂停状态 释放异常,导致 .cxx_destruct 析构失败。
解决方案:对 dispatch_source_t 状态修改,再进行释放。

三十一、取消延时操作失效(看:十九)

cancelPreviousPerformRequestsWithTarget: isn't working for me

原因一:参数异常,一个object为 'self' ,一个为 'nil'

// 设置延时操作
[self performSelector:@selector(hideNavigationBar) withObject:self afterDelay:3.0];

// 取消延时操作
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(navigationBarHide) object:nil]; 

原因二:我犯的错误,取消操作处于其他线程中,这里就牵扯到了runloop处理performSelector操作,总之在主线程中进行 创建延时操作和取消延时操作 就好了。

三十二、发布证书问题:doesn't include signing certificate

报错Provisioning profile "XX" doesn't include signing certificate "xx developer"

三十三、内存泄露检测和解决

内存检测:使用Xcode工具Instruments 和 腾讯扩展库MLeaksFinder

项目中的内存泄露主要有:
第一个是Storyboard中无用的视图且放置了init箭头;
第二个是delegate用了strong修饰;

三十四、崩溃 -[UITableView _contentOffsetForScrollingToRowAtIndexPath:atScrollPosition:usingPresentationValues:]: row (16) beyond bounds (16) for section (0).

原因:
这个错误是传入的IndexPath已经越界了。需要在调用之前加入判断语句。

拓展:
[self.tableView scrollToRowAtIndexPath:]方法滑动崩溃
类似美团、饿了么点餐界面的分类与菜品联动
当分类中某一个分类下面菜品数量为零时,点击此分类,导致程序Crash

// 将row设置为NSNotFound就可以了
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:NSNotFound inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];

三十五、iOS12上获取不到wifi名字的问题

需要添加新的权限即可。
iOS 12 获取wifi名称失败的解决方法

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

推荐阅读更多精彩内容