iOS开发点记录

1,472 阅读15分钟

平时自己开发记录,会持续更新补充到这个文章中,相关的知识点不会展开讲,主要就是说下大概这个东西,权当平时自己的记录,方便工作查询

MRC相关

  • mrc下dealloc方法,释放属性内存的时候self.xxx = nil就可以了不用release,因为set方法会先release旧值,再赋新值

  • mrc写set方法的时候要注意先relese旧值,retain新值

  • MRC中创建ARC的类,要注意用autorelease,不然ARC类会释放不掉

  • alloc, init, copy, mutablecopy, new开头的方法是返回一个引用计数为1的对象,所以外部需要对其进行内存管理,其他则返回autorelease对象,当然这是apple的编程规范,平时我们自定义方法也要按照这个规范

  • 局部变量创建加autorelease,全局变量delloc置nil

  • 在ARC下打印引用计数

    NSObject *test = [NSObject new];
    NSLog(@"%p",test);
    NSLog(@"1 == %@", [test valueForKey:@"retainCount"]);
    
    _ref = test;
    NSLog(@"2 == %@", [test valueForKey:@"retainCount"]);
    
    self.delegate = test;
    NSLog(@"3 == %@", [test valueForKey:@"retainCount"]);
    
    NSLog(@"%p",self.delegate);

UITableView 相关

  • cellForRowAtIndexPath不执行的原因

1.dataSource和delegate没有设置

2.numberOfRowsInSection和numberOfRowsInSection返回的数据不是大于0的整数

3.tableView没有add到父视图

  • UITableView ReloadData抖动,闪动

1.取消高度预算

    self.tableView.estimatedRowHeight = 0;
    self.tableView.estimatedSectionFooterHeight = 0;
    self.tableView.estimatedSectionHeaderHeight = 0;

2.只reload一行或者一个cell

  [self reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationNone];
  
    [self reloadRowsAtIndexPaths:@[
                                   [NSIndexPath indexPathForRow:0 inSection:0]
                                   ] withRowAnimation:UITableViewRowAnimationNone]

3.没有动画reloadData

[UIView performWithoutAnimation:^{
        [self reloadData];
    }];

4.如果reloadData只是为了新加入数据,可以用这个

- (void)insertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;

5.自定义cell,用了SDWebImageView,在下载完图片的回调,这里回调出去再异步去布局

6.UItableView设置为Grounp时会自动偏移64,设置下tableHeaderView,高度不能为0

self.tableView.tableHeaderView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, CGFLOAT_MIN)];

7.设置滚动条出现的时间或者一直出现

8.设置为group后,尾部出现留白处理,footerView的高度设置为CGFLOAT_MIN,对应的view return nil

  1. 取消cell的选中效果

cell.selectionStyle = UITableViewCellSelectionStyleNone;

10.去掉分割线

self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone

杂乱的Tip

  • ARC下dealloc内不需要写[super dealloc]因为在运行时编辑器会自动加上
  • <#出现代码块#>
  • 刚更新完iphone系统,打开设置,是看不到开发者选项的,这个得连下xcode才会出现的
  • block里面如果代码比较长,应该封装起来,避免太长的代码,忘记用weakself,导致了循环引用
  • UICollectionView不走datasource代理
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath

原因是继承之`UICollectionViewFlowLayout`的类`init`方法中设置的`itemSize`将其弄为负数了

width 为负数
self.itemSize = CGSizeMake(width, width);
  • 导出xcode自定义代码块
Xcode中的代码块默认存储在~/Library/Developer/Xcode/UserData/CodeSnippets下,可以直接拷贝出来,放在新电脑的该目录下即可
  • 分类不能添加实例变量,之所以不可以是因为分类不是真正意义上的类,类的本质是一个结构体,里面有对应的指针来指向对应的属性列表,方法列表等,但是分类的本质也是一个结构体,但是这个结构没有这个指针指向一个属性列表,所以分类并没有这个结构来存实例变量

  • 解决异步回调

如果有个方法有异步的回调,那么我们可以用gcd组和信号量,还有递归来解决这个问题

2、方法中局部变量的alloc后,由于要将指针传递出去,所以无法在方法内和方法外release,所以用autorelease来解决这个问题。

3、NSMutableArray每当add或remove一个对象时,会自动retain和release一次。

4、stringWithFormat:默认返回autorelease类型,或者不用手动release的初始化都会默认返回autorelease。

5、NSString和NSArray的property声明一般用copy代替retain或strong,防止外界对其进行修改,而delegate基本都声明为weak,防止cycle retain。

6、关于自建tableView: 如果继承UITableViewController,则可以直接使用其自带的tableView,并设置大小和其他属性; 如果继承的是UIViewController,则需要再添加一个tableView,并设置其delegate和dataSource。 PS:记住不要使用xib,如果删掉xib文件,之前xib中的内容还在!

7、关于自定义tableViewCell: 将新建的cell的class设置为对应的cell class,而不是file,owner,然后将cell中的自定义view和controller连线,即可方便获取自定义的label或button。 使用自定义cell时要先register自定义的nib,然后直接dequeuereuse即可。如果没有xib, 则需要类文件里边定义label,然后和系统默认方案一样去创建cell。

8、要设置cell被选中的背景状态颜色,在setSelected:animated中设置cell.selectedBackgroundView。 要想改变cell的样式,重写layoutSubviews。

9、需要执行drawRect方法是应调用setNeedsDisplay

10、委托的功能之一是使得程序对任何情况都能做出正确的响应。

11、振动:加入框架AudioToolbox.framework 导入AudioToolbox.h 在任何需要振动的地方加代码: AudioServicesPlaySystemSound(kSystemSoundID_vibrate);

12、重写drawRect方法: 首先需要获得context UIGraphicsGetCurrentContext()。 绘画时,将代码填在UIGraphicPushContext(context)和 UIGraphicPopContext()之间,如果需要保存当前绘图状态的话。

13、如果某个property和画面关联,则可以重写其setters来redraw,即调用setNeedDisplay来触发drawRect;另外为了提高效率,要在setter中判断只有在值有变化时才调用, if(_foo!=foo){ }

14、segue create a new view controller,always

15、修改navigationBar的backBarItem必须在父controller的push代码前

16、viewDidLoad时controller.view还是最原始的尺寸,若要调整其尺寸,最好放在接下来的viewWillAppear中。viewWillAppear中还适合放耗时的任务,比如下载,但要另开一个线程。

17、除非在loadView里面,否则永远不要让self.view指向其他任何东西。只要用了storyboard或者xib,就不要用loadView方法;但重写loadView方法一定要设置self.view

18、尽量不要使用awakeFromNib,初始化最好都放viewDidLoad中。

19、alloc initWithFrame比较少用。 UIImage用alloc的话会改变UIImageView的frame为图片大小,设置imageView.image则不会。

20、webView中scalePagesToFit需要设置为YES。

21、一般用NSURL来代替NSString设置文件名

22、使用scrollView时一定不要忘了设置其contentSize。要想缩放,则去设置scrollView.minimumZoomScale和maximumZoomScale,并且实现其delegate中的(UIView *)viewForZoomingInScrollview并连接其delegate。

23、block中的任何对象都需要一个strong pointer。但这可能引起memory cycle,解决办法就是声明__weak。

24、任何UIKit相关的东西都必须放在主线程里做。


NSUserDefault存的数组如果包含自定义类型,那么需要让自定义类实现<NSCoding>



6.关于方法中的if returnif elseif else是用来区分鲜明的互斥的,if return是用来处理特殊的错误状态,而且if return好处是简洁提高可读性,如果怕if return坑了后面的代码,一般这种情况是方法太冗长才会,那么当方法太冗长的时候,应该尽量的去抽取方法的代码

7.关于递归,尽量不要用递归,迫不得已用递归也得加标志位,让他走一次,不走第二次,防止一直递归卡死的情况,当然while也是同理的

8.尽量不要在set方法里面写逻辑

9.自定义cell的时候,最好用在一个地方,不要看着两个cell差不多,就通过type去控制,这样后面这个类会很冗杂

10.复制代码过来一定要看清楚代码里面做什么操作,比如我有一次是要获取所有系统字体,所以了一个代码,但是里面做了数据库的插入,更新,那么是不适合我这个方法的getAllSystemFont,所以要把数据库的操作去掉
11.代理方法的写法,方法的前缀就是类名,第一个参数就是self,类似了tableview的代理方法的写法

***
iOS开发 - Xcode不走断点
第一种,debug模式下,Xcode不走断点

解决方法:edit scheme -> info,build configuration 修改为Debug,Debug executable前的对勾打上。

 

第二种,debug模式下,xcode不在断点代码处停止,会进入线程

解决方法:Debug->Debug Workflow->always show disassembly前的对勾关掉。

 

第三种,debug模式下,xcode不走pod第三方库的断点

解决方法:试一下 clean一下再重新build。或者重启xcode,或者将断点全部删掉,重新添加。
***
  • 父类声明了类方法A,子类是不能调用类方法A的
  • oc的容器类都是不可以继承的,因为容器类都是用了类簇的设计模式

遇到bug的解决Tip

  • 场景是点击UICollectionCell,present一个页面,点击关闭页面,再点这个cell,无响应,再点一次才有响应;造成这个问题的原因是设置了UICollectionView的allowsMultipleSelection属性为YES,可以多选,导致再次点击是触发了取消选中的代理方法

    __block KSOFontInfo *currertFont = nil;
    if (mCurrentFontIndex >= 0 && mCurrentFontIndex < oldFontList.count) {
        currertFont = [oldFontList objectAtIndex:mCurrentFontIndex];
    }
   
    editFontListViewController.willDismissBlock = ^{
        if (!currertFont) return;
        [weakSelf resetWordFontList];
        [mfontList enumerateObjectsUsingBlock:^(KSOFontInfo *obj, NSUInteger idx, BOOL * _Nonnull stop) {
            if ([obj isKindOfClass:[KSOFontInfo class]]) {
                // 这里的currertFont会野指针
                if ([obj.familyName isEqualToString:currertFont.familyName]) {
                    mCurrentFontIndex = idx;
                    *stop = YES;
                    isExist=YES;
                }
            }
        }];
    }
    
    // 造成野指针的原因,看下resetWordFontList方法的部分实现
    currertFont是指向mfontList数组中的某个元素,但是在resetWordFontList之后,将这个数组的元素清空了,这就导致了这个元素野指针了
    
    // 所以这个的Tip就是,当一个指针指针一个地方的时候,尤其是在之后的回调中使用,要考虑是否会被更改,这种情况还可以考虑,使用copy
    
    // 还有一个点,记得如果是MRC,在copy使用autorelease,这样是错误的,这样还是会被释放,应该在回调完成后调release
    
- (void)resetWordFontList
{
    if (mfontList == nil)
    {
        mfontList = [[NSMutableArray alloc] init];
    }
    else
    {
        [mfontList removeAllObjects];
    }
    
    ...
}

调试技巧

1

 我们也可以在LLDB中直接用watchpoint命令,可以通过选项实现更多效果。
 
 watchpoint set self->testVar     //为该变量地址设置watchpoint
 watchpoint set expression 0x00007fb27b4969e0 //为该内存地址设置watchpoint,内存地址可从前文提及的`p`命令获取
 watchpoint command add -o 'frame info' 1  //为watchpoint 1号加上子命令 `frame info`
 watchpoint list //列出所有watchpoint
 watchpoint delete // 删除所有watchpoint

2

第一种,debug模式下,Xcode不走断点

解决方法:edit scheme -> info,build configuration 修改为Debug,Debug executable前的对勾打上。

 

第二种,debug模式下,xcode不在断点代码处停止,会进入线程

解决方法:Debug->Debug Workflow->always show disassembly前的对勾关掉。

 

第三种,debug模式下,xcode不走pod第三方库的断点

解决方法:试一下 clean一下再重新build。或者重启xcode,或者将断点全部删掉,重新添加。

3

打印图层结构
po  [[UIWindow keyWindow] recursiveDescription]

打印某个视图的图层结构
po [[[UIWindow keyWindow] rootViewController] _printHierarchy] 

打印堆栈
bt

打印所有线程的堆栈
bt all

Xcode错误提示

Init methods must return a type related to the receiver type

init开头的方法中只能返回该对象,不能返回除该对象之外的对象类型。

Xcode编译相关

1.获取当前内核数:  
$ sysctl -n hw.ncpu  
2.设置编译线程数:  
$ defaults write com.apple.dt.Xcode IDEBuildOperationMaxNumberOfConcurrentCompileTasks 8  
3.获取编译线程数:  
$ defaults read com.apple.dt.Xcode IDEBuildOperationMaxNumberOfConcurrentCompileTasks  
4.显示编译时长:  
$ defaults write com.apple.dt.Xcode ShowBuildOperationDuration YES  

工作心得

1.在设计UI组件的时候,要考虑给外部提供接口用于修改这个组件的UI状态,而不应单方面的想着,点击我的UI触发事件流,需要考虑一种其他情况,从外部事件流入UI组件,改变UI状态,要考虑双向情况,不要把UI组件封的太死

2.多个按钮状态:

点击高亮一下;

点击选中,再点击取消选中;

点击选中,再点击不取消选中,点了其他才取消选中;

多选,单选

部分多选+部分单选

3.一个UI类对外暴露UI对象,其实有一点坑的,外部可能会对这个对象进行修改,在很久后维护很麻烦,不知道外部哪里做了修改,所以对外的属性最好的readonly,然后再提供block方法,出去给外部去设置,这样后面直接搜索block方法,就知道外部哪里设置了

4.关于ipad分屏的,在窄屏的时候,可能是用iphone的样式,导航栏是隐藏的,当切到宽屏的时候,重新布局,导航栏是显示的,但是如果在这个动画的过程中去做布局,如果用到导航栏是否隐藏做判断,那么这时候会造成错误,因为动画过程中的导航栏还是iphone的状态,得在分屏完成再做布局稳妥点

5.当将一个类的属性,赋值给另一个类的属性的时候,比如AVc的属性Name,赋值给AVc上的view的属性Name的时候,要考虑,这个Name是指针进行,view对其进行修改,如果影响了AVc对Name的使用,比如需要特殊注重的是赋值一个Mode过去,这种比较需求特别注意

6.头文件尽量不要暴露太多允许读写属性,比如A类中有个属性view,然后很多地方对这个A类的view.hidden或者其他属性进行设置,这时候有时候调试起来是比较麻烦的,要找debug代码,断点不好找,打断点地方也多,所以比较好的做法是暴露接口出去,然后属性尽量只读,这样以后调试起来,以及寻找对这个A类有什么操作的时候,只能在A类的.m中看,大部分可以看出外部对其有什么操作

7.不要太依赖一些警告宏,比如API_AVAILABLE(ios12),正常会出警告说应该12使用,但是掉过坑,没有出警告,导致了ios11线上崩溃,所以需要用到API_AVAILABLE的方法,最好还是加if return稳妥点

8.init做UI处理的代码,最好加安全主线程宏,预防外部使用,比如懒加载,这种情况就有可能子线程调到,然后就崩溃了

9.修改一个类的一个UI属性的时候,尤其是取消addSubView,要注意下外部是否拿这个view做约束,因为取消addSubView,外部还拿这个view去做约束,就会崩溃了

10.关于写UI的思考,之前有分享发送的模块,它的结构大概是这样的,VC chileVc,VC主要是业务逻辑,跳转,chileVc是内容视图,本来以为这样算视图和逻辑拆分得挺好了。有一次需求,需要将这个分享模式作为view add在某个视图上,那时候我是Vc.view add上去了,效果是看出来了;但是分享里面的点击视图,尤其是跳转的代码,如self dismiss...或者self present...都是基于这个Vc是present出来的。所以说到这里就有个思考点了,这个模块的缺点就是一切要基于它是present出来的,比如现在的add view,就会导致跳转的代码没法用了,self dismiss...或者self present...都无效,因为self没有被presenting。所以就有个思考点,平时在vc写类似这样的代码,可以将self dismiss...或者self present...写成self.xxx,这个xxx由外部决定,这样就扩展性强了一点

容易忘记代码

1.数组元素执行方法

[view.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];

2.PresentedViewController 与 PresentingViewController区别

假设从A控制器通过present的方式跳转到了B控制器,那么 A.presentedViewController 就是B控制器;B.presentingViewController 就是A控制器。

官方API使用注意点

1.[NSURL URLWithString:urlStr]如果urlStr包括中文字符或者特殊字符,那么调用这个方法会返回nil

开发行为

1.拔数据线的时候要断了xcode再拔

review代码主要review点

1.block是否有直接用self

2.while是否会造成无限循环

3.声明为强引用的属性是否外部会赋值,是否会循环引用

4.改动了方法名,.h有没有对应改动,调用地方有没有对应改动,尤其是删除方法的时候,一定要搜下有没有对应调用的,尤其是perforSelect的,KVO,通知监听的

5.多人开发入库的时候,要想着这个项目是可以编起来的

没怎么见的API

1.intrinsicContentSize,也就是控件的内置大小。比如UILabel,UIButton等控件,他们都有自己的内置大小。控件的内置大小往往是由控件本身的内容所决定的,比如一个UILabel的文字很长,那么该UILabel的内置大小自然会很长。控件的内置大小可以通过UIView的intrinsicContentSize属性来获取内置大小,也可以通过invalidateIntrinsicContentSize方法来在下次UI规划事件中重新计算intrinsicContentSize。如果直接创建一个原始的UIView对象,显然它的内置大小为0。

线程相关

1.如果一个方法的回调是在子线程的,那么想要同步他可以用信号量

如果回调是主线程,那么可以用CFRunLoopRun();然后主线程空跑等待,然后回调到主线程后,再调用CFRunLoopStop(CFRunLoopGetCurrent());停止空跑