美文网首页知识积累
当我们谈论iOS时,我们都在讨论什么?

当我们谈论iOS时,我们都在讨论什么?

作者: T_guo | 来源:发表于2019-10-25 11:18 被阅读0次

    1. 什么是arc?(arc是为了解决什么问题诞生的?)

    • 什么是arc:
      arc就是自动引用技术,作用是编译器代替程序员为对象添加retain/release。(我觉得了解到这种程度就差不多了!)
    • arc是为了解决什么问题诞生的?
      对象的生命周期包括诞生和死亡。alloc一个对象obj,obj的retain+1,结束使用后调用[obj release],这样对象的retainCount为0,不会造成内存泄漏。MRC——>ARC的过程就是手动添加改为了自动添加的过程。
    • 那么为什么弃用MRC改用ARC呢?是为了解决retain和release不匹配错误,而导致不匹配的原因有:
    1. 创建一个对象,要确保该对象使用结束后销毁,避免造成内存泄露。(用完未销毁)
    2. 解决重复释放.(谁创建谁释放,谁释放,重复释放会导致奔溃)

    2.请解释以下keywords的区别: assign vs weak, __block vs __weak

    • assign和weak都是用于修饰弱引用,而他们的区别是:
      assign:简单赋值,修饰基本数据类型(NSInteger)、c数据类型(int)和delegate(避免循环引用)。
    • 那么assign为什么不用来修饰对象呢?
      因为assign会导致下面这个问题:
      指针a指向一块内存,这时把a赋给了b,如果使用assign属性,a和b就指向同一块内存。如果这时候把a释放了,那么b在使用使用这块内存的时候就会crash.

    weak:弱引用,修饰对象类型。

    • 为什么基本类型和C数据类型的修饰用assign?
      因为基本数据类型不是对象,在内存中创建和使用后,在一个方法体结束后就被删除。基本数据类型不需要引用计数,而其他修饰词需要引用计数,所以使用assign。

    2.__block和__weak的区别:

    • __block:引用修饰,所以被__block修饰的变量的值是动态变化的,也就是可以被重新赋值。
      注意:如果在block内部要修改外部变量而不用__block修饰,会直接报错.
    • __weak:主要作用是避免循环引用, __weak typeof (self)weakSelf = self;

    链接:iOS: ARC和非ARC下使用Block属性的问题

    3. __block在arc和非arc下含义一样吗?

    MRC下的__block相当于ARC下的__weak,而ARC下的__block是为了修饰一个变量能在block中被修改。

    • ARC循环引用解决:
      __weak:弱引用(首选方案)不会产生强引用,指向的对象销毁时,会自动让指针置为nil
      __unsafe_unretain:弱引用,不安全 ,指向的对象销毁时,指针存储的地址值不变
      __block:缺点就是必须执行block,清空指针
    • MRC循环引用解决:
      不支持__weak的
      __unsafe_unretain
      __block

    4. 使用atomic一定是线程安全的吗?

    • atomic的作用是为属性的setter/getter方法加锁,避免多线程情况下访问setter/getter产生数据错乱。所以在访问setter/getter方法时是线程安全的,而对于进行其他操作的多线程则和atomic没有直接关系
    • 给属性加上atomic修饰,可以保证属性的setter和getter都是原子性操作,也就是说保证setter和getter内部都是线程同步
      它并不能保证使用属性的过程是线程安全的
      atomic在ios里基本上不适用,太消耗内存,一般在Mac上用

    5. 描述一个你遇到过的retain cycle例子

    • 在-(void)dealloc里invalidate一个NSTimer对象。
    这么做会retain cycle的原因:
    + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats
    

    这个方法在官方文档里的说明:The timer maintains a strong reference to this object until it (the timer) is invalidated.解释:timer对象会对object拥有一个强引用,如果object是self,那么self就被timer retain,如果timer一直不invalidated,那么dealloc就一直不会被调用。

    解决方法:在viewWillDisappear的时候invalidateNSTimer对象。

    - (void)viewWillDisappear:(BOOL)animated{
        [super viewWillDisappear:animated];
        [timer invalidate];
    }
    
    • block中的retain cycle:
      block会对用到的对象做一个copy操作,retainCount+1,所以block对用到的对象就有了一个强引用。那么用到的对象是否对block有一个强引用是要看具体条件的。
    下面就产生了循环引用
     [self doSomething:^(BOOL param) {
            self.tage = param;
        }];
        self.doSomethingWithBlock(YES);
    

    6.+(void)load; +(void)initialize;有什么用处?

    • app启动后会自动调用每个类+ (void)load方法。
      先调用类的load方法,如果有继承了父类的,先调用load,然后在调用分类的load方法。
    • 当类第一次接受到消息时候调用initialze。
      某个类继承了父类的时候,先调用父类的initialze,在调用自己的initialze,注意,不需要调用super。如果之前已经调用过initialze就不会再initialze进行初始化。有分类的时候,有继承父类的,先调用父类的initialze,继承了父类的子类调用分类的initialze,因为在调用过程中分类的方法移到了子类的前面。

    7. 为什么其他语言里叫函数调用, objective c里则是给对象发消息(或者谈下对runtime的理解)

    当我们给对象发送消息时[tableView cellForRowAtIndexPath:indexPath],编译器会把这段Object-c代码转换为c的函数调用objc_msgSend(tableVIew,@selector(cellForRowAtIndexPath:),indexPath),其中indexPath是参数。

    8. 什么是method swizzling?

    黑魔法——偷梁换柱。每个类都有一个方法列表,存放着selector的名字和对应方法实现的映射关系。而IMP就是指向方法实现(类似指针)。method swizzling可以做到运行时更换selector指向对应方法实现的IMP。比如selectorA指向A实现,selectorB指向B实现,更换两个方法的IMP后,selectorA调用的就是B实现了。

    9. UIView和CALayer是啥关系?

    • 1.在IOS上创建一个UIView就默认创建一个CALaye,可以通过如下方法获得CALayer *layer = self.view.layer;
    • 2.Layer提供内容绘制和动画,但它本身不能创建可视界面,需要添加到UIView.
    • 3.Layer不能响应事件。
    • CALayer和UIView的区别及联系:
      UIView是iOS系统中界面元素的基础,所有的元素界面都集成自它,UIView本身完全是由CoreAnimation来实现,真正的绘图部分是由一个CALayer类来管理,UIView更像是一个CALayer的管理类,所以访问它的与绘图和坐标相关的属性,如frame,bounds等,实际上都是在访问其所包含的CALayer的相关属性,因此,,可以在所有UIview的子类上实现动画效果。
      UIview是继承自UIResponder,能接受并相应事件,负责显示内容的管理,而CALayer继承自NSObject,不能响应事件,负责显示内容的绘制。
    • UIView可以响应事件,Layer不可以
      UIKit使用UIResponder作为响应对象,来响应系统传递过来的事件并进行处理。UIApplication、UIViewController、UIView、和所有从UIView派生出来的UIKit类(包括UIWindow)都直接或间接地继承自UIResponder类。
      在 UIResponder中定义了处理各种事件和事件传递的接口, 而 CALayer直接继承 NSObject,并没有相应的处理事件的接口。
    • UIView主要是对显示内容的管理而 CALayer 主要侧重显示内容的绘制
      UIView主要是对显示内容的管理, 而CALayer主要是显示内容的绘制. UIView是CALayer的CALayerDelegate, 在代理方法内部[UIView(CALayerDelegate) drawLayer:inContext]调用UIView的drawRect方法, 从而绘制出UIView的内容. UIView的显示内容由内部的CALayer:display方法来实现.编程问题都可以抽离出机制和策略部分。机制一旦实现,就会很少更改,但策略会经常得到优化。CALayer也可以看做是一种机制,提供图层绘制,CALayer的头文件基本上是没怎么变过的,而UIView可以看做是策略,变动很多。越是底层越是机制,越是机制就越是稳定。机制与策略分离,可以使得需要修改的代码更少,特别是底层代码,这样可以提高系统的稳定性。UIView遮蔽了大部分的CALayer接口,抽取构造出更易用的frame和动画实现,这样上手更容易。
    • frame, position, bounds调用
      一个CALayer的frame是由其anchorPoint, position, bounds, transform共同决定的, 而一个UIView的的frame只是简单地返回CALayer的frame, 同样UIView的center和bounds也只是简单返回CALayer的Position和Bounds对应属性
    • 在做 iOS 动画的时候,修改非 RootLayer的属性(譬如位置、背景色等)会默认产生隐式动画,而修改UIView则不会
      对于每一个 UIView 都有一个 layer,把这个 layer 且称作RootLayer,而不是 View 的根 Layer的叫做 非 RootLayer。我们对UIView的属性修改时时不会产生默认动画,而对单独 layer属性直接修改会,这个默认动画的时间缺省值是0.25s.
      在 Core Animation 编程指南的 “How to Animate Layer-Backed Views” 中,对为什么会这样做出了一个解释:(UIView 默认情况下禁止了 layer 动画,但是在 animation block 中又重新启用了它们)是因为任何可动画的 layer 属性改变时,layer 都会寻找并运行合适的 'action' 来实行这个改变。在 Core Animation 的专业术语中就把这样的动画统称为动作 (action,或者 CAAction)。
    • 为什么CALayer不能直接使用UIColor,UIImage
      首先,CALayer是定义在QuartzCore框架中的,CGImageRef、CGColorRef两种数据类型是定义在CoreGraphics框架中的
      ,而UIColor和UIImage是定义在UIKit框架中的。
      其次,QuartzCore框架和CoreGraphics框架是可以跨平台使用的,在iOS和Mac OS X上都能使用但是UIKit只能在iOS中使用。
      所以,为了保证可移植性,QuartzCore不能使用UIImage、UIColor,只能使用CGImageRef、CGColorRef。

    10. 如何高性能的给UIImageView加个圆角?(不准说layer.cornerRadius!)

    使用CoreGraphics将图片添加到graphics context中,绘制圆角路径然后对图片内容进行裁剪:

    - (UIImage *)roundCornersOfImage:(UIImage *)image {
        CGRect rect = CGRectMake(0, 0, image.size.width, image.size.height);
        
        UIGraphicsBeginImageContextWithOptions(image.size, false, 0);
        CGContextRef ctx = UIGraphicsGetCurrentContext();
        CGPathRef path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:20].CGPath;
        CGContextAddPath(ctx, path);//将创建的路径添加到当前上下文路径中
        CGContextClip(ctx);//裁剪路径
        [image drawInRect:rect];
        UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        return finalImage;
    }
    

    注意:比使用layer.cornerRadius性能高的原因在于对大量图片处理时(比如tableViewCell中的图片)。layer.cornerRadius需要的离屏渲染会占用额外的资源导致掉帧。而使用CoreGraphics不会引起这个问题。(CoreGraphics是耗时操作,最好异步调用)。ps. IOS9后直接cornerRadius设置图片的圆角不会引起离屏渲染问题,但是如果设置了背景等一些属性后再使用cornerRadius,还是会有屏渲染问题的。

    11. 使用drawRect有什么影响?

    • 可以在这个方法里完成绘制视图的操作。这个方法执行时表明系统已经准备好绘制环境,用户可以进行绘制操作了。除了绘制,其他更改视图的行为最好不要在这个方法里完成,比如修改视图属性的行为,如背景色或者对view.layer的设置。
    • 视图在生命周期内触发这个方法的情况有多种,比如添加进视图层级或者重绘等。因此如果一个调用drawRect的视图在一个页面大量使用,可能会有性能问题。(我猜的,如果只是做了视图重绘,我认为一般是不会出问题的)

    13. 麻烦你设计个简单的图片内存缓存器(移除策略是一定要说的)(SDWebImage源码学习)

    同时使用内存缓存和硬盘缓存实现,其中内存缓存使用NSCache类。

    1. 存储:先将image对象直接存到cache,然后将image转为data写入到硬盘
    2. 读取:每次读取图片缓存,先去cache中查找。如果cache中不存在,则去disk中查找图片,然后把结果返回,同时保存到cache中。
    3. 图片移除:
    • 通知注册监听app进程移除和app进入后台两个事件。如果两个事件发生时,1.自动删除存放超过1周的图片;2.如果当前图片容量合计超过总容量,则按时间顺序删除旧的文件,直到容量达标。
    • 通知注册监听内存警告事件,当时间发生时,自动清空cache。

    14. 讲讲你用Instrument优化动画性能的经历吧(别问我什么是Instrument)

    开发中需要优化最多的就是tableView的滑动动画了。core Animation工具可以查看页面的fps。当滑动视图时,fps明显低于60则可能出现性能问题需要优化了。

    Debug -> View Debugging -> Rendering之前是在core Animation里,现在单独拿出来了。
    

    现在开发中会严重影响动画FPS的可能原因:

    1. 离屏渲染问题
      图片圆角、阴影等操作会引起离屏渲染,会产生严重掉帧问题。Color offscreen-Rendering Yellow可以查看是哪些视图产生了离屏渲染。
    2. 主线程阻塞
      比如使用coreGraphics绘图这种耗时操作,如果放在主线程里去实现就很可能引起tableView滑动卡顿。
    3. 优化:
    • cell中减少阴影圆角的使用,必须的话可以使用光栅化缓存,或者coreGraphics绘制
    • 耗时操作异步完成
    • 尽量别空含有透明度的控件
    • 图片尽量使用36bit格式,不要含有alpha通道,大小和视图frame匹配

    15.loadView是干嘛用的?

    • 当跳转到一个vc时要用到self.view,而self.view==nil时,系统会调用loadView创建一个view赋值给self.view。
    • 我们可以重写loadView方法给self.view设置自定义view。这和 我们在viewDidLoad方法里创建视图添加到self.view的区别就是,通过loadView可以直接修改vc的rootView,即self.view的类型,前者的视图层级有两层,后者是一层。
    • loadView中初始化的view不需要设置frame,系统默认它等于[UIScreen mainScreen].bounds。

    16. viewWillLayoutSubView你总是知道的。。

    • 和UIView的layoutSubviews类似。用于布局添加的子视图,只是平时我们都在viewDidLoad完成了。
    • 当vc.view的bounds变化时,系统会执行这个方法。比如你在vc旋转时,你可以在这个方法里重新给子视图布局,让他满足横向的界面。layoutSubviews和viewWillLayoutSubView都适合用于重新布局子视图的需求,不要在里面进行初始化操作,毕竟在一个周期里可能会执行多次,引起歧义.

    17. GCD里面有哪几种Queue?你自己建立过串行queue吗?背后的线程模型是什么样的?

    1. 我觉得算4种,主队列、全局队列、串行队列、并行队列。
    2. 背后的线程模型是什么意思?我是在不懂,按我自己的理解说:
    • 任何队列使用dispatch_sync方式执行,不会创建新的线程,都是在当前线程里执行的。
    • 使用dispatch_async执行主队列中任务,那么不会创建线程,在主线程中执行
    • 使用dispatch_async执行全局队列或者并行队列,那么根据任务数开辟线程数
    • 使用dispatch_async执行串行队列,会开辟一个线程,串行执行

    18.http的post和get啥区别?(区别挺多的,麻烦多说点)

    • get:
      获取服务器上的数据
      是通过URL传递数据,效率高
      请求的数据在URL上 不安全
      get请求的结果能够被浏览器缓存
    • post:
      一般是往服务器提交数据,并获取服务器返回的结果
      通过请求体传输数据 ,效率低
      请求的数据用户看不到,在请求提上 相对安全
      请求不能被浏览器缓存

    19.UDP和TCP有什么区别:

    • 连接方面区别
      TCP面向连接(如打电话要先拨号建立连接)。
      UDP是无连接的,即发送数据之前不需要建立连接。
    • 安全方面的区别
      TCP提供可靠的服务,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达。
      UDP尽最大努力交付,即不保证可靠交付。
    • 传输效率的区别
      TCP传输效率相对较低。
      UDP传输效率高,适用于对高速传输和实时性有较高的通信或广播通信。
    • 连接对象数量的区别
      TCP连接只能是点到点、一对一的
      UDP支持一对一,一对多,多对一和多对多的交互通信。

    20.runtime如何实现weak变量的自动置nil?

    runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。

    21.BAD_ACCESS在什么情况下出现?

    访问了野指针,比如对一个已经释放的对象执行了release、访问已经释放对象的成员变量或者发消息。 死循环

    22.如何用GCD同步若干个异步调用?(如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图)

    使用Dispatch Group追加block到Global Group Queue,这些block如果全部执行完毕,就会执行Main Dispatch Queue中的结束处理的block。

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, queue, ^{ /*加载图片1 */ });
    dispatch_group_async(group, queue, ^{ /*加载图片2 */ });
    dispatch_group_async(group, queue, ^{ /*加载图片3 */ }); 
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            // 合并图片
    });
    

    23.dispatch_barrier_async的作用是什么?

    • 在并行队列中,为了保持某些任务的顺序,需要等待一些任务完成后才能继续进行,使用 barrier 来等待之前任务完成,避免数据竞争等问题。 dispatch_barrier_async 函数会等待追加到Concurrent Dispatch Queue并行队列中的操作全部执行完之后,然后再执行 dispatch_barrier_async 函数追加的处理,等 dispatch_barrier_async 追加的处理执行结束之后,Concurrent Dispatch Queue才恢复之前的动作继续执行。
    • 打个比方:比如你们公司周末跟团旅游,高速休息站上,司机说:大家都去上厕所,速战速决,上完厕所就上高速。超大的公共厕所,大家同时去,程序猿很快就结束了,但程序媛就可能会慢一些,即使你第一个回来,司机也不会出发,司机要等待所有人都回来后,才能出发。 dispatch_barrier_async 函数追加的内容就如同 “上完厕所就上高速”这个动作。

    24.苹果为什么要废弃dispatch_get_current_queue?

    dispatch_get_current_queue容易造成死锁
    

    24.NSTimer有什么需注意的以及和RunLoop的关系?

    • 不管是重复性的timer还是一次性的timer都会对它的方法的接收者进行retain,这两种timer的区别在于“一次性的timer在完成调用以后会自动将自己invalidate,而重复的timer则将永生,直到你显式的invalidate它为止”。
    • timer不是一种实时的机制,会存在延迟,而且延迟的程度跟当前线程的执行情况有关。
    • 必须得把timer添加到runloop中,它才会生效。
    • 要让timer生效,必须保证该线程的runloop已启动,而且其运行的runloopmode也要匹配。

    相关文章

      网友评论

        本文标题:当我们谈论iOS时,我们都在讨论什么?

        本文链接:https://www.haomeiwen.com/subject/izzjvctx.html