【vfp开源代码栏目提醒】:以下是网学会员为您推荐的vfp开源代码-10个迷惑新手的Cocoa - 成本管理,希望本篇文章对您学习有所帮助。
本文由长春白癜风专科医院 http://tf463.com/ 收集,转载请注明出处 10 个迷惑新手的 CocoaampObjective-c 开发问题1. language background2. runtime3. thread4. runloop5. delegate protocol6. event responder7. memory management8. class heritage and category9. design pattern10. drawing issue 首先请谅解我可能使用很多英文,毕竟英文资料将来会是你的主要资料来源。
这篇博客将描述一些我见到的众多 Cocoa 开发新手遇到的问题和障碍。
在你继续深入学习 MacOS 编程之前,请停下脚步弄清这些问题。
如果你是新手,这个教程不要希望一次能看 的非常透彻,学一定阶段反回来再看看又会有新的体会的。
1. language background 首先 c c语言背景,必须。
很多人问:”没有任何语言基础,我不想学 c 直接学 objective-c。
“ 这里我简单说几句,objc 是 c 的超集,也就是说大部分 objc
代码其实是 c、而且众多
开源 代码是 cc写成的。
你不学好 c 在 unix 世界里只能是个二流开发者!也许说得过于严厉, 不过自己斟酌把。
接着 English必须。
苹果不会把它们文档都写成中文的。
“什么,有人翻译?” 等有人闲着 翻译出来了的时候,大家都已经卖了很多软件了。
你也是跟着人家屁股后面做开发。
2. runtime(运行时) Objective-c 是动态语言 很多新手或者开发人员常常被 runtime 这个东西所迷惑。
而恰恰 这是一个非常重要的概念。
我可以这么问:“如果让你(设计、)实现一个计算机语言,你 本文由吉林白癜风医院 http://tf463.com/ 收集,转载请注明出处 本文由长春白癜风专科医院 http://tf463.com/ 收集,转载请注明出处要如何下手?” 很少程序员这么思考过。
但是这么一问,就会强迫你从更高层次思考 1 以前的问题了。
注意我这句话设计括起来了,稍微次要点,关键是实现。
我把实现分成 3 种不同的层次:第一种是传统的面向过程的语言开发,例如 c 语言。
实现 c 语言编译器很简单,只要按照语法规则实现一个 LALR 语法分析器就可以了,编译器优化是非常难的 topic,不在这里讨论范围内,忽略。
这里我们实现了编译器其中最最基础和原始的目标之一就是把一份
代码里的函数名称,转化成一个相对内存地址,把调用这个函数的语句转换成一个 jmp 跳转指令。
在程序开始运行时候,调用语句可以正确跳转到对应的函数地址。
这样很好,也很直白,但是太死板了。
Everything is predetermined.我们希望语言更加灵活,于是有了第二种改进,开发面向对象的语言,例如 c。
c在 c的基础上增加了类的部分。
但这到底意味着什么呢?我们再写它的编译器要如何考虑呢?其实,就是让编译器多绕个弯,在严格的 c 编译器上增加一层类处理的机制,把一个函数限制在它处在的 class 环境里,每次请求一个函数调用,先找到它的对象 其类型返回值,参数等等,确定了这些后再 jmp 跳转到需要的函数。
这样很多程序增加了灵活性同样一个函数调用会根据请求参数和类的环境返回完全不同的结果。
增加类机制后,就模拟了现实世界的抽象模式,不同的对象有不同的属性和方法。
同样的方法,不同的类有不同的行为! 这里大家就可以看到作为一个编译器开发者都做了哪些进一步的思考。
虽然面相对象语言有所改进,但还是死板 我们仍然叫 c是 static language.希望更加灵活!于是我们完全把上面哪个类的实现部分抽象出来,做成一套完整运行阶段的检测环境,形成第三种,动态语言。
这次再写编译器甚至保留部分
代码里的 sytax 名称,名称错误检测,runtime 环境注册所以全局的类,函数,变量等等信息等等,我们可以无限的为这个层增加必要的功能。
调用函数时候,会先从这个运行时环境里检测所以可能的参数再做 jmp 跳转。
这,就是 runtime。
编译器开发起来比上面更加弯弯绕。
但是这个层极大增加了程序的灵活性。
例如当调用一个函数时候,前 2 种语言,很有可能一个 jmp 到了一个非法地址导致程序 crash 但是在这个层次里面,runtime 就过滤掉了这些可能性。
这就是为什么 dynamic langauge 更加强壮。
因为编译器和 runtime 环境开发人员已经帮你处理了这些问题。
好了上面说着这么多,我们再返回来看 objective-c 的这些语句: 本文由吉林白癜风医院 http://tf463.com/ 收集,转载请注明出处 本文由长春白癜风专科医院 http://tf463.com/ 收集,转载请注明出处 1id objself 2if obj respondsToSelector:selectorfunction1: 3 4if obj isKindOfClass:NSArray class 5 6if obj conformsToProtocol:protocolmyProtocol 7 8if obj class isSubclassOfClass:NSArray class 910obj someNonExistFunction看似很简单的语句,但是为了让语言实现这个能力,语言开发者要付出很多努力实现runtime 环境。
这里运行时环境处理了弱类型、函数存在检查工作。
runtime 会检测注册列表里是否存在对应的函数,类型是否正确,最后确定下来正确的函数地址,再进行保存寄存器状态,压栈,函数调用等等实际的操作。
id knifeKnife grateKnife1 NSArray monsterListNSArray array2 monsterList makeObjectsPerformSelector:selectorkillMonster:3 withObject:knife用 cc完成这个功能还是比较非常麻烦的,但是动态语言处理却非常简单并且这些语句让objc 语言更加 intuitive。
在 Objc 中针对对象的函数调用将不再是普通的函数调用, obj function1With:var1 这样的函数调用将被运行时环境转换成 Objc 的 runtime 环境是
开源的,objc_msgSendtargetselectorfunction1With:var1 。
所以我们可以拿出一下实现做简单介绍,可以看到 objc_msgSend 由汇编语言实现,我们甚至不必阅读
代码,只需查看注释就可以了解,运行时环境在函数调用前做了比较全面的安全检查,已确保动态语言函数调用不会导致程序 crash。
对于希望深入学习的朋友可以自行下载 Objc-runtime 源
代码来阅读,这里就不再深入讲解。
1/ 2 3 id objc_msgSendid self 4 SEL op 5 ... 6 本文由吉林白癜风医院 http://tf463.com/ 收集,转载请注明出处 本文由长春白癜风专科医院 http://tf463.com/ 收集,转载请注明出处7 On entry: a1 is the message receiver8 a2 is the selector910/11 ENTRY objc_msgSend1 check whether receiver is nil2 teq a1 01 moveq a2 03 bxeq lr14 save registers and load receivers class for CacheLookup1 stmfd sp a4v1-v35 ldr v1 a1 ISA16 receiver is non-nil: search the cache1 CacheLookup a2 LMsgSendCacheMiss71 cache hit imp in ip - prep for forwarding restore registers and8call1 teq v1 v1 / set nonstret eq /9 ldmfd sp a4v1-v32 bx ip02 cache miss: go search the method lists1LMsgSendCacheMiss:2 ldmfd sp a4v1-v32 b _objc_msgSend_uncached23LMsgSendExit:2 END_ENTRY objc_msgSend425 .text2 .align 26_objc_msgSend_uncached:27 Push stack frame 本文由吉林白癜风医院 http://tf463.com/ 收集,转载请注明出处 本文由长春白癜风专科医院 http://tf463.com/ 收集,转载请注明出处2 stmfd sp a1-a4r7lr8 add r7 sp 162 SAVE_
VFP93 Load class and selector0 ldr a1 a1 ISA / class receiver-gtisa /3 MOVE a2 a2 / selector already in a2 /13 Do the lookup2 MI_CALL_EXTERNAL__class_lookupMethodAndLoadCache3 MOVE ip a133 Prep for forwarding Pop stack frame and call imp4 teq v1 v1 / set nonstret eq /3 RESTORE_
VFP5 ldmfd sp a1-a4r7lr3 bx ip6373839404142434445464 本文由吉林白癜风医院 http://tf463.com/ 收集,转载请注明出处 本文由长春白癜风专科医院 http://tf463.com/ 收集,转载请注明出处 7 4 8 4 9 5 0 5 1 5 2 5 3 5 4 5 5 5 6 5 7 5 8现在说一下 runtime 的负面影响: 1. 关于执行效率问题。
“静态语言执行效率要比动态语言高”,这句没错。
因为一部分 cpu 计算损耗在了 runtime 过程中,而从上面的汇编
代码也可以看出,大概损耗在哪些地方。
而静态语言生成的机器指令更简洁。
正因为知道这个原因,所以开发语言的人付出很大一部分努力为了保持 runtime 小巧上。
所以 objecitve-c 是 c 的超集一个小巧的 runtime 环境。
但是,换句话说,从算法角度考虑,这点复杂度不算差别的,Big O notation 结果不会有差别。
Its not logn vs n2 2. 另外一个就是安全性。
动态语言由于运行时环境的需求,会保留一些源码级别的程序结构。
这样就给破解带来的方便之门。
一个现成的说明就是,java大家都知道 java 运行在 jre 上面。
这就是典型的 runtime例子。
它的执行文件.class 全部可以反编译回近似源
代码。
所以这里的额外提示就是如果你需要写和安全有关的
代码,离 objc 远点,直接用 c 去。
简单理解:“Runtime is everything between your each function call.”但是大家要明白,第二点我提到 runtime 并不只是因为它带来了这些简便的语言特性。
而是这些简单的语言特性在实际运用中需要你从完全不同的角度考虑和解决问题。
只是计算 本文由吉林白癜风医院 http://tf463.com/ 收集,转载请注明出处 本文由长春白癜风专科医院 http://tf463.com/ 收集,转载请注明出处11,很多语言都是一样的,但是随着问题的复杂,项目的增长,静态语言和动态语言就会演化出完全不同的风景。
3. thread“thread synchronization another notorious trouble”记得上学时候学操作系统这门课,里面都会有专门一章介绍任务调度和生产者消费者的问题。
这就是为今后使用进程、线程开发打基础。
概念很简单,但难点在 synchronization同步,因为死锁检测算法不是 100有效,否则就根本没有死锁这个说法了。
另一个原因是往往这类错误很隐晦,静态分析很难找到。
同时多线程开发抽象度较高需要经验去把握。
总体来说,我见到的在这方面的问题可以分为一下几点:1. 对系统整体结构认识模糊不知道多线程开发的几个基点,看别人
代码越看越糊涂的。
一会 NSThread一会 GrandCentral Dispatch、block,一会又看到了 pthread 等等。
Apple 封装了很多线程的 API 多线程开发的基本结构入下图:可以看到在多线程开发中你可以选择这上面这 4 种不同的方式。
Mach 是核心的操作系统部分。
其实这个我也不是非常熟悉,至少我还没有读到过直接使用mach 做多线程的
代码。
本文由吉林白癜风医院 http://tf463.com/ 收集,转载请注明出处 本文由长春白癜风专科医院 http://tf463.com/ 收集,转载请注明出处pthread(POSIX Threads)是传统的多线程标准,灵活、轻巧,但是需要理论基础,开发复杂。
需要注意一点,根据 apple 文档提示,在 Cocoa 下使用 pthread 需要先启动至少一个 NSThread,确定进入多线程环境后才可以。
NSThread 是 Mac OS 10.0 后发布的多线程 API 较为高层,但是缺乏灵活性,而且和pthread 相比效率低下。
Grand Central Dispatch 是 10.6 后引入的
开源多线程库,它介于 pthread 和NSThread 之间。
比 NSThread 更灵活、小巧,并且不需要像 pthread 一样考虑很多 lock的问题。
同时 objective-c 2.0 发布的新语法特性之一 blocks,也正是根据 Grand CentralDispatch 需求推出的。
所以在你写多线程
代码或者阅读多线程
代码时候,心理要先明确使用哪种技术。
2. thread 和 Reference Counting 内存管理造成的问题。
线程里面的方法都要放到 NSAutoreleasePool 里面吗这类问题很常见,迷惑的原因是不明白 NSAutoreleasePool 到底是干什么用的。
NSAutoreleasePool 跟 thread 其实关系并不显著,它提供一个临时内存管理空间,好比一个沙箱,确保不会有不当的内存分配泄露出来,在这个空间内新分配的对象要向这个 pool做一下注册告诉:“pool,我新分配一块空间了”。
当 pool drain 掉或者 release,它里面分配过的内存同样释放掉。
可见和 thread 没有很大关系。
但是,我们阅读
代码的时候经常会看到,新开线程的函数内总是以 NSAutoreleasePool 开始结束。
这又是为什么呢!? 因为thread 内恰好是最适合需要它的地方! 线程函数应该计算量大,时间长supposed to beheavy。
在线程里面可能会有大量对象生成,这时使用 autoreleasepool 管理更简洁。
所以这里的答案是,不一定非要在线程里放 NSAutoreleasePool,相对的在 cocoa 环境下任意地方都可以使用 NSAutoreleasePool。
如果你在线程内不使用 NSAutoreleasePool,要记得在内部 alloc 和 relase 配对出现保证没有内存泄露。
3. 线程安全每个程序都有一个主线程main thread它负责处理事件响应,和 UI 更新。
本文由吉林白癜风医院 http://tf463.com/ 收集,转载请注明出处 本文由长春白癜风专科医院 http://tf463.com/ 收集,转载请注明出处更新 UI 问题。
很多新手会因为这个问题,导致程序崩溃或出现各种问题。
而且逛论坛会看到所以人都会这么告诉你:“不要在后台线程更新你的 UI”。
其实这个说法不严密,在多线程环境里处理这个问题需要谨慎,而且要了解线程安全特性。
首先我们需要把“UI 更新”这个词做一个说明,它可以有 2 个层次理解,首先是绘制,其次是显示。
这里绘制是可以在任何线程里进行,但是要向屏幕显示出来就需要在主线程操作了。
我举个例子说明一下,例如现在我们有一个 NSImageView,里面设置了一个 NSImage 这时我想给 NSImage 加个变色滤镜,这个过程就可以理解为绘制。
那么我完全可以再另外一个线程做这个比较费时的操作,滤镜增加完毕再通知 NSImageView 显示一下。
另一个例子就是,Twitter 客户端会把每一条微博显示成一个 cell,但是速度很快,这就是因为它先对cell 做了 offscreen 的渲染,然后再拿出来显示。
所以通过这点我们也可以得到进一步的认识,合理设计 view 的更新是非常重要的部分。
很多新手写得
代码片段没错,只是因为放错了地方就导致整个程序出现各种问题。
根据苹果线程安全摘要说明,再其它线程更新 view 需要使用 lockFocusIfCanDraw 和unlockFocus 锁定,确保不会出现安全问题。
另外还要知道常用容器的线程安全情况。
immutable 的容器是线程安全的,而 mutable 容器则不是。
例如 NSArray 和 NSMutableArray。
4. Asynchronous(异步) vs. Synchronous(同步)我在一个 view 要显示多张 web 图片,我想问一下,我是应该采用异步一个一个下载的方式,还是应该采用多线程同时下载的方式,还是 2 个都用,那种方式好呢?实际上单独用这2个方法都不好。
并不是简单的用了更多线程就提高速度。
这个问题同时涉及客户端和服务器的情况。
处理这种类型的程序,比较好的结构应该是:非主线程建立一个队列相当于 Asynchronous任务,队列里同时启动 n 个下载任务相当于 Synchronous 任务。
这里的 n 在 28 左右就够了。
这个结构下可以认为队列里面每 n 个任务之间是异步关系,但是这 n 个任务之间又 本文由吉林白癜风医院 http://tf463.com/ 收集,转载请注明出处 本文由长春白癜风专科医院 http://tf463.com/ 收集,转载请注明出处是同步关系,每次同时下载 28 张图片。
这样处理基本可以满足速度要求和各类服务器限制。
5. thread 和 runlooprunloop 是线程里的一部分,但我觉得有必要单独拿出来写,是因为它涉及的东西比较容易误解,而说明它的文章又不多。
4. runloopthread 和 runloop 在以前,开发者根本不太当成一个问题。
因为没有静态语言里 runloop就是固定的线程执行 loop。
而现在 Cocoa 新手搞不明白的太多了,因为没有从动态角度看它,首先回想一下第 2 点介绍的 runtime 概念,接着出一个思考题。
现在有一个程序片段如下: 1- voidmyThread:idsender 2 3 NSAutoreleasePool poolNSAutoreleasePool alloc init 4 while TRUE 5 //do some jobs 6 //break in some condition 7 usleep10000 8 pool drain 9 10 pool release11现在要求,做某些设计,使得当这个线程运行的同时,还可以从其它线程里往它里面随意增加或去掉不同的计算任务。
这,就是 NSRunloop 的最原始的开发初衷。
让一个线程的计算任务更加灵活。
这个功能在 c c里也许可以做到但是非常难,最主要的是因为语言能力的限制,以前的程序员很少这么去思考。
好,现在我们对上面
代码做一个非常简单的进化: 1NSMutableArray targetQueue 2NSMutableArray actionQueue 本文由吉林白癜风医院 http://tf463.com/ 收集,转载请注明出处 本文由长春白癜风专科医院 http://tf463.com/ 收集,转载请注明出处 3- voidmyThread:idsender 4 5 NSAutoreleasePool poolNSAutoreleasePool alloc init 6 while TRUE 7 8 //do some jobs 9 //break in some condition10 int ntargetQueue count11 assertnactionQueue count12 forint i0iltni13 id targettargetQueue objectAtIndex:i14 SEL actionNSSelectorFromStringactionQueue15objectAtIndex:i16 if target respondsToSelector:action 17 target performSelector:action withObject:nil18 19 2021 usleep100002223 pool drain24 2526 pool release 注意,这里没有做线程安全处理,记住 Mutable container is not thread safe. 这个简单的扩 当我们从另外线程向 targetQueue 让我们看到了如何利用 runtime 能力让线程灵活起来。
展,和 actionQueue 同时加入对象和方法时候,这个线程函数就有了执行一个额外
代码的能力。
有人会问哪里有 runloop 那个是 nsrunloop 看不出来啊。
1while TRUE 2 //break in some condition3这个结构就叫线程的 runloop 它和 NSRunloop 这个类虽然名字很像,但完全不是一个东西。
以前在使用静态语言开始时候,程序员没有什么迷惑,因为没有 NSRunloop 这个东西。
我接着来说,这个 NSRunloop 是如何来得。
本文由吉林白癜风医院 http://tf463.com/ 收集,转载请注明出处 本文由长春白癜风专科医院 http://tf463.com/ 收集,转载请注明出处第二段扩展
代码里面确实没有 NSRunloop 这个玩意儿,我们接着做第 3 次改进。
这次我们的目的是把其中动态部分抽象出来。
1interface MyNSTimer : NSObject 2 3 id target 4 SEL action 5 float interval 6 CFAbsoluteTime lasttime 7 8- voidinvoke 9end1011implementation MyNSTimer12- voidinvoke1314 if target respondsToSelector:action 15 target performSelector:action withObject:nil16 1718end 1interface MyNSRunloop : NSObject 2 3 NSMutableArray timerQueue 4 5- voidaddTimer:MyNSTimert 6- voidexecuteOnce 7end 8 9implementation MyNSRunloop10.