最近两年因为公司业务需求, 我从Android开发转成了也涉猎前端的工作, 主要在做一些Android与ReactNative了. 但是众所周知的是, React Native其实是一个严重先天不足的跨平台框架. 简言之, 就是RN仅仅是一个UI框架, facebook在创建之初就是想使用yoga(跨平台的UI库)来包两个平台的UI代码. 而一旦是非UI的内容, 比如说sensor, network, security, permission, ..., 那RN就很捉急了.
当然RN并不是完全没办法, 它的做法就是分而治之, 把这些东西下放到两个native平台去做, 然后把结果通过bridge告诉js端就行了. 这一点, 其实也就意味着, 对于稍深一些的RN功能, 程序员得了解js, android, iOS平台的开发. 比如说, 我们最近要做一个本地的server, 运行在手机上. 这个明显就是非UI的内容, 所以我得在android与iOS平台上分别写代码. (其实有一个react-native-static-server的库可以参照, 但我们的需求还要读到内容后解密, 所以更复杂, 需要我们自己定制)
这样的native开发的需求, 就引发了我们js开发, android开发, 得去了解些iOS的知识. 另外, 因为RN中的iOS代码是objective-C, 所以我们学习的语言就不是swift, 而是更古老(更反人类)的OC了.
Swift 还是 OC?
上面讲过, 对于RN开发来说, 一般使用OC较多. 特别是一些特别需求, 如加密, Swift是2020年2月才有了crypto库, 以前都没有的. 这些东东都只好使用OC来开发.
另外, Swift其实底层也在利用OC, 像是selector, 所以即使使用Swift, 你还是得了解一些OC的内容.
最后, iOS开发中一个好处就是它可以直接利用C的库. OC可以方便得利用C的库. 例子有很多个
- 线程中的GCD, 其实使用的就是C语言的GCD库.
- iOS不像java, 不能天生支持zip压缩/解压. 这时OC可以方便利用MiniZip这样的库里的 .h, .c文件.
综上所述, 这篇文章主要还是在介绍OC
OC里的类型
Android (java) | iOS (OC) | js | |
---|---|---|---|
string | String | NSString | string |
int | int; Integer | int; NSInteger | number |
map | HashMap | NSDictionary | Object; Map |
byte 数组 | byte[] | NSData | ArrayBuffer |
数字
因为OC是C语言一系的, 所以那些复杂的数据类型在OC里都是支持的, 如short, unsigned long ... 这些类型在java中没有, 所以多提一句.
另外, 数字的字面值, java中直接使用数字就行, 如int i = 20
; 但在OC中得加一个@, 表示是OC中的值(而不是C中的值), 所以OC中的数字20就是表示为 @20
.
字符串
同理, OC中的String字面值, 也得前面加@. 不然的话, ""是表示C中的字符串, 也就是char[]. 只有@"", 才表示是一个NSString类型, 这是一个类, 而不是char数组了.
经常打印日志所用到的格式化输出则是用 %@
来做占位符. 如:
NSString* name = @"xxx";
NSLog(@"hello %@", name); //=> hello xxx
对象
和java中不一样, java中的对象其实都是指针, 但你不用写明, 因为java本身并没有"指针"的概念.
但在C系语言中, 指针很常见. OC则是显示地认为每个对象都是指向一堆内存的指针, 所以我们得这样定义对象: NSString* name = @"xxx";
.
(注意这里的*
就表示是一个指针)
函数
好吧, 这可能是java/js程序员看起来最反人类的地方了.
先说结论:
- OC中的方法调用是用中括号表示的 (其它大多数语言都是使用小括号的);
- 参数要带参数说明
- 而参数与参数之间用空格表示
再来看例子:
java:
String memo = "memo.txt";
String extension = memo.substring(5);
OC:
NSString* memo = @"memo.txt";
NSString* extension = [memo substring: 5];
要是更多参数, 那就这样: [object method:arg1 arg2Name:arg2 arg3Name:arg3]
还是举例来说明:
/* declaration */ + (NSData *)decryptContent:(NSData *)encrypted withKey:(NSData *)key;
/* usage */ [obj decryptContent: encrypted withKey: key];
/* declaration */ - (void)stop;
/* usage */ [obj stop];
这里的withKey
就是参数名, key
是实参名. 在调用这个decryptContent方法时, 除了第一个参数, 都要带上参数名.
这其实就相当于java中的
obj.decryptContent(byte[] content, byte[] key);
obj.stop();
源文件
java中只有".java", js也只有".js"文件. 但是C系语言都分了头文件与实现文件. C中是 : .h, .c OC中是: .h, .m
其实.h是头文件, 类型于java中的public声明. 所有能被外部文件访问的成员与方法都定义到.h里.
.m就是真正实现. 一些不对外开放的方法成员, 在.m里也能存在.
类
OC中的类声明也得分两部分: interface与implementation, 分别代表定义与实现. 一般interface放到.h里去, 这样其它文件可以调用这个类. 而implementation一般写在.m文件里. 比如说:
// MyClass.h
@interface MyClass: NSObject {
NSString* name; //property
}
-(void)hello:(NSString*)name; //method
@end
注意:
1). 和java不同, 就是你的类是NSObject的子类, 你也得写明. java中若是Object的子类, 就不用写明extends Object也是行的.
2). 类的方法定义, 最前面是+或-.
- +就代表static方法, 以后调用就是
[MyClass func]
- -代表成员方法, 调用时用对象, 而不是类名, 如
[obj func]
3). 方法返回值放到小括号里, 如上面的(void)
.
4). 上面的name是参数. 在调用时就是: [obj hello:@"xx"];
实现体则是:
@implementation MyClass
-(void) hello:(NSString*)name{
NSLog(@"hello, %@", name);
}
@end
即
- interface中只有成员/方法的定义, 没有实现.
- implementation里就是写完方法体, 这个方法到底是干嘛的
category
这个有点类似kotlin中的extension. 即你可以任意扩展某个类. 即使这类是一个系统类, 你也可以扩展.
定义一个category就类似定义一个类, 只不过你不用写super class, 而是用(category名字)
来代替父类名.
下面是一个例子. 普通NSString是没有uuid方法的. 现在我们给NSString新加一个uuid方法, 调用这个新方法就会得到一个随机uuid值.
// NSString+Uuid.h
#import <Foundation/Foundation.h>
@interface NSString (Uuid)
+ (NSString*) uuid;
@end
// NSString+Uuid.m
#import "NSString+Uuid.h"
@implementation NSString (Uuid)
+ (NSString*) uuid {
CFUUIDRef uuidRef = ...;
return uuidString;
}
@end
protocol
protocol就是java中的接口, 即一堆方法定义的集合. 因为都是表示抽象, 所以和java接口类似, protocol在OC中就只有.h文件, 没有.m文件.
@protocol ServerPlugin <NSObject>
@required -(void) serve: (int) id;
@end
// if you want to use the protocol, use it as a generics:
@interface HttpServer: NSObject<ServerPlugin>;
@end
protocol中的注意事项
在java中, 我们可以使用Map<String, Iinterface>. 取出值时就是:
Iinterfae obj = map.get(key);
但在OC中, 是不能使用 某Protocol obj = [map objectForKey:key];
因为Protocol本身根本就不是一个对象, 只是一个抽象概念. 所以正确的写法是:
NSOjbect<某Protocol> plugin1 = [dictionary objectForKey:@"key1"];
内存管理
C中的内存管理是手动的, 即当你malloc申请开辟新内存块后, 你最后也得注意把这内存给free了. OC也是这样的.
但java程序员, 或者说所有程序员, 都会觉得这样好烦, 而且容易忘记free某对象的引用时, 就会内存泄露, 极易出问题. 所以在2011年, apple也引入ARC(Auto Reference Counting), 即自动帮你记录引用数, 并在脱离作用域时帮你release掉引用. 这其实就类似java中的GC. 这样OC开发总算从内存的沼泽中被释放了出来.
当然你也不是100%全自动内存管理了. 当你在处理一些C的库, 或Core Foundation框架时,这些C的部分都没有被ARC覆盖, 所以你仍得注意自己得释放到申请的内存.
总结
以上就是我做为一个Android开发, 入手OC时碰到的一些基本知识点,分享出来, 希望能帮助更多有需要的人.