欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

IOS开发(43)之10个迷惑新手的Cocoa&Objective-c开发问题

程序员文章站 2023-11-07 11:49:04
首先请谅解我可能使用很多英文,毕竟英文资料将来会是你的主要资料来源。 这篇博客将描述一些我见到的众多cocoa开发新手遇到的问题和障碍。在你继续深入学习macos之前,请停下脚步...

首先请谅解我可能使用很多英文,毕竟英文资料将来会是你的主要资料来源。

这篇博客将描述一些我见到的众多cocoa开发新手遇到的问题和障碍。在你继续深入学习macos之前,请停下脚步弄清这些问题。如果你是新手,这个教程不要希望一次能看的非常透彻,学一定阶段反回来再看看又会有新的体会的。


1. language background
首先c, c++语言背景,必须。 很多人问:”没有任何语言基础,我不想学c直接学objective-c。“ 这里我简单说几句,objc是c的超集,也就是说大部分objc代码其实是c、而且众多开源代码是c,c++写成的。你不学好c在unix世界里只能是个二流开发者!也许说得过于严厉,不过自己斟酌把。

接着english,必须。 苹果不会把它们文档都写成中文的。“什么,有人翻译?” 等有人闲着翻译出来了的时候,大家都已经卖了很多软件了。你也是跟着人家屁股后面做开发。


2. runtime(运行时)
objective-c是动态语言, 很多新手或者开发人员常常被runtime这个东西所迷惑。而恰恰这是一个非常重要的概念。我可以这么问:“如果让你(设计、)实现一个计算机语言,你要如何下手?” 很少程序员这么思考过。但是这么一问,就会强迫你从更高层次思考1以前的问题了。 注意我这句话‘设计’括起来了,稍微次要点,关键是实现。

我把实现分成3种不同的层次:

第一种是传统的面向过程的语言开发,例如。实现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的这些语句:

 
 id obj=self; if ([obj respondstoselector:@selector(function1:)) { } if ([obj iskindofclass:[nsarray class]] ) { } if ([obj conformstoprotocol:@protocol(myprotocol)]) { } if ([[obj class] issubclassofclass:[nsarray class]]) { } [obj somenonexistfunction]; 

看似很简单的语句,但是为了让语言实现这个能力,语言开发者要付出很多努力实现runtime环境。这里运行时环境处理了弱类型、函数存在检查工作。runtime会检测注册列表里是否存在对应的函数,类型是否正确,最后确定下来正确的函数地址,再进行保存寄存器状态,压栈,函数调用等等实际的操作。

 
 id knife=[knife grateknife]; nsarray *monsterlist=[nsarray array]; [monsterlist makeobjectsperformselector:@selector(killmonster:) withobject:knife]; 

用c,c++完成这个功能还是比较非常麻烦的,但是动态语言处理却非常简单并且这些语句让objc语言更加intuitive。

在objc中针对对象的函数调用将不再是普通的函数调用,[obj function1with:var1]; 这样的函数调用将被运行时环境转换成objc_msgsend(target, @selector(function1with:), var1);。objc的runtime环境是开源的,所以我们可以拿出一下实现做简单介绍,可以看到objc_msgsend由汇编语言实现,我们甚至不必代码,只需查看注释就可以了解,运行时环境在函数调用前做了比较全面的安全检查,已确保动态语言函数调用不会导致程序crash。对于希望深入学习的朋友可以自行下载objc-runtime源代码来阅读,这里就不再深入讲解。
 
 /******************************************************************** * id objc_msgsend(id self, * sel op, * ...) * * on entry: a1 is the message receiver, * a2 is the selector ********************************************************************/   entry objc_msgsend # check whether receiver is nil  teq a1, #0  moveq a2, #0  bxeq lr  # save registers and load receiver's class for cachelookup  stmfd sp!, {a4,v1-v3}  ldr v1, [a1, #isa]  # receiver is non-nil: search the cache  cachelookup a2, lmsgsendcachemiss  # cache hit (imp in ip) - prep for forwarding, restore registers and call  teq v1, v1 /* set nonstret (eq) */  ldmfd sp!, {a4,v1-v3}  bx ip  # cache miss: go search the method lists lmsgsendcachemiss:  ldmfd sp!, {a4,v1-v3}  b _objc_msgsend_uncached  lmsgsendexit:  end_entry objc_msgsend    .text  .align 2 _objc_msgsend_uncached:  # push stack frame  stmfd sp!, {a1-a4,r7,lr}  add r7, sp, #16  save_vfp  # load class and selector  ldr a1, [a1, #isa] /* class = receiver->isa */ # move a2, a2 /* selector already in a2 */  # do the lookup  mi_call_external(__class_lookupmethodandloadcache)  move ip, a1  # prep for forwarding, pop stack frame and call imp  teq v1, v1 /* set nonstret (eq) */  restore_vfp  ldmfd sp!, {a1-a4,r7,lr}  bx ip 

现在说一下runtime的负面影响:

 1. 关于执行效率问题。 “静态语言执行效率要比动态语言高”,这句没错。因为一部分cpu计算损耗在了runtime过程中,而从上面的汇编代码也可以看出,大概损耗在哪些地方。而静态语言生成的机器指令更简洁。正因为知道这个原因,所以开发语言的人付出很大一部分努力为了保持runtime小巧上。所以objecitve-c是c的超集+一个小巧的runtime环境。 但是,换句话说,从算法角度考虑,这点复杂度不算差别的,big o notation结果不会有差别。( it’s not log(n) vs n2 )

2. 另外一个就是安全性。动态语言由于运行时环境的需求,会保留一些级别的程序结构。这样就给带来的方便之门。一个现成的说明就是,java,大家都知道java运行在jre上面。这就是典型的runtime例子。它的执行文件.class全部可以反编译回近似源代码。所以这里的额外提示就是如果你需要写和安全有关的代码,离objc远点,直接用c去。

简单理解:“runtime is everything between your each function call.”

但是大家要明白,第二点我提到runtime并不只是因为它带来了这些简便的语言特性。而是这些简单的语言特性,在实际运用中需要你从完全不同的角度考虑和解决问题。只是计算1+1,很多语言都是一样的,但是随着问题的复杂,项目的增长,静态语言和动态语言就会演化出完全不同的风景。


3. thread
“thread synchronization another notorious trouble!”

记得上学时候学操作这门课,里面都会有专门一章介绍任务调度和生产者消费者的问题。 这就是为今后使用进程、线程开发打基础。概念很简单,但难点在synchronization(同步),因为死锁检测算法不是100%有效,否则就根本没有死锁这个说法了。另一个原因是往往这类错误很隐晦,静态分析很难找到。同时多线程开发抽象度较高需要经验去把握。