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的區別:
file | Without properties | With 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》第二,三章的總結。