XSLT存档  

不及格的程序员-八神

 查看分类:  ASP.NET XML/XSLT JavaScripT   我的MSN空间Blog

iOS 中级面试题

半年前在知乎浏览到一个帖子,是关于如何面试iOS工程师。由于当时公司正在招聘iOS工程师,自己也面试了不少同学,就饶有兴致的把自己的面试问题清单贴了出去,却意外的引来了不少关注。还有不少同学认真回答并将答案私信于我交流的,还有求隐藏关卡的:(。由于工作太忙,私信和评论后面都没怎么回复,这里一并做下总结回复:p

一份面试题的意义

我把收到的私信和一百多条评论都仔细读了遍,发现大家之所以会关注大致出于以下几个原因:

  • 刚毕业的同学最近在找工作面试,想刷题增加面试通过率。
  • 有一定的工作经历的同学,想测试下自己的iOS水平,看自己能卖多少钱。
  • 本身iOS基础不错,抱着技多不压身心态补充知识的。
  • 土豪老板就差一个程序员了,想找份带标准答案的面试题找真爱。

除了第三类同学心态正确外,其他的都高估面试题的作用了。面试题只是武功招式,知识体系才是内功心法。刚入门记住的都是招式,但招式何其多,面试的时候总会有遗漏和盲区,内功心法才是一通万通,能以不变应万变。这份面试题你答不全不能说明你iOS不及格,你全答对了你也不能上天。真正应该关注的是这份题背后所包含的理论知识体系。帖子里还有其他不少优质回答,涵盖候选人心态,习惯,基础知识,产品理解等各个方面,都值得一读。当然啦,既然出了题,就得有答案,就有它的目标群体。主要考察对象是从事iOS开发 1~3年的同学。不需要全部答对,能对一半以上问题侃侃而谈就不错了。



评论区百态

评论区有各路神仙吐槽,有的说难,有的说太容易,还有美工和安卓党出现。大家七嘴八舌的讨论意见很杂,但从中可以看出不少同学心态都不正确。技术这条路无穷无尽,广度和深度的拓展都需要长年累月的积累,不存在什么够用就好了,用的时候再查下,没必要了解这么深。技术人员的视野和耐力决定在这条路上你能够走多远。下面几类同学点名批评:

  • 故作无知都机灵的。
  • 觉得sqlite太重没必要用的。
  • 说太容易不愿意答题的。
  • 说都不会但不影响做项目的。
  • 说圆角头像让美工切个图就搞定的。
  • 说一半不会没必要深究的。

心态不及格。



合格的答案

出乎我意料之外的是有好几位同学都正二八经的答了题,还把答案私信了我。这里贴出其中一份答得还不错的,再后面是答主自己的答案。 过关回答



我的答案

我有过不少面试和被面试的经历,作为面试官出这份面试题从来就不是为了难倒面试者,而是为了多角度全面的了解面试者从而建立信任。面试的时候最担心的是冷场,面试题只不过个引子,我心底里最希望遇到的面试者是能够举一反三,除了回答问题本身之外,还能自信的旁征博引,深谈其背后原理或者相关的知识理论的。问题本身反而并不怎么重要。这份清单里的问题也并不难,这里我列下我的回答以及从我的角度所期望的答案。

什么是arc?(arc是为了解决什么问题诞生的?)
现在有不少程序员是直接从arc上手的,从没接触过mrc,对arc的理解仅仅停留在apple帮助管理内存的层面。这个问题真正想了解的是对内存管理的理解,retain release虽然不用写了,但arc下还是会有内存泄漏野指针crash的bug存在。如果能从retain count这种内存管理策略的角度去阐述arc诞生的意义就算答对了。如果还能扯下其他类型的策略,比如java里的mark and sweep,那就加分点赞。

请解释以下keywords的区别: assign vs weak, __block vs __weak
这道题属于基础语法题,可以网上搜到答案。不过真有不少同学不知道weak在对象释放后会置为nil。__block关键字的理解稍微难点,因为在arc和mrc下含义(对retain count的影响)完全不同。理解了这几个关键字就能应付使用block时引入retain cycle的风险了。这题还在内存管理的范畴之内。

使用atomic一定是线程安全的吗?
看这题的问法不用想答案肯定是NO。有些人说不出所以然,有些人知道通过property的方式使用才能保证安全,还有人知道这个用来做多线程安全会有性能损耗,更有出色的候选人能谈atomic,synchronized,NSLock,pthread mutex,OSSpinLock的差别。好奇宝宝点我

描述一个你遇到过的retain cycle例子。(别撒谎,你肯定遇到过)
说没遇到过的我很难相信你有过成熟项目的经历。这题答不出了会扣很多很多分。用过block,写过delegate的肯定都踩过坑。

+(void)load; +(void)initialize;有什么用处?
这题属于runtime范畴,我遇到过能说出对runtime的理解却不知道这两个方法的候选人。所以答不出来也没关系,这属于细节知识点,是加分项,能答出两个message各在什么阶段接收就可以了。

为什么其他语言里叫函数调用, objective c里则是给对象发消息(或者谈下对runtime的理解)
这题考查的是objective c这门语言的dynamic特性,需要对比c++这类传统静态方法调用才能理解。最好能说出一个对象收到message之后的完整的流程是如何的。对runtime有完整理解的候选人还能说出oc的对象模型。

什么是method swizzling?
说了解runtime但没听过method swizzling是骗人的。这题很容易搜到答案。定位一些疑难杂症bug,hack老项目实现,阅读第三方源码都有机会接触到这个概念。

UIView和CALayer是啥关系?
能答出UIView是CALayer的delegate就及格了,能说出UIView主要处理事件,CALayer负责绘制就更好,再聊下二者在使用过程中对动画流畅性影响的注意点就superb。UI流畅性是个大话题,推荐看下这两篇文章。中餐西餐

如何高性能的给UIImageView加个圆角?(不准说layer.cornerRadius!)
这题讨论的最多,还有说美工切图就搞定的。答主在项目里做过圆角头像的处理,里面的坑还真不少。cornerRadius会导致offscreen drawing有性能问题,美工切图无法适用有背景图的场景,即使加上shouldRasterize也有cache实效问题。正确的做法是切换到工作线程利用CoreGraphic API生成一个offscreen UIImage,再切换到main thread赋值给UIImageView。这里还涉及到UIImageView复用,圆角头像cache缓存(不能每次都去绘制),新旧头像替换等等逻辑。还有其他的实现方式,但思路离不开工作线程与主线程切换。

使用drawRect有什么影响?(这个可深可浅,你至少得用过。。)
不少同学都用过drawRect或者看别人用过,但不知道这个api存在的含义。这不仅仅是另一种做UI的方式。drawRect会利用CPU生成offscreen bitmap,从而减轻GPU的绘制压力,用这种方式最UI可以将动画流畅性优化到极致,但缺点是绘制api复杂,offscreen cache增加内存开销。UI动画流畅性的优化主要平衡CPU和GPU的工作压力。推荐一篇文章:西餐

ASIHttpRequest或者SDWebImage里面给UIImageView加载图片的逻辑是什么样的?(把UIImageView放到UITableViewCell里面问更赞) 
很多同学没有读源码的习惯,别人的轮子拿来只是用用却不知道真正的营养都在源代码里面。这两个经典的framework代码并不复杂,很值得一读。能对一个UIImageView怎么通过url展示一张图片有完整的理解。涉及到的知识点也非常多,UITableViewCell的复用,memory cache, disk cache, 多线程切换,甚至http协议本身都需要有一定的涉及。

麻烦你设计个简单的图片内存缓存器(移除策略是一定要说的)
内存缓存是个通用话题,每个平台都会涉及到。cache算法会影响到整个app的表现。候选人最好能谈下自己都了解哪些cache策略及各自的特点。常见的有FIFO,LRU,LRU-2,2Q等等。由于NSCache的缓存策略不透明,一些app开发者会选择自己做一套cache机制,其实并不难。

讲讲你用Instrument优化动画性能的经历吧(别问我什么是Instrument)
Apple的instrument为开发者提供了各种template去优化app性能和定位问题。很多公司都在赶feature,并没有充足的时间来做优化,导致不少开发者对instrument不怎么熟悉。但这里面其实涵盖了非常完整的计算机基础理论知识体系,memory,disk,network,thread,cpu,gpu等等,顺藤摸瓜去学习,是一笔巨大的知识财富。动画性能只是其中一个template,重点还是理解上面问题当中CPU GPU如何配合工作的知识。

loadView是干嘛用的?
不要就简单的告诉我没用过,至少问下我有什么用。。这里是apple给开发者自己设置custom view的位置。说UI熟悉的一定要知道。

viewWillLayoutSubView你总是知道的。。
controller layout触发的时候,开发者有机会去重新layout自己的各个subview。说UI熟悉的一定要知道。

GCD里面有哪几种Queue?你自己建立过串行queue吗?背后的线程模型是什么样的?
两种queue,串行和并行。main queue是串行,global queue是并行。有些开发者为了在工作线程串行的处理任务会自己建立一个serial queue。背后是苹果维护的线程池,各种queue要用线程都是这个池子里取的。GCD大家都用过,但很多关键的概念不少人都理解的模凌两可。串行,并行,同步,异步是GCD的核心概念。

用过coredata或者sqlite吗?读写是分线程的吗?遇到过死锁没?咋解决的?
没用过sqlite是说不过去的。用过CoreData的肯定有很多血泪史要说。多谢线程模型你肯定做过比较选择。死锁是啥肯定也是要知道的,没遇到过至少能举个简单的例子来说明。单个线程可以死锁(main thread里dispatch_sync到main queue),多个线程直接也可以死锁(A,B线程互相持有对方需要的资源且互相等待)。

http的post和get啥区别?(区别挺多的,麻烦多说点)
这个可以说很多。不希望听到的答案有

  • 两个差不多,随便用一个。
  • post比get安全(其实两个都不安全)

能说下两个http格式有什么不同,各自应用的场景就合格了。更多可以阅读下这个答案

我知道你大学毕业过后就没接触过算法数据结构了,但是请你一定告诉我什么是Binary search tree? search的时间复杂度是多少?我很想知道!
很多人都很排斥数据结构和算法题,我个人意见是复杂的可以不知道,基础的一定要了解。时间复杂度是什么得知道,list,queue,stack,table,tree这些都要明白是啥。连hash表的概念都不知道怎么能保证在写代码的时候注意性能呢。



隐藏关卡😓

其实当初写这份答案的时候并没有准备什么隐藏关卡,只不过有一些从自己这些年项目经历里总结出来的有深度的知识点,感觉可以难倒不少同学:p。求隐藏关卡的同学真不少,近期我会再准备一份进阶版面试题,权当作隐藏关卡。面向的对象是3~5年iOS开发经验的同学。再次申明下:这只是一份面试题。


一份"有点难"的iOS面试题

之前一时兴致在知乎上出过一份iOS的中级面试题,引起一些关注,不少同学表示对”隐藏关卡“感兴趣。升级版iOS面试题来了,目测难倒90%iOS程序员,目测一大波程序员撸着袖子在靠近。每道题都不难,对知识广度有要求,请摸着良心回答,不要百度。

声明:这份面试题和iOS程序员本身技术水平没任何关联,无论你能否全部答出,都不要对自己产生任何正面或消极的评价,权当做闲暇之余的消遣。

1.NSString如何计算字符的个数?

2.PKI体系当中加密和签名有什么区别?

3.如何自己高效实现NSUserDefault?

4.解释下tcp的慢启动特性。

5.如何用HTTP实现长连接?

6.HTTP2.0针对同一个域名的多个请求,会建立多少个tcp连接?

7.数据库建表的时候索引有什么用?

9.iOS下如何实现指定线程数目的线程池?

10.介绍下iOS设备获取唯一设备号的历史变迁。

11.函数式编程当中的 first-class function是什么意思呢?

12.如何使用runtime hook一个class的某个方法,又如何hook某个instance的方法?

13.谈下Objective C都有哪些锁机制,你一般用哪个?

14.聊下HTTP post的body体使用form-urlencoded和multipart/form-data的区别。

15.让你设计一种机制检测UIViewController的内存泄漏,你会怎么做?

16.通过[UIImage imageNamed:]生成的对象什么时候被释放?

17.applicationWillEnterForeground和applicationDidBecomeActive都会在哪些场景下被调用?举例越多越好。

18.如何终止正在运行的工作线程?

19.穷举iOS下所有的本地持久化方案。

20.如果公司强制996,你有什么心里话要对老板说吗?

如果挑战的朋友数量多,后面我会抽空公布自己的答案:)。

欢迎关注公众号:


  • 孤城
    孤城

    关于方案一可以看一下老谭的http://www.tanhao.me/code/151113.html/这篇

     


    知乎链接:http://www.zhihu.com/question/19604641

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

    首先解释ARC: automatic reference counting自动引用计数。 
    ARC几个要点: 
    在对象被创建时 retain count +1,在对象被release时 retain count -1.当retain count 为0 时,销毁对象。 
    程序中加入autoreleasepool的对象会由系统自动加上autorelease方法,如果该对象引用计数为0,则销毁。 
    那么ARC是为了解决什么问题诞生的呢?这个得追溯到MRC手动内存管理时代说起。 
    MRC下内存管理的缺点: 
    1.当我们要释放一个堆内存时,首先要确定指向这个堆空间的指针都被release了。(避免提前释放) 
    2.释放指针指向的堆空间,首先要确定哪些指针指向同一个堆,这些指针只能释放一次。(MRC下即谁创建,谁释放,避免重复释放) 
    3.模块化操作时,对象可能被多个模块创建和使用,不能确定最后由谁去释放。 
    4.多线程操作时,不确定哪个线程最后使用完毕

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

    assign适用于基本数据类型,weak是适用于NSObject对象,并且是一个弱引用。 
    assign其实也可以用来修饰对象,那么我们为什么不用它呢?因为被assign修饰的对象在释放之后,指针的地址还是存在的,也就是说指针并没有被置为nil。如果在后续的内存分配中,刚好分到了这块地址,程序就会崩溃掉。 
    而weak修饰的对象在释放之后,指针地址会被置为nil。所以现在一般弱引用就是用weak。 
    首先__block是用来修饰一个变量,这个变量就可以在block中被修改(参考block实现原理) 
    __block:使用__block修饰的变量在block代码快中会被retain(ARC下,MRC下不会retain) 
    __weak:使用__weak修饰的变量不会在block代码块中被retain 
    同时,在ARC下,要避免block出现循环引用 __weak typedof(self)weakSelf = self;

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

    是不一样的。 
    在MRC中__block variable在block中使用是不會retain的 
    但是ARC中__block則是會Retain的。 
    取而代之的是用__weak或是__unsafe_unretained來更精確的描述weak reference的目的 
    其中前者只能在iOS5之後可以使用,但是比較好 (該物件release之後,此pointer會自動設成nil) 
    而後者是ARC的環境下為了相容4.x的解決方案。 
    所以上面的範例中

     __block MyClass* temp = …;    // MRC環境下使用
     __weak MyClass* temp = …;    // ARC但只支援iOS5.0以上的版本
     __unsafe_retained MyClass* temp = …;  //ARC且可以相容4.x以後的版本
    

    4.使用nonatomic一定是线程安全的吗?()

    不是的。 
    atomic原子操作,系统会为setter方法加锁。 具体使用 @synchronized(self){//code } 
    nonatomic不会为setter方法加锁。 
    atomic:线程安全,需要消耗大量系统资源来为属性加锁 
    nonatomic:非线程安全,适合内存较小的移动设备

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

    block中的循环引用:一个viewController

        @property (nonatomic,strong)HttpRequestHandler * handler;
        @property (nonatomic,strong)NSData          *data;
        _handler = [httpRequestHandler sharedManager];
        [ downloadData:^(id responseData){
            _data = responseData;
        }];
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    self 拥有_handler, _handler 拥有block, block拥有self(因为使用了self的_data属性,block会copy 一份self) 
    解决方法:

        __weak typedof(self)weakSelf = self
        [ downloadData:^(id responseData){
            weakSelf.data = responseData;
        }];
    • 1
    • 2
    • 3
    • 4
    • 1
    • 2
    • 3
    • 4

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

    在Objective-C中,runtime会自动调用每个类的两个方法。+load会在类初始加载时调用,+initialize会在第一次调用类的类方法或实例方法之前被调用。这两个方法是可选的,且只有在实现了它们时才会被调用。 
    共同点:两个方法都只会被调用一次。

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

    先来看看怎么理解发送消息的含义:

    曾经觉得Objc特别方便上手,面对着 Cocoa 中大量 API,只知道简单的查文档和调用。还记得初学 Objective-C 时把[receiver message]当成简单的方法调用,而无视了“发送消息”这句话的深刻含义。于是[receiver message]会被编译器转化为: 
    objc_msgSend(receiver, selector) 
    如果消息含有参数,则为: 
    objc_msgSend(receiver, selector, arg1, arg2, ...)

    如果消息的接收者能够找到对应的selector,那么就相当于直接执行了接收者这个对象的特定方法;否则,消息要么被转发,或是临时向接收者动态添加这个selector对应的实现内容,要么就干脆玩完崩溃掉。

    现在可以看出[receiver message]真的不是一个简简单单的方法调用。因为这只是在编译阶段确定了要向接收者发送message这条消息,而receive将要如何响应这条消息,那就要看运行时发生的情况来决定了。

    Objective-C 的 Runtime 铸就了它动态语言的特性,这些深层次的知识虽然平时写代码用的少一些,但是却是每个 Objc 程序员需要了解的。

    Objc Runtime使得C具有了面向对象能力,在程序运行时创建,检查,修改类、对象和它们的方法。可以使用runtime的一系列方法实现。

    顺便附上OC中一个类的数据结构 /usr/include/objc/runtime.h

       struct objc_class {
        Class isa OBJC_ISA_AVAILABILITY; //isa指针指向Meta Class,因为Objc的类的本身也是一个Object,为了处理这个关系,r       untime就创造了Meta Class,当给类发送[NSObject alloc]这样消息时,实际上是把这个消息发给了Class Object
    
        #if !__OBJC2__
        Class super_class OBJC2_UNAVAILABLE; // 父类
        const char *name OBJC2_UNAVAILABLE; // 类名
        long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
        long info OBJC2_UNAVAILABLE; // 类信息,供运行期使用的一些位标识
        long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小
        struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量链表
        struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定义的链表
        struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法缓存,对象接到一个消息会根据isa指针查找消息对象,这时会在method       Lists中遍历,如果cache了,常用的方法调用时就能够提高调用的效率。
        struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表
        #endif
    
        } OBJC2_UNAVAILABLE;
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    OC中一个类的对象实例的数据结构(/usr/include/objc/objc.h):

        typedef struct objc_class *Class;
    
        /// Represents an instance of a class.
    
        struct objc_object {
    
            Class isa  OBJC_ISA_AVAILABILITY;
    
        };
    
        /// A pointer to an instance of a class.
    
        typedef struct objc_object *id;
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    向object发送消息时,Runtime库会根据object的isa指针找到这个实例object所属于的类,然后在类的方法列表以及父类方法列表寻找对应的方法运行。id是一个objc_object结构类型的指针,这个类型的对象能够转换成任何一种对象。

    然后再来看看消息发送的函数:objc_msgSend函数

    在引言中已经对objc_msgSend进行了一点介绍,看起来像是objc_msgSend返回了数据,其实objc_msgSend从不返回数据而是你的方法被调用后返回了数据。下面详细叙述下消息发送步骤:

    检测这个 selector 是不是要忽略的。比如 Mac OS X 开发,有了垃圾回收就不理会 retain,release 这些函数了。 
    检测这个 target 是不是 nil 对象。ObjC 的特性是允许对一个 nil 对象执行任何一个方法不会 Crash,因为会被忽略掉。 
    如果上面两个都过了,那就开始查找这个类的 IMP,先从 cache 里面找,完了找得到就跳到对应的函数去执行。 
    如果 cache 找不到就找一下方法分发表。 
    如果分发表找不到就到超类的分发表去找,一直找,直到找到NSObject类为止。 
    如果还找不到就要开始进入动态方法解析了,后面会提到。

    后面还有: 
    动态方法解析resolveThisMethodDynamically 
    消息转发forwardingTargetForSelector

    详情可参考 http://www.jianshu.com/p/620022378e97

    8.什么是method swizzling?

    Method Swizzling 原理(方法搅拌?)

    在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的。 
    每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向具体的Method实现。

    方法指向

    我们可以利用 method_exchangeImplementations 来交换2个方法中的IMP,

    我们可以利用 class_replaceMethod 来修改类,

    我们可以利用 method_setImplementation 来直接设置某个方法的IMP, 
    …… 
    归根结底,都是偷换了selector的IMP,如下图所示: 
    方法交换

    详情:http://blog.csdn.net/yiyaaixuexi/article/details/9374411

    9.UIView和CALayer是啥关系?

    1.UIView是iOS系统中界面元素的基础,所有的界面元素都继承自它。它本身完全是由CoreAnimation来实现的 (Mac下似乎不是这样)。它真正的绘图部分,是由一个叫CALayer(Core Animation Layer)的类来管理。 UIView本身,更像是一个CALayer的管理器,访问它的跟绘图和跟坐标有关的属性,例如frame,bounds等 等,实际上内部都是在访问它所包含的CALayer的相关属性。

    2.UIView有个layer属性,可以返回它的主CALayer实例,UIView有一个layerClass方法,返回主layer所使用的 类,UIView的子类,可以通过重载这个方法,来让UIView使用不同的CALayer来显示,例如通过

        - (class) layerClass {
    
             return ([CAEAGLLayer class]);
        }
    • 1
    • 2
    • 3
    • 4
    • 1
    • 2
    • 3
    • 4

    =使某个UIView的子类使用GL来进行绘制。

    3.UIView的CALayer类似UIView的子View树形结构,也可以向它的layer上添加子layer,来完成某些特殊的表 示。例如下面的代码

        grayCover = [[CALayer alloc] init];
    
        grayCover.backgroundColor = [[[UIColor blackColor] colorWithAlphaComponent:0.2] CGColor];
    
        [self.layer addSubLayer: grayCover];
    • 1
    • 2
    • 3
    • 4
    • 5
    • 1
    • 2
    • 3
    • 4
    • 5

    会在目标View上敷上一层黑色的透明薄膜。

    4.UIView的layer树形在系统内部,被系统维护着三份copy(这段理解有点吃不准)。

    1. 逻辑树,就是代码里可以操纵的,例如更改layer的属性等等就在这一份。
    2. 动画树,这是一个中间层,系统正在这一层上更改属性,进行各种渲染操作。
    3. 显示树,这棵树的内容是当前正被显示在屏幕上的内容。

    这三棵树的逻辑结构都是一样的,区别只有各自的属性。

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

    我觉得应该是使用Quartz2D直接绘制图片,得把这个看看。 
    步骤: 
      a、创建目标大小(cropWidth,cropHeight)的画布。

      b、使用UIImage的drawInRect方法进行绘制的时候,指定rect为(-x,-y,width,height)。

      c、从画布中得到裁剪后的图像。

    - (UIImage*)cropImageWithRect:(CGRect)cropRect
    {
        CGRect drawRect = CGRectMake(-cropRect.origin.x , -cropRect.origin.y, self.size.width * self.scale, self.size.height * self.scale);
    
        UIGraphicsBeginImageContext(cropRect.size);
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGContextClearRect(context, CGRectMake(0, 0, cropRect.size.width, cropRect.size.height));
    
        [self drawInRect:drawRect];
    
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
    
        return image;
    }
    
    @end
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    11. 使用drawRect有什么影响?(这个可深可浅,你至少得用过。。)

    drawRect方法依赖Core Graphics框架来进行自定义的绘制,但这种方法主要的缺点就是它处理touch事件的方式:每次按钮被点击后,都会用setNeddsDisplay进行强制重绘;而且不止一次,每次单点事件触发两次执行。这样的话从性能的角度来说,对CPU和内存来说都是欠佳的。特别是如果在我们的界面上有多个这样的UIButton实例。

    12. ASIHttpRequest或者SDWebImage里面给UIImageView加载图片的逻辑是什么样的?

    详见SDWebImage的实现流程 http://www.cnblogs.com/6duxz/p/4159572.html

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

    图片的内存缓存,可以考虑将图片数据保存到一个数据模型中。所以在程序运行时这个模型都存在内存中。 
    移除策略:释放数据模型对象。

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

    可以参考iOS App性能优化

    15. loadView是干嘛用的?

    当你访问一个ViewController的view属性时,如果此时view的值是nil,那么,ViewController就会自动调用loadView这个方法。这个方法就会加载或者创建一个view对象,赋值给view属性。 
    loadView默认做的事情是:如果此ViewController存在一个对应的nib文件,那么就加载这个nib。否则,就创建一个UIView对象。

    如果你用Interface Builder来创建界面,那么不应该重载这个方法。

    如果你想自己创建view对象,那么可以重载这个方法。此时你需要自己给view属性赋值。你自定义的方法不应该调用super。如果你需要对view做一些其他的定制操作,在viewDidLoad里面去做。

    =========================================

    根据上面的文档可以知道,有两种情况:

    1、如果你用了nib文件,重载这个方法就没有太大意义。因为loadView的作用就是加载nib。如果你重载了这个方法不调用super,那么nib文件就不会被加载。如果调用了super,那么view已经加载完了,你需要做的其他事情在viewDidLoad里面做更合适。

    2、如果你没有用nib,这个方法默认就是创建一个空的view对象。如果你想自己控制view对象的创建,例如创建一个特殊尺寸的view,那么可以重载这个方法,自己创建一个UIView对象,然后指定 self.view = myView; 但这种情况也没有必要调用super,因为反正你也不需要在super方法里面创建的view对象。如果调用了super,那么就是浪费了一些资源而已 
    参考:http://www.cnblogs.com/dyllove98/archive/2013/06/06/3123005.html

    16. viewWillLayoutSubView你总是知道的。

    横竖屏切换的时候,系统会响应一些函数,其中 viewWillLayoutSubviews 和 viewDidLayoutSubviews。

    //

    - (void)viewWillLayoutSubviews
    
    {
    
         [self _shouldRotateToOrientation:(UIDeviceOrientation)[UIApplication sharedApplication].statusBarOrientation];
    
    }
    
    -(void)_shouldRotateToOrientation:(UIDeviceOrientation)orientation {
            if (orientation == UIDeviceOrientationPortrait ||orientation ==
                    UIDeviceOrientationPortraitUpsideDown) {
              // 竖屏
    }
    else {
             // 横屏
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    通过上述一个函数就知道横竖屏切换的接口了。 
    注意:viewWillLayoutSubviews只能用在ViewController里面,在view里面没有响应。

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

    1.主队列 dispatch_main_queue(); 串行 ,更新UI 
    2.全局队列 dispatch_global_queue(); 并行,四个优先级:background,low,default,high 
    3.自定义队列 dispatch_queue_t queue ; 可以自定义是并行:DISPATCH_QUEUE_CONCURRENT或者串行DISPATCH_QUEUE_SERIAL

    18. 用过coredata或者sqlite吗?读写是分线程的吗?遇到过死锁没?咋解决的?

    参考:CoreData与SQLite的线程安全

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

    1.GET请求的数据会附在URL之后(就是把数据放置在HTTP协议头中),以?分割URL和传输数据,参数之间以&相连,如:login.action?name=hyddd&password=idontknow&verify=%E4%BD%A0%E5%A5%BD。如果数据是英文字母/数字,原样发送,如果是空格,转换为+,如果是中文/其他字符,则直接把字符串用BASE64加密,得出如:%E4%BD%A0%E5%A5%BD,其中%XX中的XX为该符号以16进制表示的ASCII。 
      POST把提交的数据则放置在是HTTP包的包体中。

    2.”GET方式提交的数据最多只能是1024字节,理论上POST没有限制,可传较大量的数据,IIS4中最大为80KB,IIS5中为100KB”??!

      以上这句是我从其他文章转过来的,其实这样说是错误的,不准确的:

      (1).首先是”GET方式提交的数据最多只能是1024字节”,因为GET是通过URL提交数据,那么GET可提交的数据量就跟URL的长度有直接关系了。而实际上,URL不存在参数上限的问题,HTTP协议规范没有对URL长度进行限制。这个限制是特定的浏览器及服务器对它的限制。IE对URL长度的限制是2083字节(2K+35)。对于其他浏览器,如Netscape、FireFox等,理论上没有长度限制,其限制取决于操作系统的支持。

      注意这是限制是整个URL长度,而不仅仅是你的参数值数据长度。[见参考资料5]

      (2).理论上讲,POST是没有大小限制的,HTTP协议规范也没有进行大小限制,说“POST数据量存在80K/100K的大小限制”是不准确的,POST数据是没有限制的,起限制作用的是服务器的处理程序的处理能力。

    3.在ASP中,服务端获取GET请求参数用Request.QueryString,获取POST请求参数用Request.Form。在JSP中,用request.getParameter(\”XXXX\”)来获取,虽然jsp中也有request.getQueryString()方法,但使用起来比较麻烦,比如:传一个test.jsp?name=hyddd&password=hyddd,用request.getQueryString()得到的是:name=hyddd&password=hyddd。在PHP中,可以用GET_POST分别获取GET和POST中的数据,而REQUESTGETPOSTJSP使requestPHP使_REQUEST都会有隐患,这个下次再写个文章总结。

    4.POST的安全性要比GET的安全性高。注意:这里所说的安全性和上面GET提到的“安全”不是同个概念。上面“安全”的含义仅仅是不作数据修改,而这里安全的含义是真正的Security的含义,比如:通过GET提交数据,用户名和密码将明文出现在URL上,因为(1)登录页面有可能被浏览器缓存,(2)其他人查看浏览器的历史纪录,那么别人就可以拿到你的账号和密码了,除此之外,使用GET提交数据还可能会造成Cross-site request forgery攻击。

    总结一下,Get是向服务器发索取数据的一种请求,而Post是向服务器提交数据的一种请求,在FORM(表单)中,Method默认为”GET”,实质上,GET和POST只是发送机制不同,并不是一个取一个发!

    20. 我知道你大学毕业过后就没接触过算法数据结构了,但是请你一定告诉我什么是Binary search tree? search的时间复杂度是多少?

    Binary search tree:二叉搜索树。 
    主要由四个方法:(用C语言实现或者Python) 
    1.search:时间复杂度为O(h),h为树的高度

    2.traversal:时间复杂度为O(n),n为树的总结点数。

    3.insert:时间复杂度为O(h),h为树的高度。

    4.delete:最坏情况下,时间复杂度为O(h)+指针的移动开销。

    可以看到,二叉搜索树的dictionary operation的时间复杂度与树的高度h相关。所以需要尽可能的降低树的高度,由此引出平衡二叉树Balanced binary tree。它要求左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。这样就可以将搜索树的高度尽量减小。常用算法有红黑树、AVL、Treap、伸展树等。

    Written with StackEdit.


PerpendiculousProgramming, Personal Finance, and Personal musings

2009.09.23

@synchronized, NSLock, pthread, OSSpinLock showdown, done right

Filed under: Uncategorized — cwright @ 1:19 am

Somewhere out there on the internet, there’s a “showdown” between @synchronized, NSLock, pthread mutexes, and OSSpinLock. It aims to measure their performance relative to each other, but uses sloppy code to perform the measuring. As a result, while the performance ordering is correct (@synchronized is the slowest, OSSpinLock is the fastest), the relative cost is severely misrepresented. Herein I attempt to rectify that benchmark.

Locking is absolutely required for critical sections. These arise in multithreaded code, and sometimes their performance can have severe consequences in applications. The problem with the aforementioned benchmark is that it did a bunch of extraneous work while it was locking/unlocking. It was doing the same amount of extraneous work, so the relative order was correct (the fastest was still the fastest, the slowest still the slowest, etc), but it didn’t properly show just how much faster the fastest was.

In the benchmark, the author used autorelease pools, allocated objects, and then released them all.  While locking.  This is a pretty reasonable use-case, but by no means the only one.  For most high-performance, multithreaded code, you’ll spend a _bunch_ of time trying to make the critical sections as small and fast as possible.  Large, slow critical sections effectively undo the multithreading speed up by causing threads to block each other out unnecessarily.  So when you’ve trimmed the critical sections down to the minimum, another sometimes-justified optimization is to optimize the amount of time spent locking/unlocking itself.

Just to make things exciting though, not all locking primitives are created equal.  Two of the 4 mentioned have special properties that can affect how long they take, and how the operate under pressure.  I’ll get to that towards the end.

First up, here’s my “no-nonsense” microbench code:

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <objc/message.h>
#import <libkern/OSAtomic.h>
#import <pthread.h>

#define ITERATIONS (1024*1024*32)

static unsigned long long disp=0, land=0;

int main()
{
 double then, now;
 unsigned int i, count;
 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
 OSSpinLock spinlock = OS_SPINLOCK_INIT;

 NSAutoreleasePool *pool = [NSAutoreleasePool new];

 NSLock *lock = [NSLock new];
 then = CFAbsoluteTimeGetCurrent();
 for(i=0;i<ITERATIONS;++i)
 {
 [lock lock];
 [lock unlock];
 }
 now = CFAbsoluteTimeGetCurrent();
 printf("NSLock: %f sec\n", now-then);    

 then = CFAbsoluteTimeGetCurrent();
 IMP lockLock = [lock methodForSelector:@selector(lock)];
 IMP unlockLock = [lock methodForSelector:@selector(unlock)];
 for(i=0;i<ITERATIONS;++i)
 {
 lockLock(lock,@selector(lock));
 unlockLock(lock,@selector(unlock));
 }
 now = CFAbsoluteTimeGetCurrent();
 printf("NSLock+IMP Cache: %f sec\n", now-then);    

 then = CFAbsoluteTimeGetCurrent();
 for(i=0;i<ITERATIONS;++i)
 {
 pthread_mutex_lock(&mutex);
 pthread_mutex_unlock(&mutex);
 }
 now = CFAbsoluteTimeGetCurrent();
 printf("pthread_mutex: %f sec\n", now-then);

 then = CFAbsoluteTimeGetCurrent();
 for(i=0;i<ITERATIONS;++i)
 {
 OSSpinLockLock(&spinlock);
 OSSpinLockUnlock(&spinlock);
 }
 now = CFAbsoluteTimeGetCurrent();
 printf("OSSpinlock: %f sec\n", now-then);

 id obj = [NSObject new];

 then = CFAbsoluteTimeGetCurrent();
 for(i=0;i<ITERATIONS;++i)
 {
 @synchronized(obj)
 {
 }
 }
 now = CFAbsoluteTimeGetCurrent();
 printf("@synchronized: %f sec\n", now-then);

 [pool release];
 return 0;
}

We do 5 tests:  We test NSLock, NSLock with IMP caching, pthread mutexes, OSSpinLocks, and then finally @synchronized.  We simply lock and unlock 33554432 times (that’s 1024*1024*32 for those keeping score at home ;), and see how long it takes.  No allocation, no releases, no autorelease pools, nothing.  Just pure lock/unlock goodness.  I ran the test a few times, and averaged the results (so overall, the results are from something like 100 million lock/unlock cycles each)

  1. NSLock: 3.5175 sec
  2. NSLock+IMP Cache: 3.1165 sec
  3. Mutex: 1.5870 sec
  4. SpinLock: 1.0893
  5. @synchronized: 9.9488 sec
Lock Performance

Lock Performance

From the above graph, we can see a couple thing:  First, @synchronized is _Really_ expensive — like, 3 times as expensive as anything else.  We’ll get into why that is in a moment.  Otherwise, we see that NSLock and NSLock+IMP Cache are pretty close — these are built on top of pthread mutexes, but we have to pay for the extra ObjC overhead.  Then there’s Mutex (pthread mutexes) and SpinLock — these are pretty close, but even then SpinLock is almost 30% faster than Mutex.  We’ll get into that one too.  So from top to bottom we have almost an order of magnitude difference between the worst and best.

The nice part about these all is that they all take about the same amount of code — using NSLock takes as many lines as a pthread mutex, and the same number for a spinlock.  @synchronized saves a line or two, but with a cost like that it quickly looks unappealing in all but the most trivial of cases.

So, what makes @sychronized and SpinLock so different from the others?

@synchronized is very heavy weight because it has to set up an exception handler, and it actually ends up taking a few internal locks on its way there.  So instead of a simple cheap lock, you’re paying for a couple locks/unlocks just to acquire your measly lock.  Those take time.

OSSpinLock, on the other hand, doesn’t even enter the kernel — it just keeps reloading the lock, hoping that it’s unlocked.  This is terribly inefficient if locks are held for more than a few nanoseconds, but it saves a costly system call and a couple context switches.  Pthread mutexes actually use an OSSpinLock first, to keep things running smoothly where there’s no contention.  When there is, it resorts to heavier, kernel-level locking/tasking stuff.

So, if you’ve got hotly-contested locks, OSSpinLock probably isn’t for you (unless your critical sections are _Really_ _Fast_).  Pthread mutexes are a tiny bit more expensive, but they avoid the power-wasting effects of OSSpinLock.

NSLock is a pretty wrapper on pthread mutexes.  They don’t provide much else, so there’s not much point in using them over pthread mutexes.

Of course, standard optimization disclaimers apply:  don’t do it until you’re sure you’ve chosen the correct algorithms, have profiled to find hotspots, and have found locking to be one of those hot items.  Otherwise, you’re wasting your time on something that’s likely to provide minimal benefits.

6 Comments »

  1. This is very interesting and useful, thanks for the sample code too!

    Comment by Zachary Howe — 2012.08.10 @ 12:48 pm

  2. thank you very much, With your article, i finally konw the speed of different sync mechanism

    Comment by maple — 2012.11.01 @ 8:05 am

  3. nice article 

    Comment by Paul — 2012.11.03 @ 11:35 am

  4. Interesting article,

    I just thought that I let you know that I tried your code on an iPhone6 Plus (arm64, iOS9.3).

    What is interesting is that NSLock is now faster than pthread_mutex (on iOS at least, it is still slower on Mac OS X). So aside from using spin locks, which have their own disadvantages and are only good for special cases as you pointed out, clean Objective-C code (using NSLock) is the fastest way to do it. (Not that I would have used the mutex before just for the relatively small impact on the speed in actual applications.)

    I had problems with the NSLock+IMPCache method (it seams the IMP prototype has changed to take 0 parameters, but when I removed the parameters, it crashes at runtime), so I just removed it. I copied the code three times into the viewDidLoad function of a template single view app. I did it three times to be sure that the effect of the fresh start of the app doesn’t negatively influence the measurements.

    Here is a typical output:

    NSLock: 2.193462 sec
    pthread_mutex: 2.603898 sec
    OSSpinlock: 1.192254 sec
    @synchronized: 8.019554 sec

    NSLock: 2.171150 sec
    pthread_mutex: 2.605145 sec
    OSSpinlock: 1.193469 sec
    @synchronized: 7.984741 sec

    NSLock: 2.178009 sec
    pthread_mutex: 2.604524 sec
    OSSpinlock: 1.192347 sec
    @synchronized: 7.991478 sec

    Comment by Jochen — 2016.03.29 @ 6:00 am

  5. […] 这篇文章 […]

    Pingback by 轻松理解多线程同步基础概念-IT技术 — 2016.05.30 @ 10:06 am

  6. Note: You should absolutely never use spin locks. They’re unsafe because there’s no way to guarantee forward progress if there’s a priority inversion. Mutex is the way to go – if there’s a priority inversion, the kernel can temporarily bump the blocked thread’s priority so that things can move forward.

    Comment by Adlai — 2016.07.05 @ 10:39 am


    1. [OC]之 atomic 与 nonatomic的区别

      susanjia susanjia 2015-03-05 15:24:51
       
posted on 2016-11-28 16:31  不及格的程序员-八神  阅读(14)  评论(0编辑  收藏  举报