android-iOS区别记(上)——从语言的特性说开去
这篇文章写起来挺艰难的。因为网上很少有人提到过这种对于android和 iOS的横向比较。因缘际会之下博主同时接触了这两种语言这两个平台。也把我的一些体会分享给大家。
语言的特性
1. 划分
首先,OC是编译型语言,Java是混合型语言;OC是动态语言,Java是静态语言。它们又都是面向对象的静态类型语言。前者的区别造成了它们在代码形式风格上的诸多不同;后者又使它们在写法上诸多类似。 这里补充一点:动态类型语言和动态语言是两个概念。动态类型语言指的是运行期间才去做数据类型检查的语言,类似一些脚本语言,重点是数据类型。而动态语言指的是运行期间可以改变代码结构的语言,比如Object-C、C#、JavaScript、PHP;反之则如Java、C、C++。所以在这一点上,Java和OC显露出了分歧。
Java有虚拟机JVM的概念。
关于Java 运行时的逻辑图:
注:Java先生成字节码,再在Java虚拟机中解释执行。所以是混合型语言。
而编译型的OC则相对简单:
Objective-C → Compiler-gcc → .o文件 → linker装载器 → executable可执行文件
其中,编译是把源代码编译成机器码,链接是把各个模块的机器码和依赖库串连起来生成可执行文件。
2. 继承vs组合
oc天然用组合来组织类与类之间的关系(比如category)
java却常用面向对象,继承的方式来衍生出更多的类。
假如你想在全局的activity里做点什么,你会多重继承。拿百姓网主站app举例:
BXMainActivity→ BaixingBaseActivity→BaseActivity
这么一个继承关系。
假如你想在所有的Fragment中定制UI特有的表现形式,比如全局的底部菜单栏。Java会定义一个BaseFragment来做这件事,以后其他多个Fragment都需要做的事情也会放在Base类里。每当新写一个fragment,都需要先继承base类。这样会导致继承关系复杂,并且最后把很多不同的事全扔到Base类里做。Base类里的代码变得不可看。
并且,一个新来的同学可能不知道它们的继承关系;一个基于敏捷开发的小demo想要合进来也变得狠困难。
OC有一种天然的组合方式叫category。 如果你想实现上述的功能,只需对controller派生一个UIViewController+BottomMenu,在需要的页面去import它。通过这样的方式可以将不同功能打散到特定的category里。
UIViewController.m, UIViewController+BottomMenu.m
这么个关系。
这里对仅了解Java的朋友说明一点:OC的category不是只能扩展自定义的类,而是可以改变cocoa底层的框架类,甚至是更底层的OC类(相当于android类和Java类)。 OC之所以能做到这一点,是因为它在编译阶段将include的头文件代码写入了进来,而C系列的语言是动态不定长的,既有类里的方法可以被改变;而Java为了安全、封闭等考虑,不让开发者写完一个类再去修改里面的内容。
Java想要做到动态改原生Activity,只能使用一种Hack的方式,即反射。
3. runtime VS reflection
想要要更深入地理解这种区别,不得不提到runtime和reflection。
iOS中的运行时编程,类似Java的反射。
Java是一门静态语言,类和方法都有严格的public, private之分。而反射机制却可以实现动态性,获取类的私有方法等。
3.1 相同点
都可以实现的功能:获取类信息、属性设置获取、类的动态加载、方法的动态调用等。
ios中相关方法使用:
类的动态加载:NSClassFromString(@“className”)
方法的动态调用:NSSelectorFromString(@“doSomethingMethod”)
……
Java反射API的第一个主要作用是获取程序在运行时刻的内部结构。
通过getConstructor、getField和getMethod
这三个方法可以获取到java.lang.Class类
的对象中的构造方法、域和方法。(注:java.lang.Class类
是所有Java类的父类)
3.2 不同点
OC能动态得给class添加类和方法,Java则不行。例如:
平台的特性
ARC与GC的异同
-
GC是什么? GC,garbage collection。 Java由GC来负责回收内存。
-
ARC是什么? ARC是一种OC对象的自动管理机制,由LLVM编译器来保证(这件事情在预编译的过程中完成最好不过)。编译器在编译期间,动态地帮你添加retain/release/autorelease。同时,还引入了新的运行时特性:弱指针,性能优化等。
-
difference
Java 中的 GC 与 OC 中的 ARC 都有计数器的概念。 其实,ARC借鉴了现代语言中的引用计数。当时看的时候在想Java有非常完善的引用计数机制。但ARC又不是GC,它不扫描堆、不暂停进程运行,没有不确定的内存释放。所以其实你可以理解ARC是狠轻的对象管理,帮你添加了一些代码,是编译器提供的机制,而不是GC这种复杂的运行时机制。 -
我们可以怎样在android中利用GC机制? 首先,不同厂商的JVM对于GC的调用策略不同,但我们在应用层不需要关心这些。我们只需要知道,GC过程与对象的引用类型是严重相关的,我们来看看Java对引用的分类Strong reference, SoftReference, WeakReference, PhatomReference。如下表:
级别 | 回收时机 | 用途 | 生存时间 |
---|---|---|---|
强 | 从来不会 | 对象的一般状态 | JVM停止运行时终止 |
软 | 内存不足时 | 对象的二级高速缓存 | 内存不足时终止 |
弱 | 垃圾回收时 | 对象的一级高速缓存 | gc运行后终止 |
虚 | 垃圾回收时 | 联合ReferenceQueue跟踪垃圾回收器的活动 | gc运行后终止 |
对于这一点,常体现在对于Bitmap的引用上。下面再展开阐述。这就引出了一个非常重要的话题:性能优化。
关于性能优化
android面临的内存问题要比ios的严峻很多。我们来看一下对比:
iOS8/iPhone6/646MB
Android 4.1/Mi2/96MB
WOW!
而随着android手机越做越好,努力地去满足消费市场;屏幕越做越大,内存等硬件参数也越来越好看。这个问题有没有好转呢?让我们来看下面这幅图:
这幅图第三列中的 16→24→48
代表的是android框架对每个进程的内存限制。我们可以看到虽然进程的内存限制在逐年增大,然而渲染每一屏所占的图像大小也随着分辨率的增大而不断增大。即使渲染速度变快了,内存却依旧容易溢出。
尽管苹果的消费者对于摄像头、分辨率的呼声很高。苹果却并没有一味地去迎合消费市场。:)
言归正传,回到这样一个场景:假如我们有很多个经常需要用到的bitmap展示,最佳实践应该是怎样的?怎样利用好Java的GC原理?
如果每次都去读取图片,由于读取文件需要硬件操作,速度较慢,会导致性能较低。所以我们考虑将图片缓存起来,需要的时候直接从内存中读取。但是,由于图片占用内存空间比较大,缓存很多图片需要很多的内存,就可能比较容易发生OutOfMemory异常。这时,我们可以考虑使用软/弱引用技术来避免这个问题发生。以下就是高速缓冲器的雏形:
首先定义一个HashMap,保存软引用对象。
private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();
再来定义一个方法,保存Bitmap的软引用到HashMap。
使用软引用以后,在OutOfMemory异常发生之前,这些缓存的图片资源的内存空间可以被释放掉。从而避免内存达到上限,避免Crash的发生。
如果只是想避免OutOfMemory异常的发生,则可以使用软引用。如果对于应用的性能更在意,想尽快回收一些占用内存比较大的对象,则可以使用弱引用。
另外可以根据对象是否经常使用来判断选择软引用还是弱引用。如果该对象可能会经常使用的,就尽量用软引用。如果该对象不被使用的可能性更大些,就可以使用弱引用。
总而言之,堆内存中的长生命周期的对象持有短生命周期对象的强/软引用,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是Java中内存泄露的根本原因。
关于android底下的内存泄露以及如何更好得利用GC的工作原理,让GC运行更加有效率还有太多可说的。这里就不展开了,留到 《下》 再来一一细述,免得iOS程序员看得不耐烦:)
ARC下的内存泄露
在我初接触苹果开发的时候,碰到一个很有趣的现象。大多数iOS程序员在苹果近年推出 ARC 机制以后,认为程序不再会内存泄露,可以高枕无忧了。殊不知Java早有类似的、比它更高级的机制,然而Java程序员却无时无刻不在关心android内存泄露的问题。这种现象一部分来自于人的特殊心理,另一部分来自于并未深刻理解ARC或者内存泄露,最后一部分得益于苹果系统并不像android那么容易发生内存泄露。
事实上,除了前文提到的关于 Java 内存泄露的诸多可能;OC不仅包含了这些,并且只可能更多。
举一个OC中特有的例子:
1. performSelector 系列
performSelector 顾名思义即在运行时执行一个 selector,最简单的方法如下:
- (id)performSelector:(SEL)selector;
这种调用 selector 的方法和直接调用 selector 基本等效,执行效果相同:
[object methodName];
[object performSelector:@selector(methodName)];
但 performSelector 相比直接调用更加灵活
这段代码就相当于在动态之上再动态绑定。在 ARC 下编译这段代码,编译器会发出警告
warning: performSelector may cause a leak because its selector is unknow [-Warc-performSelector-leak]
正是由于动态,编译器不知道即将调用的 selector 是什么,不了解方法签名和返回值,甚至是否有返回值都不知道,所以编译器无法用 ARC 的内存管理规则来判断返回值是否应该释放。因此,ARC 采用了比较谨慎的做法,不添加释放操作,即在方法返回对象时就可能持有该对象,从而可能导致内存泄露。
以本段代码为例,前两种情况(newObject, copy)都需要再次释放,而第三种情况不需要。这种泄露隐藏得如此之深,以至于使用 static analyzer 都很难检测到。如果把代码的最后一行改成:
[object performSelector:selector];
不创建一个返回值变量测试分析,简直难以想象这里居然会出现内存问题。所以如果你使用的 selector 有返回值,一定要处理掉。
以上只是一个小小的例子,类似的还不少。
所以,就 语言层面 来说,OC因为其动态的特性,比如一个selector(相当于method)究竟存不存在,在编译阶段我们不得而知;就 内存管理 来说,OC没有像Java一样时刻不停监控内存、回收内存的管理器;就 人员角度 来说,自ARC以后讨论OC内存泄露的技术文章少之又少。 不管如何看,OC都比Java更可能发生内存泄露。
下文预告:
关于性能优化,可讲的实在太多。我们在下一篇文章中再徐徐展开。
- 垃圾回收的四个触发时机
- Java编程建议
- 安卓和iOS不同的优化策略
- 工具
- 支持的图片格式
- 支持的语音格式
本文系冰洁原创,遵循 署名-非商业性知识共享进行许可. 转载请在文章开头显眼处注明注明作者和出处 【冰洁】http://www.bingjie.me
人生在于体验,体验下打赏吧:)