给Android程序员看的Objective-C入门

2,560 阅读7分钟

最近两年因为公司业务需求, 我从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
stringStringNSStringstring
intint; Integerint; NSIntegernumber
mapHashMapNSDictionaryObject; Map
byte 数组byte[]NSDataArrayBuffer

数字

因为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时碰到的一些基本知识点,分享出来, 希望能帮助更多有需要的人.