iOS 常见问题集锦

在开发过程中,遇到的问题和解决方案,当做备忘,以后会持续更新。

1. Attempted to dereference garbage pointer 0x20

访问了垃圾指针(野指针,对象的内存已经释放,但是指针还没置nil)。

调试技巧:打开“NSZombies”。这样做,就是每次你的应用程序试图释放一个对象时,Objective-C会用一个NSZombie对象替换它,当你当程序因“Attempted to dereference garbage pointer”崩溃的时候,它会提供一个更有用的错误信息。


2. iOS9以下使用苹方字体崩溃

iOS 8及以下是不支持系统苹方字体的,如果强行使用不但没有效果,而且还会引起崩溃。解决这个问题可以手动引入苹方字体文件,引用教程://www.greatytc.com/p/32ae87d4fe16


3. iOS处理图片的一些小Tip

参考博客:https://blog.ibireme.com/author/ibireme/

保存Gif到相册:iOS的相册是支持保存GIF和APNG动图的,只是不能直接播放。用[ALAssetsLibrary writeImageDataToSavedPhotosAlbum:metadata:completionBlock]可以直接把APNG/GIF的数据写入相册。如果想省事直接用UIImageWriteToSavedPhotosAlbum() 写相册,那么图像会被强制转成PNG。

把UIImage保存到磁盘的三种方式:

1.直接用NSKeyedArchiver把UIImage序列化保存。

2.用UIImagePNGRepresentation()把图片转为PNG保存。

3.用UIImageJPEGRepresentation()把图片压缩成JPEG保存。

比较:NSKeyedArchiver是调用了UIImagePNGRepresentation进行序列化的,用它来保存图片是消耗最大的。苹果对JPEG有硬编码和硬解码,保存成JPEG会大大缩减编解码的时间,也能减小文件体积。所以如果不包含透明像素时,UIImageJPEGRepresentation(0.9)是最佳的图片保存方式,其次是UIImagePNGRepresentation()。

UIImage 缓存:通过 imageNamed 创建 UIImage 时,系统实际上只是在 Bundle 内查找到文件名,然后把这个文件名放到 UIImage 里返回,并没有进行实际的文件读取和解码。当 UIImage 第一次显示到屏幕上时,其内部的解码方法才会被调用,同时解码结果会保存到一个全局缓存去。据我观察,在图片解码后,App 第一次退到后台和收到内存警告时,该图片的缓存才会被清空,其他情况下缓存会一直存在。

我要是用 imageWithData 能不能避免缓存呢?

不能。通过数据创建 UIImage 时,UIImage 底层是调用 ImageIO 的 CGImageSourceCreateWithData() 方法。该方法有个参数叫 ShouldCache,在 64 位的设备上,这个参数是默认开启的。这个图片也是同样在第一次显示到屏幕时才会被解码,随后解码数据被缓存到 CGImage 内部。与 imageNamed 创建的图片不同,如果这个图片被释放掉,其内部的解码数据也会被立刻释放。

如何判断一个文件的图片类型?

通过读取文件或数据的头几个字节然后和对应图片格式标准进行比对。在这里有一个简单的函数,能很快速的判断图片格式


4. 适配iphoneX以及各种X

DEMO:https://github.com/shaozhe-chen/AdaptationTool

如果导航栏隐藏之后,可能很多同学适配iphoneX的都是这样的:

if(_IS_IPHONEX){//设置top}else{//设置top}

而且很多同学判断iPhone X的条件,应该很多都是通过尺寸判断的吧。这时候发布了iPhone XS Max,我们就不得不去对_IS_IPHONEX的判断条件做更改,然后发布,所以基于这种问题,我设计了一个思路:

iPhone X

其实思路也很简单,就是获取到导航栏的子视图_UIBarBackground类的view,获取topSafeArea = view.height-44,这样就能获得安全区域的高度了。


5. iOS性能优化

1.对象的创建会分配内存、调整属性(view的frame、bounds、transform等)、甚至还有读取文件等操作,比较消耗资源。

2.用轻量级的对象代替重量级的对象,比如如果不需要响应时间的话,可以使用CALayer代替UIView。

3.不涉及UI操作尽量放到后台线程去处理。

4.通过StoryBoard创建视图对象时,其资源消耗会比直接通过代码创建视图对象要大得多

5.如果对象可以复用,尽量加到复用池中。

6.视图的布局计算是最为常见的消耗CPU的地方。如果能在后台线程提前计算好视图布局、并且对视图布局进行缓存,那么这块基本就不会产生性能问题(性能最好的布局是直接使用frame布局,如果不想手动调整frame属性,可以使用第三方工具方法替代:top、bottom、left、right、width、height)

7.对于复杂的视图,使用autolayout会随着视图数量的增长,CPU的消耗也会呈指数增长。

8.文本计算宽高会占用很大一部分资源,并且不可避免(也可以使用[NSAttributedString boundingRectWithSize:options:context:] 来计算文本宽高)。有这方面优化需求的,可以使用CoreText绘制文本,自己计算宽高。

9.图片的解码,当使用UIImage或CGImageSource的那几个方法创建图片的时候,图片数据并不会立刻解码,只有当图片设置到UIImageView或者CALayer.contents中去,并且CALayer被提交到GPU前,CGImage中的数据才会得到解码,这一步是发生在主线程的,并且不可避免。如果想绕开这个机制,常见的做法是在后台线程先把图片绘制到CGBitMapContext中,然后从Bitmap直接创建图片。

10.图片的绘制通常是指那些以CG开头的方法,把图像绘制到画布中,如何从画布创建图片并显示这样的一个过程。最常见的地方就是[UIView drawRect:]。由于CoreGraphic方法通常都是线程安全的,所以图像的绘制可以很容易放到后台线程中进行。



6. XCode 9.4.1 Could not build module and redefinition of module - Can't Build


7. 几个内存泄漏的常见的情景

1.block循环引用.

2.delegate循环引用

3.定时器没有销毁

4.非OC对象内存处理,没有手动释放

5.循环体中多次创建对象,造成内存暴涨

详情请见我另一边文章//www.greatytc.com/p/d6821fd028f2


8.EXC_BAD_ACCESS(code=EXC_I386_GPFLT)

出现野指针,查看属性是否有使用assign修饰OC对象的。block是否在多线程中执行,但是没有使用weak-strong-dance的。

 __weak typeof (self) weakSelf = self;

            self.animations= ^{

                __strong typeof(self) strongSelf = weakSelf;

                weakSelf.str=@"ooo";

            };

9. 在@protocol 和 category 中如何使用 @property

在@protocol中使用@property只会声明Getter和Setter方法,并没有实现Getter和Setter方法。我们在@protocol中使用@property是希望遵守我协议的对象能实现该属性的Getter和Setter方法,如果没有实现代理,直接调用 _delegate.__delegate.appleName = @"abc";会直接crash,报:unrecognized selector sent to instance。正确使用如下:

声明代理


代理实现

在category中使用@property也只会声明Getter和Setter方法,并没有实现Getter和Setter方法。如果我们真的需要给 category 增加属性的实现,需要借助于运行时的两个函数:objc_setAssociatedObject、objc_getAssociatedObject,代码如下:


10. atomic不是真正意义的线程安全

atomic修饰的属性,只保证这个属性的getter、setter方法是线程安全的,并无法保证这个对象线程安全。


ARC下原子性

声明了一个nonatomic的array,重写getter、setter方法,使用@synchronize同步锁保证readWrite线程安全。但是却无法保证这个对象的线程安全,原因是:如果线程A在对array进行release操作,同时线程B在访问array就会导致crash。


11. setNeedsLayout和layoutIfNeeded的使用

继承于UIView的子类重写,进行布局更新,刷新视图。如果某个视图自身的bounds或者子视图的bounds发生改变,那么这个方法会在当前runloop一个周期结束的时候被调用。为什么不是立即调用呢?因为渲染毕竟比较消耗性能,特别是视图层级复杂的时候。这种机制下任何UI控件布局上的变动不会立即生效,而是每次间隔一个周期,所有UI控件在布局上的变动统一生效并且在视图上更新,苹果通过这种高性能的机制保障了视图渲染的流畅性。

· setNeedsLayout

标记为需要重新布局,异步调用layoutIfNeeded刷新布局,不立即刷新,在下一轮runloop结束前刷新,对于这一轮runloop之内的所有布局和UI上的更新只会刷新一次,layoutSubviews一定会被调用。

· layoutIfNeeded

如果有需要刷新的标记,立即调用layoutSubviews进行布局(如果没有标记,不会调用layoutSubviews)。

总结:如果需要马上更新视图,可以这么调用

[self setNeedsLayout];

[self layoutIfNeeded];


12. layoutSubviews和drawRect的调用时机

· layoutSubviews在以下情况下会被调用:

1、init初始化不会触发layoutSubviews(还没添加到视图,不会调用layoutSubviews)。

2、第一次addSubview会触发layoutSubviews和一次drawRect。

3、改变一个UIView的bounds会触发layoutSubviews(注意:是改变bounds而不是frame,因为改变x,y是不会调用layoutSubviews的),当然前提是bounds的值设置前后发生了变化。

4、滚动一个UIScrollView引发UIView的重新布局会触发layoutSubviews。

5、旋转Screen会触发父UIView上的layoutSubviews事件。

6、直接调用setNeedsLayout 或者 layoutIfNeeded。

· drawRect在以下情况下会被调用:

1、如果在UIView初始化时没有设置rect大小,将直接导致drawRect不被自动调用。drawRect 调用是在Controller->loadView, Controller->viewDidLoad 两方法之后调用的.所以不用担心在控制器中,这些View的drawRect就开始画了.这样可以在控制器中设置一些值给View(如果这些View draw的时候需要用到某些变量值).

2、通过设置contentMode属性值为UIViewContentModeRedraw。那么将在每次设置或更改bounds的时候自动调用drawRect:。

3、直接调用setNeedsDisplay,或者setNeedsDisplayInRect:触发drawRect:,但是有个前提条件是rect不能为0。


13. iPhone5机型以下(包含)获取时间戳异常问题

在一次获取时间戳时,发现iPhone5获取到的时间戳是9位的,导致时间戳异常。

// 64位架构,使用NSInteger 1508461200000 10月20日09点

// 32位架构,使用NSInteger 927679104 01月12日01点

经过研究才发现,原来iPhone5机型以下都是使用32位的CPU,而我使用NSInteger来修饰时间戳,导致溢出。

如图所示,我们可以看出,在64位的CPU下,NSInteger是long,所以获取13位的时间戳是没有问题的。而在32位CPU下,NSInteger是int,所以就导致了溢出。

问题解决:

使用时间戳时,我们有个专门用于时间戳的类:NSTimeInterval,使用它来代替NSInteger能保证万无一失。

14. 自修复unrecognized selector sent to instance造成的crash

具体内容看我的demo,里面有注释,应该很容易看懂。


15. 创建cocoapods仓库的一点经验

对于想创建自己的cocoapods仓库,但是对于这方面没有经验的,提供一些经验,具体看我的另一篇文章

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

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,089评论 1 32
  • 面向对象的三大特性:封装、继承、多态 OC内存管理 _strong 引用计数器来控制对象的生命周期。 _weak...
    运气不够技术凑阅读 1,085评论 0 10
  • 1. 使用UIImageJPEGRepresentation(image, compressionQuality)...
    loongod阅读 777评论 0 2
  • 1.#import 跟 #include有什么区别?@class的又有什么作用? 1)#import指令是Obje...
    ldxgxy阅读 2,094评论 0 1
  • 大年的脚步走得这么快!明天是小年,眨眼就是又一个大年了。 自从农村来到城市,我就没点过旺火,没响过大炮,最多也就放...
    微微的雨声阅读 262评论 0 1