Objective-C
OC 是 C 的超集,所以在OC中既有C语言的语法特性,也有扩展出来的语法特性。有时候可以互通,大部分时候不能互通。例如 C语言中的 char
类型,在OC中极少用到,C语言中的CFImageRef完全不能在扩展语法部分使用,必须使用bridge。
从语言层面上,iOS SDK 的实现分为几层,大致是 CGFoudation -> Foundation -> Cocoa Touch (大部分时候是UIKit)。CGFoudnation部分是C语言编写,另两部分则是OC开发。
OC在编译阶段被CLang翻译成C语言。C 到 OC 的实现大量使用了 struct 类型。
数据类型
基本数据类型
- 从C语言过度来的:
int
long
double
等 - Foundation扩展的:
NSInterger
等 - UIKit 扩展的:CGFloat 等
对象类型
- NSObject
- NSNumber
- NSString
- NSArray
- NSDictionary
- NSNull
还有很多SDK内置的类型,和自定义的class类型。
对象类型的基类有两个,NSObject, NSProxy。其中NSObject是最常见最常用的。
变量
double width = 100;
CGFloat width = 100;
NSString *str = @"123";
需要注意的:
- OC中没有类型推导,因此没有
let a = 1;
这种定义形式,定义变量一定要声明类型。 - C和C++都是经典的C系语言,最让人望而却步的就是指针类型。因此,在声明对象类型时,请使用 * 表示后面的变量是一个指针类型。请和基础数据类型做区别。
''
和""
在C语言中表示char 和 char * 类型,因此,NSString类型的字面量要使用 @ 修饰,表示这是OC中的对象类型。同理,NSArray 和 NSDictionary 都是这样。
函数
作为C的超集,即可以使用C函数的定义,也可以使用扩展部分的定义。
需要注意的是,这里讨论扩展的函数定义,一定是定义在class 中,作为实例方法或者静态方法呈现,对于使用了OC对象类型的C函数定义依然认为是C函数。
无参数的函数定义
int main() {
// some code ...
}
- (int)main {
}
单参数的函数定义
int length(NSString *str) {
// some code ...
}
- (int)length:(NSString *)str {
// some code ...
}
多参数的函数定义
int sum(int a, int b) {
// some code ...
}
- (int)sumWithA:(int)a withB:(int)b {
// some code ...
}
多参数方法中,习惯性使用with作为每段函数签名的开始,嫌丑的话可以自己发挥,保持优良的可读性即可。
函数的调用
C 方法的调用和 Dart 语言十分相似,不再赘述。
已上面三个方法为例:
// main
int value = [someObjectInstance main];
// length
int length = [someObjectInstance length:@"angryli"];
// sum
int sum = [someObjectInstance sumWithA:1 withB:2];
类
C语言没有class这一说。OC则在其基础上扩展了OOP功能,这里会生成一个面试题:怎么使用C语言实现面向对象?
OC基于C,同事也带来了头文件和实现文件的概念。在OC中,头文件以.h 后缀,实现文件已.m 后缀。理论上其他类只能访问头文件中声明的属性和方法,但是利用运行时(类似于映射)方法,可以访问到.m文件中的私有方法,当然你这是有风险的,一旦.m移除该方法,外部调用是无从得知的。
先看一个例子:
// Person.h
@interface Person : NSObject
@property (strong, nonatomic) NSString *name;
- (void)sayHello;
+ (void)cry;
@end
// Person.m
@interface Person () {
int _a;
}
@property(assign, nonatomic) int age;
@end
@implementation Person
- (void)sayHello {
[self _printHello];
}
- (void)_printHello {
NSLog(@"Hello %@, my age is %d", self.name, self.age);
}
+ (void)cry:(int)a b:(int)b {
NSLog(@"Crazy!");
}
- (NSString *)description {
return @"I am Person class";
}
@end
扩展与分类
@interface Person ()
@end
@interface Person (Run)
- (void)run;
@end
@implementation Person (Run)
- (void)run {
NSLog(@"我跑了 3 千米");
}
@end
这里有几个概念:实例变量,实例属性,实例方法,类方法,class定义,class实现,class扩展,class分类,需要大家逐一理解。
需要注意的是:
- 类似Java中的get和set方法,貌似在15还是13年的时候,OC 2.0 升级了,升级了 @property, 利用编译器特性,在编译阶段自动添加 set、get 方法和一个带下划线的实例变量,例如 age 会生成
- (int)age; -(void)setAge(int)age { _age = age; }
和int _age;
- 扩展中定义的属性会自动生成set 和 get 方法,扩展中声明的属性则不会补全。
- 扩展中出现同名方法,后加载的会覆盖之前的方法
协议和代理
@protocol FinishDelegate : NSObject
// @required 可省略
- (void)didFinish;
@optional
- (void)didCancel;
@end
@interface DetectViewController() <FinishDelegate>
@end
@implementation DetectViewController
// 必须方法如果不实现,则会出警告。如果坚持不实现,也不会卡编译,运行时会crash。
- (void)didFinish {
NSLog(@"完成");
}
// 可选方法可以不实现,无论是否实现,调用方在调用该方法时,正常代码都需要判断是否实现 responseToSelector:
- (void)didCancel {
NSLog(@"取消");
}
- (void)_showScanController {
ScanViewController *vc = [[ScanViewController alloc] init];
vc.delegate = self;
[self showDetailController:vc sender:self];
}
@end
@interface ScanViewController : UIViewController
@property (weak, nonatomic) id<FinishDelegate> delegate;
@end
@implementation ScanViewController
// after some actions, you will become finish or cancel state
- (void)_scanSuccess {
if (self.delegate && [self.delegate responseToSelector:@selector(didFinish)]) {
[self.delegate didFinish];
}
}
- (void)_scanCancel {
if (self.delegate && [self.delegate responseToSelector:@selector(didCancel)]) {
[self.delegate didCancel];
}
}
@end
最好是无论是什么方法,都判断是否实现。
内存管理
三种基本的GC方式:标记清除、复制收集和引用计数。
OC选择使用引用计数的方式管理内存。而且是手动的。在iOS 6之后出现了ARC,自动引用计数,也意味着开发者在使用OC对象的时候无需手动申请和释放内存空间,只要按照一定的规范编写,编译器会在编译阶段自动添加 alloc(分配) 和 release(释放) 代码。
引用计数最大缺点就是循环引用,相同的代码在Java或者Dart中没有问题,在OC中则大大的有问题。例如下面的代码:
void initState {
_textController.listen(() {
print(_textController.text);
});
}
我们经常会这么去监听一个输入框的输入并打印其内容,这在 dart 中十分常见,也没有什么问题。但在OC中,类似代码则已经造成了非常常见一种的循环引用了。
_textController
持有 listen
闭包,listen
闭包又捕获并持有_textController
,他们的引用计数都 >=1,因此谁都不会被释放,如果没有其他代码断开他们之间任一的引用关系,这段会成为永远无法释放的内存。