概述
5月份马上就要结束了,这个月的iOS
学习计划就要告一段落了。这是最后一篇总结,主要是将Objective-C
语法进行了一个比较全面的总结。以后还是会继续专注于Android
,当然偶尔会学习下iOS
,防止掌握的知识遗忘,好不容易学习这么久。
语法篇
1.Object-对象
1.1 对象创建
Party *partyInstance = [Party alloc];
[partyInstance init];
对象创建一般需要调用alloc
和init
两个方法。alloc
为对象分配内存,然后此时对象并没有初始化,所以不能正常工作。init
方法调用后,对象被初始化,此后才可以正常工作。
1.2 发送消息
发送消息,即我们所说的调用方法。发送一个消息,包含如下3个要素:
- receiver 指向对象的指针
- selector 对象的方法
- arguments 对象的方法对应的参数
其关系如下图所示:
1.3 对象销毁
partyInstance = nil;
通常,要销毁某个对象,只需要将该对象设置为nil
。值得注意的是,iOS
中可以给空对象发送消息,因此不需要做判空操作。
1.4 几个常见对象
如下为Array的继承关系图:
其中NSObject
为所有类的父类。
(1) NSString
NSString
不可变字符串NSMutableString
可变字符串,继承自NSString
字符串表示:
NSString *myString = @"Hello, World!";
字符串内容,以@
符号开头。
格式化输出:
int a = 1;
float b = 2.5;
char c = 'A';
NSString *d = "Hello";
NSLog(@"Integer: %d, Float: %f, Char: %c, String: %@", a, b, c, d);
字符串类型以%@
表示。
(2) NSArray
- NSArray:不可变数组
- NSMutableArray:可变数组,继承自NSArray
数组创建:
NSMutableArray *items = [[NSMutableArray alloc] init];
[items addObject:@"One"];
[items addObject:@"Two"];
[items addObject:@"Three"];
[items insertObject:@"Zero" atIndex:0];
items = nil;
数组遍历:
for (int i = 0; i < [items count]; i++) {
NSString *item = [items objectAtIndex:i];
NSLog(@"%@", item);
}
for(NSString *item in items) {
NSLog(@"%@", item);
}
关于添加空值:
NSArray
或NSMutableArray
不允许添加nil
,但可以使用NSNull
。
[items addObject:[NSNull null]];
1.5 关于类名
Objective-C
没有命名空间的概念,而是通过使用类名前缀进行区分。前缀通常根据应用或公司名称来定义,一般2-3个大写英文字符。
2.实例变量
实例变量创建
//BNRItem.h
#import <Foundation/Foundation.h>
@interface BNRItem : NSObject
{
//以下划线开头
NSString *_itemName;
NSString *_serialNumber;
int _valueInDollars;
NSDate *_dateCreated;
}
- (void)setItemName:(NSString *)str;
- (NSString *)itemName;
- (void)setSerialNumber:(NSString *)str;
- (NSString *)serialNumber;
- (void)setValueInDollars:(int)v;
- (int)valueInDollars;
@end
//BNRItem.m
#import "BNRItem.h";
@implementation BNRItem
- (void)setItemName:(NSString *)str
{
_itemName = str;
}
- (NSString *)itemName
{
return _itemName;
}
- (void)setSerialNumber:(NSString *)str
{
_serialNumber = str;
}
- (NSString *)serialNumber
{
return _serialNumber;
}
- (void)setValueInDollars:(int)v
{
_valueInDollars = v;
}
- (int)valueInDollars
{
return _valueInDollars;
}
实例变量访问
//发送消息访问
BNRItem *item = [[BNRItem alloc] init];
[item setItemName:@"Red Sofa"];
[item setSerialNumber:@"A1B2C"];
[item setValueInDollars:100];
[item itemName];
[item serialNumber];
[item valueInDollars];
[item dateCreated];
//点号访问
item.itemName = @"Red Sofa";
item.serialNumber = @"A1B2C";
item.valueInDollars = 100;
NSLog(@"%@ %@ %@ %@", item.itemName, item.serialNumber, item.valueInDollars);
3. Property-属性
如下为不声明属性变量和声明属性变量的等价对比图。
示例代码如下:
//BNRItem.h
@interface BNRItem : NSObject
@property BNRItem *containedItem;
@property BNRItem *container;
@property NSString *itemName;
@property NSString *serialNumber;
@property int valueInDollars;
@property NSDate *dateCreated;
@end
当我们使用@property
声明属性变量时,实际上意味着:实例变量和getter/setter方法的声明以及getter/setter方法的实现。
3.1 属性修饰符
@property (nonatomic, readwrite, strong) NSString *itemName;`
括号括起来的部分就是属性修饰符。
总共有3类属性修饰符:多线程类型、读写类型、内存管理类型。
(1) 多线程修饰符
nonatomic
非原子的atomic
原子的。适用于多线程,默认类型
(2) 读写修饰符
readwrite
可读写。默认类型readonly
只读
readwrite
意味着系统实现了setter
和getter
方法。readonly
意味着只实现getter
方法。
(3)内存管理修饰符
strong
强引用weak
弱引用。避免循环强引用出现内存泄露问题copy
拷贝unsafe_unretained
关于copy
:
当属性指向类的实例,而该类具有可变子类(如NSString
/NSMutableString
,NSArray
/NSMutableArray
)时,此时建议用copy
修饰符。
为什么对于NSString
和NSArray
使用copy
更安全呢?因为若不使用copy
,当属性指向的可变对象同时被其他外部对象引用时,外部对象可能修改该可变对象的值,从而会影响到当前属性。而使用copy
并不直接引用到可变对象,只是对可变对象的值做了一个拷贝,指向的是一个新对象。
@property (nonatomic, copy) NSString *itemName;
@property (nonatomic, copy) NSString *serailNumber;
//如上copy修饰意味着setter里的实现是:
- (void)setItemName:(NSString *)itemName
{
_itemName = [itemName copy];
}
关于unsafe_unretained
:
使用unsafe_unretained
修饰符修饰对象时,若对象被销毁,则该对象不会自动设置为`nil`,这是和weak
修饰符不同的地方。
unsafe_unretained
是非对象属性(如基本数据类型)的默认修饰符。
对象属性以上4种都可以使用,默认是strong
,一般都会显示声明,因为默认类型可能会变化。
由于定义@property
属性时,系统是会生成默认getter/setter
的。如果不想使用默认的,则可以进行复写如果只复写setter/getter中的某一个方法,则系统不会生成被复写了的方法,未被复写的方法还是会生成的。另外值得注意的是,如果setter/getter均被复写,则系统不会生成相应方法且不会创建相应的实例变量,如果你想使用对应的实例变量,需要手动声明。
3.2 关于@synthesize
:
默认情况下, @property
声明的属性也是@synthesize
的。
正是由于@synthesize
的存在,使得属性变量能够自动产生对应的实例变量和setter/getter方法以及实现相应的setter/getter方法。
4.方法
- 类方法:创建类的新实例或获取类的全局属性,不操作对象或实例变量。以
+
开头 - 实例方法:操作类的特定实例。以
-
开头
4.1 实例方法
初始化方法:
//BNRItem.h
@interface BNRItem
- (instancetype)initWithItemName:(NSString *)name
valueInDollars:(int)value
serialNumber:(NSString *)sNumber;
- (instancetype)initWithItemName:(NSString *)name;
@end
相关概念:
(1)instancetype
方法返回类型,通常用于init
初始化相关方法的返回类型。
返回类型不写成BNRItem *, 是因为如果BNRItem有子类时,其子类方法的返回类型应该是子类类型,又由于子类继承了BNRItem相关方法,然而Objective-C
不允许一个类中存在两个消息的方法名相同,而参数或返回类型不同的情况。
(2)id
当不确定对象类型时,可以使用类型id。可用于修饰变量、方法参数和返回值。
(3)self
对象自身
(4)super
父类。通常发送消息给某个对象时,会先从该对象查找对应方法,如果没找到会继续从父类中查找,一直到NSObject,直到查到为止。
4.2 类方法
+ (instancetype)randomItem
{
NSArray *randomAdjectiveList = @[@"Fluffy", @"Rusty", @"Shiny"];
NSArray *randomNounList = @[@"Bear", @"Spork", @"Mac"];
NSInteger adjectiveIndex = arc4random() % [randomAdjectiveList count];
NSInteger nounIndex = arc4random() % [randomNounList count];
NSString *randomName = [NSString stringWithFormat:@"%@ %@",
[randomAdjectiveList objectAtIndex:adjectiveIndex],
[randomNounList objectAtIndex:nounIndex]];
int randomValue = arc4random() % 100;
NSString *randomSerialNumber = [NSString stringWithFormat:@"%c%c%c%c%c",
'0' + arc4random() % 10,
'A' + arc4random() % 26,
'0' + arc4random() % 10,
'A' + arc4random() % 26,
'0' + arc4random() % 10];
BNRItem *newItem = [[self alloc] initWithItemName:randomName
valueInDollars:randomValue
serialNumber:randomSerialNumber];
return newItem;
}
类方法的实现中,应该使用self
,而不能使用类名本身,以便子类可以调用该类方法。
4.3 异常处理
很多语言使用try catch
捕获异常,虽然Objective-C
也具备这个能力,然而一般不这么使用。Apple
认为,异常属于代码错误,应该在编码阶段就解决,而不是在运行阶段处理异常。
5.内存管理
内存管理,从大的方面来说,主要指栈和堆上的内存管理。
5.1 栈内存
当一个方法执行的时候,内存分配在Stack
上。假设有如下调用关系: `main() ->randomItem -> alloc`,当我们开始执行main()
方法时,会从栈底到栈顶依次给 main()及其本地变量、randomItem方法及其本地变量、alloc方法及其本地变量 分配内存。当方法执行完成时,会从栈顶到栈底依次出栈。
5.2 堆内存
所有Objective-C
中的对象,内存分配都在Heap
上。通常使用指针存储对象在堆上的内存地址。
5.3 对象引用
(1)对象持有
当一个对象不被其他对象持有的时候,该对象将被销毁。通常分如下几种情况:
- 将指向该对象的指针指向其他对象
- 将指向该对象的指针设置为nil
- 引用该对象的对象销毁
- 对象从集合中移除
(2)强引用和弱引用
弱引用存在,是为了解决强引用的循环依赖,导致内存没法释放的问题。通常情况下,两个实例之间的引用存在一个包含与被包含关系。一般将具备包含关系的实例设置为弱引用。
先看一段代码:
//BNRItem.h
@interface BNRItem : NSObject
{
BNRItem *_containedItem;
BNRItem *_container;
}
- (void)setContainedItem:(BNRItem *)item;
- (BNRItem *)containedItem;
- (void)setContainer:(BNRItem *)item;
- (BNRItem *)container;
@end
//BNRItem.m
@implementation BNRItem
- (void)setContainedItem:(BNRItem *)item
{
_containedItem = item;
item.container = self;
}
//main.m
BNRItem *backpack = [[BNRItem alloc] initWithItemName:@"Backpack"];
BNRItem *calculator = [[BNRItem alloc] initWithItemName:@"Calculator"];
back.containedItem = calculator;
backpack = nil;
calculator = nil;
以上代码,当main()
方法执行完时,由于backpack
和calculator
指向的对象互相强引用,因此无法释放内存,会导致内存泄露。解决办法是将_container
设置为弱引用。
__weak BNRItem *_container;
5.4 关于autoreleasepool
和ARC
ARC
即Automatic Reference Counting
,自动引用计数。是相对于MRC
(Manual Reference Counting,手动引用计数)而言的。
若使用autoreleasepool
关键字括起来,当括号里方法执行完时,实例对象会自动释放。
@autoreleasepool {
BNRItem *item = [BNRItem someItem];
}
iOS
使用ARC
和autoreleasepool
机制实现内存管理。
参考
iOS Programming:The Big Nerd Ranch Guide