Objective-C tips

Objective-C tips

nil

下面两种OC语法是等价的:

if (venue == nil) {
    [organizer remindToFindVenueForParty];
}
if (!venue) {  //类似JS的语法,判断对象为空或非空
    [organizer remindToFindVenueForParty];
}

其他语言经常要加非空判断,像JAVA JS都需要,避免出现空指针异常,OC中不需要像下面这样判断,如果对象为nil,OC会直接忽略被调用的方法。

// Is venue non-nil?
if (venue) { //例如JS,判断对象不为空
    [venue sendConfirmation];
}

@

创建字符串对象:

NSString *myString = @"Hello, World!";

NSLog格式化字符串,不同类型使用不同占位符:

int a = 1;
float b = 2.5;
char c = 'A';
NSLog(@"Integer: %d Float: %f Char: %c", a, b, c);

        
NSString *str = @"hello oc";
NSLog(@"print %@", str);

%@代表“a pointer to any object”,当对象被Log时,会发送给给对象description消息,返回字符串。就像java中的toString()方法。

实例变量 set&get方法

在头文件中声明实例变量,ANDROID中实例变量以m开头,OC中实例变量以 _ 开头,约定而已,不是必须的。
get方法不需要以get开头。

#import <Foundation/Foundation.h>

@interface BKItem : NSObject
{
    NSString *_itemName; //*号说明变量是个指针
    NSString *_serialNumber;
    int _valueInDollars;
    NSDate *_dateCreated;
}

//在头文件中声明set get方法,像是java中声明接口的抽象方法
- (void)setItemName:(NSString *)str;
- (NSString *)itemName;

- (void)setSerialNumber:(NSString *)str;
- (NSString *)serialNumber;

- (void)setValueInDollars:(int)v;
- (int)valueInDollars;

- (NSDate *)dateCreated; //只有get方法

@end

类的实现中实现set get方法:

#import "BKItem.h"

@implementation BKItem

- (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;
}

- (NSDate *)dateCreated
{
    return _dateCreated;
}

@end

set get的两种访问语法,使用点语法最终会被编译器转成第一种方法,但是使用点语法更方便。

        BKItem *item = [[BKItem alloc] init];
        [item setItemName:@"Red Sofa"];
        [item setSerialNumber:@"A1B2C"];
        [item setValueInDollars:100];
        
        NSLog(@"%@ %@ %@ %d", [item itemName], [item dateCreated],
              [item serialNumber], [item valueInDollars]);
        
       // 推荐使用下面的点语法 dot syntax:等号左边调用set方法,等号右边调用get方法
        item.itemName=@"Red Sofa";
        item.serialNumber=@"A1B2C";
        item.valueInDollars = 100;
        NSLog(@"%@ %@ %@ %d", item.itemName, item.dateCreated,
              item.serialNumber, item.valueInDollars);

重写方法

重写父类方法,只需在子类的实现文件中重写,头文件中不需要声明,因为被重写的方法已经被父类的头文件声明过了。

Initializers

构造器,类默认只有init一个初始化方法,可以添加自定义的初始化方法。

#import <Foundation/Foundation.h>

@interface BKItem : NSObject
{
    NSString *_itemName; //*号说明变量是个指针
    NSString *_serialNumber;
    int _valueInDollars;
    NSDate *_dateCreated;
}

// 声明自定义的初始化方法
- (instancetype)initWithItemName:(NSString *)name valueInDollars:(int) value serialNumber:(NSString *)sNumber;
- (instancetype)initWithItemName:(NSString *)name;

@end

实现在头文件中声明的初始化方法,通常参数最多的初始化方法是指定初始化方法(designated initializer)
instancetype 关键字是初始化方法的返回类型,谁来调用初始化方法,instancetype 就是谁(an instance of the receiving object)。

#import "BKItem.h"

@implementation BKItem

// Designated initializer (指定的构造器,通常是参数最多的那个init方法)
- (instancetype)initWithItemName:(NSString *)name valueInDollars:(int) value serialNumber:(NSString *)sNumber{
    // Call the superclass's designated initializer
    self = [super init];
    // Did the superclass's designated initializer succeed?
    if (self) { // 判断对象非空,类似JS语法
        // Give the instance variables initial values
        _itemName = name;
        _serialNumber = sNumber;
        _valueInDollars = value;
        // Set _dateCreated to the current date and time
        _dateCreated = [[NSDate alloc] init];
    }
    // Return the address of the newly initialized object
    return self;
}
- (instancetype)initWithItemName:(NSString *)name{
    return [self initWithItemName:name
                   valueInDollars:0
                     serialNumber:@""];
}

// 重写父类的init方法,调用子类的designated initializer
- (instancetype)init
{
    return [self initWithItemName:@"Item"];
}

// 重写父类的description方法
- (NSString *)description
{
    NSString *descriptionString =
    [[NSString alloc] initWithFormat:@"%@ (%@): Worth $%d, recorded on %@",
     self.itemName,
     self.serialNumber,
     self.valueInDollars,
     self.dateCreated];
    return descriptionString;
}

@end

id – a pointer to any object

Because id is defined as “a pointer to any object,” you do not include an * when declaring avariable or method parameter of this type.

用id来声明变量时,由于他已经是指针了,所以不需要添加 * 号。

Class method 类方法(静态方法)

类方法常被用来创建对象实例(类似JAVA中获取单例类对象的方法)或是获取全局属性。类方法不能访问实例变量(instance variables)。
通过 + 号来声明类方法。

在头文件中声明类方法,注意头文件内容的顺序:instance variable, class method, initializer, instance method.

#import <Foundation/Foundation.h>

@interface BKItem : NSObject
{
    NSString *_itemName; //*号说明变量是个指针
    NSString *_serialNumber;
    int _valueInDollars;
    NSDate *_dateCreated;
}

+ (instancetype)randomItem; //类方法

- (instancetype)initWithItemName:(NSString *)name valueInDollars:(int) value serialNumber:(NSString *)sNumber;
- (instancetype)initWithItemName:(NSString *)name;

@end

在实现文件中实现类方法:

#import "BKItem.h"

@implementation BKItem

+ (instancetype)randomItem
{
    // Create an immutable array of three adjectives
    NSArray *randomAdjectiveList = @[@"Fluffy", @"Rusty", @"Shiny"];
    // Create an immutable array of three nouns
    NSArray *randomNounList = @[@"Bear", @"Spork", @"Mac"];
    // Get the index of a random adjective/noun from the lists
    // Note: The % operator, called the modulo operator, gives
    // you the remainder. So adjectiveIndex is a random number
    // from 0 to 2 inclusive.
    NSInteger adjectiveIndex = arc4random() % [randomAdjectiveList count];
    NSInteger nounIndex = arc4random() % [randomNounList count];
    // Note that NSInteger is not an object, but a type definition
    // for "long"
    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];
    BKItem *newItem = [[self alloc] initWithItemName:randomName
                                       valueInDollars:randomValue
                                         serialNumber:randomSerialNumber];
    return newItem;
}

@end

NSArray NSMutableArray

在Objective-C中,同一数组可以包含任意类型的对象,但是必须是Objective-C的对象,不能是基本类型和C struct。

不能将 nil 添加到数组中,但是 NSNull 可以。

NSMutableArray *items = [[NSMutableArray alloc] init];
[items addObject:nil]; //ERROR -[__NSArrayM insertObject:atIndex:]: object cannot be nil
[items addObject:[NSNull null]]; // this is OK

数组下标访问,以下两个方法等价:

NSString *str = [items objectAtIndex:0];
NSString *rts = items[0];

isa instance variable

每个对象都有一个 isa 实例变量指针,指向对象所属的类。

Exception

 // id 可以代表任何类型,就像JAVA中的Object
id lastObj = [items lastObject];
[lastObj count];

如果lastObj没有count方法,会报出以下错误:

2015-07-07 19:31:16.787 RandomItems[3647:303] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[BKItem count]: unrecognized selector sent to instance 0x7fd1a2c0c650'

unrecognized selector,selector就是message,就是类中的方法。
-[BKItem count],- 表示receiver是BKItem的实例,如果是 + 则receiver是BKItem class.

类名前缀

OC中没有包或命名空间的概念(这点也真是醉了)…
所以OC的类名都有前缀来区分,可以是公司名的缩写或是项目名的缩写,通常使用三个字母,因为两个字母被苹果使用了,防止命名冲突。
NS — NeXTSTEP,苹果收购的公司

Strong and Weak References

如果两个对象都引用了对方,并且都是强引用,那么会造成内存泄漏。可以通过弱引用来解决这个问题,通常两个对象互相引用对方时,这两个对象存在父子关系,父对象会强引用子对象,子对象只需弱引用父对象。

//声明一个弱引用变量
__weak BNRItem *_container;

@property

之前每声明一个实例变量,都需要写对应的set get方法,通过property来声明可以不用写set get方法,编译器会帮你将实例变量和set get访问方法声明好。

@property NSString *itemName;

下表为是否使用@property的区别:

fileWithout propertiesWith properties
BNRThing.h@interface BNRThing : NSObject
{
NSString *_name;
}
– (void)setName:(NSString *)n;
– (NSString *)name;
@end
@interface BNRThing : NSObject
@property NSString *name;
@end
BNRThing.m@implementation BNRThing
– (void)setName:(NSString *)n
{
_name = n;
}
– (NSString *)name
{
return _name;
}
@end
@implementation BNRThing
@end

注意property属性名字不需要下划线前缀

property attribute (这两个单词有意思,都能翻译成属性…)

@property (nonatomic, readwrite, strong) NSString *itemName;

nonatomic, atomic,默认值是atomic,和多线程相关的attribute(原子的,即线程安全的;非原子,即非线程安全,性能更高),iOS中通常用nonatomic

readwrite, readonly,默认值是readwrite,告诉编译器是否生成set方法,只读不生成set方法。

strong, weak, copy, unsafe_unretained,针对OC的对象,其默认值是strong。如果是非OC的对象(如基本类型),其只有一个可选值unsafe-unretained,也是非OC对象的默认值,所以可以不用声明。

什么时候用copy?当要声明的变量类型有可变的子类时,如NSString/NSMutableString or NSArray/NSMutableArray,这时要用copy,其生成的最终代码如下,copy会复制对象,并将变量强引用到复制生成的对象

- (void)setItemName:(NSString *)itemName
{
    _itemName = [itemName copy];
}
@property (nonatomic, strong) BNRItem *containedItem;
@property (nonatomic, weak) BNRItem *container;
@property (nonatomic, copy) NSString *itemName;
@property (nonatomic, copy) NSString *serialNumber;
@property (nonatomic) int valueInDollars;
@property (nonatomic, readonly, strong) NSDate *dateCreated;

The memory management attribute’s values are strong, weak, copy, and unsafe_unretained. This
attribute describes the type of reference that the object with the instance variable has to the object that
the variable is pointing to.

上面的第二句话怎么理解? 好多that…

自定义set get方法

当你的set get方法中有逻辑时,需要在类实现文件中添加自定义的set get方法:

- (void)setContainedItem:(BNRItem *)containedItem
{
    _containedItem = containedItem;
    self.containedItem.container = self;
}

如果你同时实现了set get方法,则需要在头文件中声明实例变量,@property不会再帮你自动添加实例变量了。

本文是对《iOS Programming The Big Nerd Ranch Guide 4th Edition》第二,三章的总结。

点赞