基本概念
- 類( class )
- 實例/對象 ( object )
- 實例變量/成員變量 ( ivar, instance variable )
- 對象 和 變量 是兩個東西藻烤,當(dāng)一個變量指向一個對象
- 屬性
- 消息與方法
- 編譯時與運行時
- SEL -- 可理解為消息名
- IMP -- 地址,可理解為實現(xiàn)代碼
- Method -- SEL + IMP + 其他
Selector(typedef struct objc_selector *SEL)
: 在運行時 Selectors 用來代表一個方法的名字。Selector 是一個在運行時被注冊(或映射)的C類型字符串煤禽。Selector由編譯器產(chǎn)生并且在當(dāng)類被加載進(jìn)內(nèi)存時由運行時自動進(jìn)行名字和實現(xiàn)的映射墨坚。
Implementation(typedef id (*IMP)(id, SEL,...))
:這個數(shù)據(jù)類型指向一個方法的實現(xiàn)的最開始的地方。該方法為當(dāng)前CPU架構(gòu)使用標(biāo)準(zhǔn)的C方法調(diào)用來實現(xiàn)凤类。該方法的第一個參數(shù)指向調(diào)用方法的自身(即內(nèi)存中類的實例對象穗泵,若是調(diào)用類方法,該指針則是指向元類對象 metaclass)踱蠢。第二個參數(shù)是這個方法的名字selector火欧,該方法的真正參數(shù)緊隨其后。
Method(typedef struct objc_method *Method)
: 方法是一個不透明的用來代表一個方法的定義的類型茎截。
基本數(shù)據(jù)類型
iOS 應(yīng)避免使用基本類型苇侵,建議使用 Foundation 數(shù)據(jù)類型,這是為了基于64-bit 適配考慮企锌,對應(yīng)關(guān)系如下:
基本類型 | Foundation 類型 |
---|---|
int | NSInteger |
float | CGFloat |
unsigned | NSUInteger |
動畫時間 | NSTimeInterval |
一般文件形式
- .h 文件
// 引用
// #import 與 C 的 #include 區(qū)別是 #import 可確保不會重復(fù)導(dǎo)入同一個文件
#import <UIKit/UIKit.h>
// 一般用于引用系統(tǒng)框架
//@import UserNotifications;
// @class 只是對類進(jìn)行聲明
//@class TestItem;
// Objective-C 中定義一個類并不是使用 @class , 而是使用 @interface 榆浓。 interface 直譯過來就是接口的意思, Objective-C 使用這個單詞定義類撕攒,意思是說”外部能用的接口都在這里了“
// : NSObject -- 表示繼承
@interface TestObject : NSObject
//{
// //放置實例變量陡鹃,但一般不會寫在.h文件中烘浦,因為一般默認(rèn)實例變量是私有的
// //實例變量,一般約定實例變量采用 _ 開頭命名
// NSString *_str1;
//}
// 存取方法
//- (void)setStr1:(NSString *)str;
//- (NSString *)str1;
// 屬性 = 實例變量 + getter + setter
// 屬性的特征:多線程特征萍鲸、讀寫特征闷叉、內(nèi)存管理特征
@property (nonatomic, readwrite, strong) NSString *str2;
@property (nonatomic, readwrite, assign) CGFloat floatValue;
// 初始化方法 ( initialization method ),一般約定實例初始化方法格式為 initXXX 脊阴,類初始化方法格式為 類名 + XXX
// 返回值為什么是 instancetype 握侧?涉及到繼承問題
// 實例方法 ( instance method ),以 - 開頭
- (instancetype)initWithParam:(NSString *)param1 param2:(NSInteger)param2;
// 類方法 ( class method )嘿期,以 + 開頭
+ (instancetype)testObjectWithoutParam;
// 返回值類型 函數(shù)名(包含:) 參數(shù)類型 參數(shù)名
- (void)method1;
- (void)method2:(NSString *)str;
+ (NSString *)sayHello;
@end
- .m 文件
#import "TestObject.h"
// 類擴(kuò)展( class extension )
@interface TestObject()
{
// 放置實例變量
}
// 放置屬性
@end
// 實現(xiàn)程序段 ( implementation block )從 @implementation 指令開始品擎, @end 指令結(jié)束
@implementation TestObject
{
// 放置實例變量
}
// 自定義屬性的合成。對應(yīng)也有 @dynamic 备徐,@dynamic 告訴編譯器:屬性的setter與getter方法由用戶自己實現(xiàn)萄传,不自動生成
@synthesize str2 = _str2;
// 如果自定義了屬性的存取方法,編譯器不會自動創(chuàng)建相應(yīng)的實例變量蜜猾,則需要進(jìn)行屬性合成
- (void)setStr2:(NSString *)str2 {
_str2 = str2;
}
- (NSString *)str2 {
return _str2;
}
//在初始化方法中盡量避免使用存取方法訪問實例變量秀菱,而是直接訪問實例變量
- (instancetype)initWithParam:(NSString *)param1 param2:(NSInteger)param2 {
self = [super init];
if (self) {
}
return self;
}
+ (instancetype)testObjectWithoutParam {
TestObject *testObject = [[TestObject alloc] initWithParam:@"defaultStr" param2:0];
return testObject;
}
@end
NOTE1:
- self -- 隱式的局部變量,指向當(dāng)前調(diào)用方法的這個類的實例
- super -- 本質(zhì)是一個編譯器標(biāo)示符瓣铣,和 self是指向的同一個消息接受者
super 和 self 的區(qū)別是: 當(dāng)向 self 發(fā)送消息時答朋,會從當(dāng)前類的方法列表中開始找,如果沒有棠笑,就從父類中再找梦碗;而當(dāng)向 super 發(fā)送消息時,其實是向self發(fā)送消息蓖救,但是要求系統(tǒng)在查找方法時跳過當(dāng)前對象的類洪规,從父類的方法列表開始查詢 - id -- 指向任意對象的指針
NOTE2: 類方法和實例方法的區(qū)別和聯(lián)系
- 類方法:
類方法是屬于類對象的
類方法只能通過類對象調(diào)用
類方法中的self是類對象
類方法可以調(diào)用其他的類方法
類方法中不能訪問成員變量
類方法中不定直接調(diào)用對象方法 - 實例方法:
實例方法是屬于實例對象的
實例方法只能通過實例對象調(diào)用
實例方法中的self是實例對象
實例方法中可以訪問成員變量
實例方法中直接調(diào)用實例方法
實例方法中也可以調(diào)用類方法(通過類名)
OC對象的內(nèi)存布局
- 所有父類的成員變量和自己的成員變量都會存放在該對象所對應(yīng)的存儲空間中,對象的結(jié)構(gòu)如圖
- 每一個對象內(nèi)部都有一個
isa
指針,指向他的類對象,類對象中存放著本對象的 對象方法列表循捺、成員變量的列表斩例、屬性列表 - 類對象也有一個
isa
指針指向元對象 ( meta class ) ,元對象內(nèi)部存放的是 類方法列表 - 類對象還有一個
superclass
指針指向他的父類對象
屬性
- 屬性 = 實例變量 + getter + setter
- 屬性的主要作用在于封裝對象中的數(shù)據(jù)
- 完成屬性定義后从橘,編譯器會自動編寫訪問這些屬性所需的方法念赶,此過程叫做“自動合成”,這個過程由編譯器在編譯期執(zhí)行
- 每增加一個屬性,系統(tǒng)都會在ivar_list(成員變量列表)中添加一個成員變量的描述,在method_list(方法列表)中增加setter與getter方法的描述,在prop_list(屬性列表)中增加一個屬性的描述,然后計算該屬性在對象中的偏移量,然后給出setter與getter方法對應(yīng)的實現(xiàn),在setter方法中從偏移量的位置開始賦值,在getter方法中從偏移量開始取值,為了能夠讀取正確字節(jié)數(shù),系統(tǒng)對象偏移量的指針類型進(jìn)行了類型強轉(zhuǎn)
- 在 protocol 中使用 @property 只會生成 setter 和 getter 方法聲明,我們使用屬性的目的,是希望遵守我協(xié)議的對象能實現(xiàn)該屬性
- 在 category 使用 @property 也是只會生成 setter 和 getter 方法的聲明,如果我們真的需要給 category 增加屬性的實現(xiàn),需要借助于運行時的兩個函數(shù):objc_setAssociatedObject 和 objc_getAssociatedObject
- 什么情況下不會 autosynthesis(自動合成):手動合成屬性和實例變量恰力;同時重寫了 getter 和 setter 方法叉谜;重寫了只讀屬性的 getter 方法;使用 @dynamic 時踩萎;在 @protocol 中定義所有的屬性停局;在 category 中定義的所有屬性;重載的屬性,在子類中重載了父類的屬性董栽,必須用 @synthesize 來手動合成ivar码倦。
property 在 Runtime 中是 objc_property_t
,定義如下:
typedef struct objc_property *objc_property_t;
而 objc_property 是一個結(jié)構(gòu)體锭碳,包括 name 和 attributes 袁稽,定義如下:
struct property_t {
const char *name;
const char *attributes;
};
而 attributes 本質(zhì)是 objc_property_attribute_t
,定義了 property 的一些屬性工禾,定義如下:
/// Defines a property attribute
typedef struct {
const char *name; /**< The name of the attribute */
const char *value; /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;
而attributes的具體內(nèi)容包括:類型运提,原子性,內(nèi)存語義和對應(yīng)的實例變量闻葵。
例如:我們定義一個 string 的 property @property (nonatomic, copy) NSString *string;
,通過 property_getAttributes(property)
獲取到 attributes 并打印出來之后的結(jié)果為T@"NSString",C,N,V_string
其中T就代表類型癣丧,可參閱Type Encodings槽畔,C就代表Copy,N代表nonatomic胁编,V就代表對于的實例變量厢钧。
屬性的特征
- 多線性特征
nonatomic / atomic - 讀寫特征
readwrite / readonly - 內(nèi)存管理特征
strong / weak / assign / copy / unsafe_unretained- strong 修飾OC對象屬性描述符;強引用嬉橙,引用計數(shù)加一
- weak 可以修飾OC對象早直;不做強引用,引用計數(shù)不加一市框,當(dāng)屬性指向的對象被回收后屬性自動置為nil
- assign 修飾非OC對象屬性描述符霞扬;拷貝值,不做引用計數(shù)
- copy 所表達(dá)的所屬關(guān)系與 strong 類似枫振。然而設(shè)置方法并不保留新值喻圃,而是將其“拷貝” (copy)。一般用于指向有可修改子類的對象的指針的屬性
- unsafe_unretained 修飾的屬性的指針指向的對象被銷毀時粪滤,指針不會自動置為nil斧拍,而是成為空指針(保留原始值)。因此用于不需要做內(nèi)存管理杖小,即不指向任何對象的屬性
- 存取方法名
getter= / setter= - 其他
nonnull / nullable / null_resettable
默認(rèn)修飾符
- 基本數(shù)據(jù)類型 -- atomic,readwrite,assign
- OC對象 -- atomic,readwrite,strong
weak的使用場景
- 避免引用循環(huán)
- 自身已經(jīng)對它進(jìn)行一次強引用,沒有必要再強引用一次
copy的使用場景
- NSString肆汹、NSArray、NSDictionary 等有可修改子類
- block
block 使用 copy 是從 MRC 遺留下來的“傳統(tǒng)”,在 MRC 中,方法內(nèi)部的 block 是在棧區(qū)的,使用 copy 可以把它放到堆區(qū).在 ARC 中寫不寫都行:對于 block 使用 copy 還是 strong 效果是一樣的予权,但寫上 copy 也無傷大雅昂勉,還能時刻提醒我們:編譯器自動對 block 進(jìn)行了 copy 操作
weak 與 assign 區(qū)別
//輸出為:
//A dealloc
//b.a: 0x0
//B dealloc
// 因為 A 是強引用 B,所以先 dealloc A伟件,然后 B 中屬性指向的 A 指針指向 nil硼啤,最后 dealloc B
@interface A : NSObject
@property B *b;
@end
@implementation A
- (void)dealloc {
NSLog(@"A dealloc");
}
@end
@interface B : NSObject
@property (weak)A *a;
@end
@implementation B
- (void)dealloc {
NSLog(@"B dealloc");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
B *b = [[B alloc] init];
{
A *a = [[A alloc] init];
a.b = b;
b.a = a;
}
NSLog(@"b.a: %p", b.a);
}
return 0;
}
//輸出為:
//A dealloc
//b.a: 0x1002025a0 //這里是有值的
//B dealloc
@interface A : NSObject
@property B *b;
@end
@implementation A
- (void)dealloc {
NSLog(@"A dealloc");
}
@end
@interface B : NSObject
@property (assign)A *a;
@end
@implementation B
- (void)dealloc {
NSLog(@"B dealloc");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
B *b = [[B alloc] init];
{
A *a = [[A alloc] init];
a.b = b;
b.a = a;
}
NSLog(@"b.a: %p", b.a);
}
return 0;
}
- weak 此特質(zhì)表明該屬性定義了一種“非擁有關(guān)系” (nonowning relationship)。為這種屬性設(shè)置新值時,設(shè)置方法既不保留新值谴返,也不釋放舊值煞肾。在屬性所指的對象遭到摧毀時,屬性值也會清空(nil out)嗓袱。 weak 必須用于 OC 對象籍救。
- assign在屬性所指的對象遭到摧毀時,屬性值不會清空渠抹。assign 可以用非 OC 對象蝙昙。
Runtime 如何實現(xiàn) weak 屬性
Runtime 對注冊的類,會進(jìn)行布局梧却,對于 weak 對象會放入一個 hash 表中奇颠。 用 weak 指向的對象內(nèi)存地址作為 key,wesk 修飾的屬性變量的內(nèi)存地址為 value 放航。當(dāng)此對象的引用計數(shù)為 0 的時候會 dealloc烈拒,假如 weak 指向的對象內(nèi)存地址是 a,那么就會以 a 為 key 广鳍, 在這個 weak 表中搜索荆几,找到所有以 a 為 key 的 weak 對象,從而設(shè)置為 nil赊时。
屬性所指向的對象使用 copy 修飾符的條件
要使屬性所指向的對象能使用 copy 修飾符吨铸,對象必需實現(xiàn) <NSCopying>
協(xié)議的 - copyWithZone: 方法
- (id)copyWithZone:(NSZone *)zone {
CYLUser *copy = [[[self class] allocWithZone:zone]
initWithName:_name
age:_age
sex:_sex];
copy->_friends = [_friends mutableCopy];
return copy;
}
//在上述例子中,存放朋友對象的 set 是用 “copyWithZone:” 方法來拷貝的祖秒,這種淺拷貝方式不會逐個復(fù)制 set 中的元素诞吱。若需要深拷貝的話,則可像下面這樣狈涮,編寫一個專供深拷貝所用的方法:
- (id)deepCopy {
CYLUser *copy = [[[self class] allocWithZone:zone]
initWithName:_name
age:_age
sex:_sex];
copy->_friends = [[NSMutableSet alloc] initWithSet:_friends
copyItems:YES];
return copy;
}
NSObject 與 <NSObject>
NSObject 是所有類的根類狐胎,<NSObject> 是 NSObject 實現(xiàn)的協(xié)議( protocol ),集合了對所有OC對象基本的方法
實例的創(chuàng)建歌馍、初始化和銷毀
// 返回接收類的新的實例握巢,但這個實例并不能使用,因為所有關(guān)于這個實例的實例變量的內(nèi)存地址都指向0
+ alloc
// 一般的初始化方法
- init
+ new
// 當(dāng)實例被回收前系統(tǒng)發(fā)送該通知
- dealloc
// 返回一個對象松却,使用此方法必須實現(xiàn) <NSCopying> 協(xié)議的- copyWithZone:方法
- copy
// 返回一個對象暴浦,使用此方法必須實現(xiàn) <NSMutableCopying> 協(xié)議的- mutableCopyWithZone:方法
- mutableCopy
淺復(fù)制與深復(fù)制
淺復(fù)制:指針復(fù)制
深復(fù)制:內(nèi)容復(fù)制。但對于集合類來說晓锻,內(nèi)容復(fù)制是復(fù)制集合對象本身歌焦,對于集合對象內(nèi)的元素仍是指針復(fù)制,即單層深復(fù)制
- 非集合類
immutable 對象 | mutable 對象 | |
---|---|---|
copy | 淺復(fù)制 | 深復(fù)制 |
mutableCopy | 深復(fù)制 | 深復(fù)制 |
- 集合類
immutable 對象 | mutable 對象 | |
---|---|---|
copy | 淺復(fù)制 | 單層深復(fù)制 |
mutableCopy | 單層深復(fù)制 | 單層深復(fù)制 |
判斷對象類砚哆、繼承独撇、行為和一致性
// 返回類對象
- class
- NSStringFromClass // 返回類名的字符串
- NSClassFromString // 根據(jù)字符串返回對應(yīng)的類對象
// 判斷實例是否某一類對象或繼承與該類的子類的對象
- isKindOfClass:
// 判斷實例是否某一類對象
- isMemberOfClass:
// 判斷實例是否能夠響應(yīng)選擇器 ( selector )
- respondsToSelector:
- instanceRespondsToSelector:
// 判斷實例是否實現(xiàn)協(xié)議內(nèi)容
- conformsToProtocol:
判斷兩個實例是否相等
// 1.先判斷hash值:hash == NO,對象不等;hash == YES ==> isEqual:
// 2.isEqual: == NO纷铣,對象不等卵史,hash == YES,對象相等
.hash
- isEqual:
發(fā)送消息
- performSelector:
- performSelector:withObject:
- performSelector:withObject:withObject:
- performSelector:withObject:afterDelay:
對象的描述
//可重寫屬性 getter 方法返回對象的描述
.description
MRC 相關(guān)
- retain
- release
- autorelease
- retainCount
KVC 相關(guān)
- setValue:forKey:
- setValue:forUndefinedKey:
- setValue:forKeyPath:
- valueForKey:
- valueForUndefinedKey:
- valueForKeyPath:
KVO相關(guān)
- addObserver:forKeyPath:options:context:
- removeObserver:forKeyPath:
// oberver 的方法回調(diào)
- observeValueForKeyPath:ofObject:change:context:
其他
- dictionaryWithValuesForKeys:
- awakeFromNib
內(nèi)存管理
OC 通過引用計數(shù)來管理內(nèi)存搜立,決定對象是否需要釋放以躯。檢查對象的引用計數(shù)( retainCount ),如果為 0 啄踊,則釋放掉
對象的內(nèi)存銷毀步驟
- 對象的引用計數(shù)變?yōu)榱?/li>
- 對象正在被銷毀忧设,生命周期即將結(jié)束
- 不能再有新的
__weak
弱引用,否則將指向 nil - 調(diào)用 [self dealloc]
- 父類對象調(diào)用 - dealloc
- 繼承關(guān)系最底層的父類調(diào)用 - dealloc
- 如果是 MRC 代碼颠通,則手動釋放實例變量們( ivars )
- 繼承關(guān)系中的每一層的父類址晕,都在調(diào)用 - dealloc
- 根類對象 NSObject 調(diào)用 - dealloc
- 調(diào)用 OC Runtime 中的 object_dispose() 方法
- 調(diào)用 object_dispose()
- 為 C++ 的實例變量們( ivars ) 調(diào)用 destructors
- 為 ARC 狀態(tài)下的實例變量們( ivars ) 調(diào)用 - release
- 解除所有使用 Runtime Associate 方法關(guān)聯(lián)的對象
- 解除所有
__wesk
引用 - 調(diào)用 free()
MRC
在OC 1.0采用的是手動引用計數(shù) (MRC) 來管理內(nèi)存
- 關(guān)閉內(nèi)存自動管理
在 Targets 設(shè)置里 -- Build Phases -- Compile Sources 里,對想要關(guān)閉內(nèi)存自動管理的文件的 Compiler Flags 輸入-fno-objc-arc
- release
- retain
- retainCount
@interface Test : NSObject
@end
@implementation Test
- (void)dealloc {
NSLog(@"Test: dealloc");
}
@end
Test *a = [[Test alloc] init];
Test *b = nil;
b = a;
[a release];
//輸出 Test: dealloc
//因為 b 并未擁有 Test 類型的對象顿锰,所以并不影響這個對象的回收斩箫,此時如果執(zhí)行 [b release] 會報錯提示 overreleased
Test *a = [[Test alloc] init];
Test *b = nil;
b = a;
[a retain];
[a release];
//輸出 Test: dealloc
[a release]; // 或 [b release];
//誰持有誰釋放
Test *a = [[Test alloc] init];
Test *b = nil;
b = a;
[b retain];
[a release];
//輸出 Test: dealloc
[b release];
@interface Test : NSObject
@end
@implementation Test
- (void)dealloc {
NSLog(@"Test: dealloc");
}
//重寫 retain 和 release 方法
- (instancetype)retain {
NSLog(@"Test before retain: %@", @(self.retainCount));
id result = [super retain];
NSLog(@"Test after retain: %@", @(self.retainCount));
return result;
}
- (oneway void)release
{
NSLog(@"Test before release: %@", @(self.retainCount));
[super release];
NSLog(@"Test after release: %@", @(self.retainCount));
}
@end
Test *a = [[Test alloc] init];
//Test before release: 1
//Test: dealloc
//Test after release: 1
[a release];
//Test after release: 1 是因為都已經(jīng)釋放了,也沒必要將引用計數(shù)置為 0
@interface Test : NSObject
+ (Test *)getInstance;
@end
@implementation Test
- (void)dealloc {
NSLog(@"Test: dealloc");
}
+ (Test *)getInstance {
Test *result = [[Test alloc] init];
return result;
}
@end
Test *a = [Test getInstance];
[a release];
// 這里我們明確知道要調(diào)用 [a release]撵儿,但如果 [Test getInstance] 是個黑箱,又如何知道是否該 release
// 函數(shù)創(chuàng)建對象狐血,但無法release它淀歇;函數(shù)調(diào)用者使用對象,且不應(yīng)去 release 它
//即函數(shù)調(diào)用的時候不是釋放對象的好時機
// autorelease == 延后調(diào)用一次release 匈织,關(guān)于延后浪默,具體是什么時機 ==> NSAutoreleasePool 相關(guān)
//對象標(biāo)記 autorelease 相當(dāng)于 放到 NSAutoreleasePool 對象里,當(dāng) NSAutoreleasePool 對象調(diào)用 - drain 時缀匕,它會對里面所有對象都調(diào)用 release
@interface Test : NSObject
+ (Test *)getInstance;
@end
@implementation Test
- (void)dealloc {
NSLog(@"Test: dealloc");
}
+ (Test *)getInstance {
// OC 約定放到最近的一個 NSAutoreleasePool 對象
Test *result = [[[Test alloc] init] autorelease];
return result;
}
@end
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
Test *a = [Test getInstance];
//輸出 Test: dealloc
[pool drain];
- autoreleasepool 以一個隊列數(shù)組的形式實現(xiàn)纳决,主要通過三個函數(shù)完成 Push / release / Pop 操作
- objc_autoreleasepoolPush
- objc_autorelease
- objc_autoreleasepoolPop
ARC
ARC(自動引用計數(shù))相對于 MRC ,不僅僅在編譯時自動添加 retain / release / autorelease 乡小,應(yīng)該是編譯期和運行期兩部分幫助開發(fā)者管理內(nèi)存
- 編譯期阔加,ARC 用的是更底層的 C 接口實現(xiàn)的 retain / release / autorelease ,這樣做性能更好满钟,也是為什么不能再 ARC 環(huán)境下手動 retain / release / autorelease 胜榔,同時對同一上下文的同一對象的成對 retain / release 操作進(jìn)行優(yōu)化
ARC 下 不能使用 NSAutoreleasePool ,對于 autorelease 需求湃番,有 NSAutoreleasePool ==> @autoreleasepool { … }
夭织, ARC 下 autorelease 的釋放時機:
- 指定 @autoreleasepool { … } 下,在當(dāng)前大括號的作用域結(jié)束時釋放
- 不指定 @autoreleasepool { … } 吠撮,對象會在當(dāng)前 Runloop 結(jié)束時釋放
ARC下的dealloc
//創(chuàng)建B并且當(dāng)回收時尊惰,先調(diào)用B的dealloc,再調(diào)用A的dealloc
@interface A : NSObject
@end
@implementation A
- (void)dealloc {
NSLog(@"A dealloc");
}
@end
@interface B : NSObject
@property A *a;
@end
@implementation B
- (void)dealloc {
NSLog(@"B dealloc");
}
- (instancetype)init {
self = [super init];
if (self) {
_a = [[A alloc] init];
}
return self;
}
@end
BAD_ACCESS
BAD_ACCESS 出現(xiàn)的情況分為兩種:
- 訪問了野指針
- 在 MRC 下對已經(jīng)釋放的對象再次執(zhí)行 release
- 如屬性用 assign 修飾,當(dāng)所指向的對象已經(jīng)釋放回收后弄屡,屬性并不會指向 nil 题禀,這時訪問該對象
- 死循環(huán)
CoreFoundation 管理內(nèi)存
CoreFoundation 沒有自動內(nèi)存管理,需要手動釋放內(nèi)存
CFStringRef string = CFStringCreateWithCString(NULL, "蘋果", kCFStringEncodingUTF8);
CFRelease(string);
因此琢岩,當(dāng)一個對象在 Foundation 或 CoreFoundation 中創(chuàng)建而需要在另一個系統(tǒng)中使用時投剥,需要確定對象的管理權(quán),分三種情況:
- 不改變
- 在 Foundation 中創(chuàng)建str担孔,在CoreFoundation中手動管理
- 在 CoreFoundation 中創(chuàng)建str江锨,在Foundation中自動管理
原則:明確告知由哪個系統(tǒng)來管理內(nèi)存資源
- __bridge —— 跨層級使用,但不更改對象的擁有權(quán)
- __bridge_retained —— 內(nèi)存歸屬修改糕篇,需手動release
- __bridge_transfer —— 內(nèi)存歸屬修改啄育,由ARC管理,不需手動release
NSString *str = @"蘋果";
CFStringRef str2 = (__bridge CFStringRef)str;
NSString *str3 = @"蘋果";
CFStringRef str4 = (__bridge_retained CFStringRef)str3;
CFRelease(str4);
CFStringRef string = CFStringCreateWithCString(NULL, "蘋果", kCFStringEncodingUTF8);
NSString *string2 = (__bridge NSString *)string;
//...
CFRelease(string);
CFStringRef string3 = CFStringCreateWithCString(NULL, "蘋果", kCFStringEncodingUTF8);
NSString *string4 = (__bridge_transfer NSString *)string3;
KVC & KVO
KVC
KVC ( Key Value Coding )拌消,它把整個 object 看成 key 和 value 的對應(yīng)關(guān)系
- setValue:forKey:
// 可重寫用于處理沒有對應(yīng)key的情況
- setValue:forUndefinedKey:
// 嵌套使用
- setValue:forKeyPath:
- valueForKey:
- valueForUndefinedKey:
- valueForKeyPath:
- KVC 屬性不用實現(xiàn) getter 和 setter 方法
KVC Collection Operators
@avg.
@sum.
@max.
@min.
@count
NSMutableArray* array = [NSMutableArray array];
NSDictionary* dic = @{@"name" : @"蘋果", @"price" : @1.5};
Fruit* fruit = [[Fruit alloc] initWithDict:dict];
[array addObject:fruit];
NSDictionary* dic = @{@"name" : @"蘋果", @"price" : @1};
Fruit* fruit = [[Fruit alloc] initWithDict:dict];
[array addObject:fruit];
NSDictionary* dic = @{@"name" : @"蘋果", @"price" : @2.5};
Fruit* fruit = [[Fruit alloc] initWithDict:dict];
[array addObject:fruit];
CGFloat avg = [[array valueForKeyPath:@"@avg.price"] floatValue];
CGFloat max = [[array valueForKeyPath:@"@max.price"] floatValue];
KVO
KVO ( Key Value Observing )
- addObserver:forKeyPath:options:context:
- removeObserver:forKeyPath:
//觀察的回調(diào)
- observeValueForKeyPath:ofObject:change:context:
Runtime
要使用Runtime相關(guān)方法挑豌,需導(dǎo)入Runtime框架#import <objc/runtime.h>
OC 可以轉(zhuǎn)成 C 的 API,通過這些API可以達(dá)到高級的功能墩崩,這個C API也稱為 Runtime
在運行時氓英,類(Class)維護(hù)了一個消息分發(fā)列表來解決消息的正確發(fā)送。每一個消息列表的入口是一個方法(Method)鹦筹,這個方法映射了一對鍵值對铝阐,其中鍵值是這個方法的名字 selector(SEL),值是指向這個方法實現(xiàn)的函數(shù)指針 implementation(IMP)
OC函數(shù)調(diào)用本質(zhì)
@interface Fruit : NSObject
@property CGFloat price;
@end
@implementation Fruit
@end
@interface Ferrari : NSObject
@property CGFloat price;
@end
@implementation Ferrari
@end
@interface Util : NSObject
@end
@implementation Util
+(CGFloat)getPriceForFruit:(Fruit *)f {
return f.price;
}
@end
Ferrari *f = [[Ferrari alloc] init];
f.price = 5000000;
//輸出5000000
NSLog(@"price: %@", @([Util getPriceForFruit:f]));
上面的代碼Xcode會警告但編譯運行仍會輸出結(jié)果铐拐,對于 Util 的類方法 getPriceForFruit 徘键,它接收的是 Fruit 類型的參數(shù),但在代碼中實際我們給的是 Ferrari 類型是實例對象
再看下面這一句代碼:
CGFloat result = [cashier checkout];
這句代碼可以有兩種說法:
- 調(diào)用cashier的一個名為checkout的函數(shù)遍蟋,該函數(shù)沒有參數(shù)吹害,其返回結(jié)果是一個CGFloat,存入result變量中
- 給cashier發(fā)送一個名為checkout的消息虚青,沒有附加內(nèi)容它呀,消息響應(yīng)結(jié)果是一個CGFloat,存入result變量中
兩種說法都沒有出現(xiàn)類名挟憔,所以钟些,發(fā)送消息只跟該對象響應(yīng)該消息的實現(xiàn)相關(guān)。
objc_msgSend
首先用終端切換到項目目錄下绊谭,輸入 clang -rewrite-objc xxx.m
政恍,把OC程序重寫成C代碼
簡化代碼可知每一個對象的消息發(fā)送,都是調(diào)用到
// id -- self达传,指發(fā)送消息的對象本身
// SEL -- _cmd篙耗,指消息名稱
objc_msgSend(id, SEL, …)
即在 OC 動態(tài)編譯時迫筑,方法在運行時會被動態(tài)轉(zhuǎn)為消息發(fā)送,即: objc_msgSend()
- OC 中向一個對象發(fā)送消息時宗弯,Runtime 會根據(jù)對象的 isa 指針找到該對象所屬的類脯燃,然后在該類中的方法列表及其父類方法列表中尋找方法運行,然后在發(fā)送消息的時候蒙保,objc_msgSend 方法不會返回值辕棚,所謂的返回內(nèi)容都是具體調(diào)用時執(zhí)行的
- 如果向一個 nil 對象發(fā)送消息, isa 指針指向的內(nèi)存地址為 0 邓厕,所以不會出現(xiàn)任何錯誤
- 在 objc_msgSend() 中第二個參數(shù)為 SEL逝嚎,對于每個類對象都有一個方法列表,方法列表中記錄著方法的名稱详恼,方法實現(xiàn)以及參數(shù)類型补君,其實 selector 本質(zhì)就是方法名稱,通過這個 selector 昧互,Runtime 可以嘗試找到對應(yīng)的 IMP 地址挽铁,即其方法實現(xiàn)。如果找到了敞掘,就跳到響應(yīng)的函數(shù)IMP去執(zhí)行實現(xiàn)代碼叽掘,如果在最頂層的父類中依然找不到響應(yīng)的方法實現(xiàn),則用
objc_msgForward()
函數(shù)(IMP類型)指針代替 IMP 玖雁,最后執(zhí)行這個 IMP够掠。objc_msgForward()
會做以下幾件事:- Method resolution
Runtime 發(fā)送 + resolveInstanceMethod: 或 + resolveClassMethod: 。在這兩個方法里可以提供一個方法將方法與傳入的 SEL 綁定( class_addMethod() )茄菊,并放回 YES,那么運行時系統(tǒng)就會重新啟動一次消息發(fā)送的過程 - Fast forwarding
如果在 Method resolution 的方法里返回 NO,則 Runtime 發(fā)送 - forwardingTargetForSelector: 赊堪。在這個方法里可以 return 一個對象面殖,則系統(tǒng)會將消息發(fā)送給該對象 - Normal forwarding
如果在 Fast forwarding 的方法里返回 nil ,則 Runtime 發(fā)送 - methodSignatureForSelector: 哭廉。在這個方法里可以 return 一個 NSMethodSignature 類型的對象脊僚,它表示一個方法簽名,記錄了返回值和參數(shù)的類型信息遵绰,如果方法 return 為 nil 辽幌,則 Runtime 會發(fā)出 - doesNotRecognizeSelector: 消息,程序報 unrecognized selector 錯誤椿访。如果 return 一個 NSMethodSignature 類型的對象乌企,則 Runtime 會創(chuàng)建一個 NSInvocation 類型的對象作為 - forwardInvocation: 的實參發(fā)送消息給目標(biāo)對象。NSInvocation 實際上就是對一個消息的描述成玫,包括 selector 以及參數(shù)等信息加酵。在 - forwardInvocation:方法里可以給 NSInvocation 類型對象發(fā)送 - invokeWithTarget:消息拳喻,傳進(jìn)去一個對象,則由該對象來發(fā)送該消息
- Method resolution
@interface TestModel : NSObject
- (void)hahahaInstance;
+ (void)hahahaClass;
@end
@implementation TestModel
- (void)instanceMethodDealWithHahaha {
NSLog(@"hahaha");
}
+ (void)classMethodDealWithHahaha {
NSLog(@"hahaha");
}
// Method resolution
+ (BOOL)resolveClassMethod:(SEL)sel {
NSLog(@"resolveClassMethod");
return [super resolveClassMethod:sel];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"resolveInstanceMethod");
if (sel == @selector(hahahaInstance)) {
Method method = class_getInstanceMethod([self class], @selector(instanceMethodDealWithHahaha));
IMP methodImp = method_getImplementation(method);
const char *methodType = method_getTypeEncoding(method);
class_addMethod([self class], sel, methodImp, methodType);
return YES;
}
return [super resolveInstanceMethod:sel];
}
// Fast forwarding
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"forwardingTargetForSelector");
if (aSelector == @selector(hahahaInstance)) {
TestModel2 *testModel2 = [[TestModel2 alloc] init];
return testModel2;
}
return [super forwardingTargetForSelector:aSelector];
}
// Normal forwarding
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
Method m = class_getInstanceMethod([TestModel2 class], aSelector);
const char *type = method_getTypeEncoding(m);
signature = [NSMethodSignature signatureWithObjCTypes:type];
}
return signature;
}
- (void)doesNotRecognizeSelector:(SEL)aSelector {
NSLog(@"doesNotRecognizeSelector");
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"forwardInvocation");
TestModel2 *aaa = [[TestModel2 alloc] init];
[anInvocation invokeWithTarget:aaa];
}
@end
@interface TestModel2 : NSObject
- (void)hahahaInstance;
@end
@implementation TestModel2
- (void)hahahaInstance {
NSLog(@"TestModel2's hahaha");
}
@end
OC <=> C
要使用Runtime相關(guān)方法猪腕,需包含頭文件 #include <objc/runtime.h>
Runtime C API 可做到:
- 讀取語言信息
- 可以做一些OC無法做的事情
- 修改語言信息
獲取一個類對象的所有屬性名
unsigned int count = 0;
//objc_property_t 是一個C的數(shù)據(jù)結(jié)構(gòu)冗澈,為屬性的所有描述
objc_property_t *properties = class_copyPropertyList([XXX class], &count);
for (int i = 0; i < count; i++) {
objc_property_t peoperty = properties[i];
const char *propertyName = property_getName(properties[i]);
NSString *name = [NSString stringWithUTF8String:propertyName];
NSLog(@"%@", name);
}
free(properties);
Category添加屬性(關(guān)聯(lián)對象)
- objc_setAssociatedObject
object -- 關(guān)聯(lián)的源對象
key -- 關(guān)聯(lián)用到的 key,key 值必須保證是一個對象級別的唯一常量
value -- 對象的key所關(guān)聯(lián)的變量的值掏秩,當(dāng)為 nil 時可清除一個已存在的關(guān)聯(lián)
policy -- 關(guān)聯(lián)策略该镣,Associative Object Behaviors - objc_getAssociatedObject
//一般不用 - objc_removeAssociatedObjects
//.h 文件
#import <Foundation/Foundation.h>
@interface NSObject (ExtentNSObject)
@property (nonatomic, copy)NSString *name;
@end
//.m 文件
#import "NSObject+ExtentNSObject.h"
#include <objc/runtime.h>
//取key值一般有三種推薦的方式
//static 的地址是唯一的
static char kAssociatedObjectNameKey1;
//
static void *kAssociatedObjectNameKey2 = &kAssociatedObjectNameKey2;
@implementation NSObject (ExtentNSObject)
- (NSString *)name {
// return objc_getAssociatedObject(self, &kAssociatedObjectNameKey1);
// return objc_getAssociatedObject(self, kAssociatedObjectNameKey2);
return objc_getAssociatedObject(self, _cmd);
}
- (void)setName:(NSString *)name {
// objc_setAssociatedObject(self, &kAssociatedObjectNameKey1, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
// objc_setAssociatedObject(self, kAssociatedObjectNameKey2, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end
添加方法
在 runtime 中為類在運行時添加一個方法
class_addMethod()會為類添加一個其父類的實現(xiàn)方法的重寫氏堤,但不會替換掉原來的方法的實現(xiàn)。也就是說捌归,如果類沒有重寫父類的實現(xiàn)方法, class_addMethod 會用指定的實現(xiàn)方法重寫父類的實現(xiàn)方法(相當(dāng)于重寫)柴梆,但如果子類重寫了父類的方法陨溅,則消息發(fā)送的還是重寫的方法
// SEL -- 方法名
// IMP -- 方法必須帶至少兩個參數(shù):self 和 _cmd
// typeEncoding -- 用來描述消息實現(xiàn)體的參數(shù)和返回值類型的順序:返回值 + 參數(shù)
class_addMethod(class,SEL, IMP, typeEncoding)
//TestModel 沒有重寫 NSObject 的 description 方法
TestModel *testModel = [[TestModel alloc] init];
//輸出為 <TestModel: 0x100202fa0>
NSLog(@"%@", [testModel description]);
NSObject *obj = [[NSObject alloc] init];
//輸出為 <NSObject: 0x100202500>
NSLog(@"%@", [obj description]);
class_addMethod([TestModel class], @selector(description), imp_implementationWithBlock(^NSString*(TestModel* self){
return @"hahaha";
}), "@@:");
//輸出為 hahaha
NSLog(@"%@", [testModel description]);
//輸出為 <NSObject: 0x100202500>
NSLog(@"%@", [obj description]);
typeEncoding
- (NSString *)description; ==> id id SEL
id OC對象在運行時都是OC對象
id 所有的事件都有一個隱含的參數(shù)self
SEL 所有的事件都有一個隱含的參數(shù)_cmd
typeEncoding 的對應(yīng)關(guān)系表
|type| id| SEL| void|
|:---:|:---:|:---:|:---:|
|Encoding| @| :| v|可用
method_getTypeEncoding()
獲取一個方法的 TypeEncoding
方法替代
如果類中有對應(yīng)的方法實現(xiàn)(不是其父類),則可以用 class_replaceMethod()
#import <Foundation/Foundation.h>
@interface NSObject (ExtentNSObject)
@end
#import "NSObject+ExtentNSObject.h"
#include <objc/runtime.h>
@implementation NSObject (ExtentNSObject)
- (NSString *)myDescription {
return @"hahaha";
}
@end
TestModel *testModel = [[TestModel alloc] init];
//輸出為 <TestModel: 0x100400800>
NSLog(@"%@", [testModel description]);
Method method = class_getInstanceMethod([NSObject class], @selector(myDescription));
IMP imp = method_getImplementation(method);
const char *typeEncoding = method_getTypeEncoding(method);
class_replaceMethod([NSObject class], @selector(description), imp, typeEncoding);
//輸出為 hahaha
NSLog(@"%@", [testModel description]);
這里用 category 擴(kuò)展的方法實現(xiàn) myDescription 代替了 NSObject 原本的 description
但是原本的 description 就被丟棄了
方法交換
method_exchangeImplementations() 交換了兩個方法的實現(xiàn)
#import <Foundation/Foundation.h>
@interface NSObject (ExtentNSObject)
@end
#import "NSObject+ExtentNSObject.h"
#include <objc/runtime.h>
@implementation NSObject (ExtentNSObject)
- (NSString *)myDescription {
//輸出為 selector: description
NSLog(@"selector: %@", NSStringFromSelector(_cmd));
//調(diào)用了 myDescription 的實現(xiàn)
return [NSString stringWithFormat:@"hahaha, %@", [self myDescription]];
}
@end
TestModel *testModel = [[TestModel alloc] init];
//輸出為 <TestModel: 0x100400800>
NSLog(@"%@", [testModel description]);
Method method = class_getInstanceMethod([NSObject class], @selector(myDescription));
Method m = class_getInstanceMethod([NSObject class], @selector(description));
method_exchangeImplementations(method, m);
//輸出為 hahaha, <TestModel: 0x100300d70>
NSLog(@"%@", [testModel description]);
Method Swizzle
Method Swizzle 要盡早的發(fā)生绍在,在方法調(diào)用之前
#import <Foundation/Foundation.h>
@interface NSObject (ExtentNSObject)
@end
#import "NSObject+ExtentNSObject.h"
#include <objc/runtime.h>
@implementation NSObject (ExtentNSObject)
+ (void)load {
Method method = class_getInstanceMethod([NSObject class], @selector(myDescription));
Method m = class_getInstanceMethod([NSObject class], @selector(description));
method_exchangeImplementations(method, m);
}
- (NSString *)myDescription {
//輸出為 selector: description
NSLog(@"selector: %@", NSStringFromSelector(_cmd));
//調(diào)用了 myDescription 的實現(xiàn)
return [NSString stringWithFormat:@"hahaha, %@", [self myDescription]];
}
@end
TestModel *testModel = [[TestModel alloc] init];
//輸出為 hahaha, <TestModel: 0x100400180>
NSLog(@"%@", [testModel description]);
Method Swizzle 最佳實踐
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method newMethod = class_getInstanceMethod([NSObject class], @selector(myDescription));
IMP newImp = method_getImplementation(newMethod);
const char *newType = method_getTypeEncoding(newMethod);
Method oldMethod = class_getInstanceMethod([NSObject class], @selector(description));
IMP oldImp = method_getImplementation(oldMethod);
const char *oldType = method_getTypeEncoding(oldMethod);
BOOL addMethod = class_addMethod([self class], @selector(description), newImp, newType);
if (addMethod) {
NSLog(@"method add");
class_replaceMethod([self class], @selector(myDescription), oldImp, oldType);
} else {
NSLog(@"method did not add");
method_exchangeImplementations(newMethod, oldMethod);
}
});
}
- (NSString *)myDescription {
// NSLog(@"selector: %@", NSStringFromSelector(_cmd));
return [NSString stringWithFormat:@"hahaha, %@", [self myDescription]];
// return @"hahaha";
}
- swizzling應(yīng)該只在+load中完成
在 Objective-C 的運行時中门扇,每個類有兩個方法都會自動調(diào)用。+load 是在一個類被初始裝載時調(diào)用偿渡,+initialize 是在應(yīng)用第一次調(diào)用該類的類方法或?qū)嵗椒ㄇ罢{(diào)用的臼寄。兩個方法都是可選的,并且只有在方法被實現(xiàn)的情況下才會被調(diào)用溜宽。 - swizzling 應(yīng)該只在 dispatch_once 中完成
由于 swizzling 改變了全局的狀態(tài)吉拳,所以我們需要確保每個預(yù)防措施在運行時都是可用的。原子操作就是這樣一個用于確保代碼只會被執(zhí)行一次的預(yù)防措施适揉,就算是在不同的線程中也能確保代碼只執(zhí)行一次留攒。
NSString
初始化方法
//語法糖
NSString *str = @“xxoo”;
NSString *str = [[NSString alloc] initWithUTF8String:"xxoo"];
NSString *str = [NSString stringWithFormat:@"哈哈哈 %@", @"xxoo"] ;
字符串比較
- isEqualToString:
// 返回值為NSComparisonResult類型的枚舉值
- compare:
- compare:options:
- compare:options:range:
BOOL result = [str1 compare:str2 options:NSCaseInsensitiveSearch] == 0;//大小寫無關(guān)相等
獲取長度
.length
查找和替換
// 返回值NSRange不是一個OC對象,它是一個結(jié)構(gòu)體嫉嘀,包含兩個屬性.location
和.length
炼邀,當(dāng)查找結(jié)果不存在時,.location的值為NSNotFound
- rangeOfString:
NSRange range = [str1 rangeOfString:@"xxoo"];
可修改子類 NSMutableString
//str不能修改原文剪侮,- stringByReplacingOccurrencesOfString: 是重新新建一個NSString對象并賦予實例變量
str1 = [str1 stringByReplacingOccurrencesOfString:@"xx" withString:@"ss"];
//NSMutableString可修改原文
\- replaceOccurrencesOfString:withString:options:range:
格式化字符串
//在OC輸出只要帶*的都可以用%@輸出OC變量
%@
%d
%f
%p
_cmd
__FUNCTION__
__FILE__
//文件名
__LINE__
// 行數(shù)
__PRETTY_FUNCTION__
// 類名與方法名
其它
NSStringFromClass
NSStringFromSelector
NSArray
創(chuàng)建
NSArray *d = @[@“a”, @“b”];
// 等同于
NSString *raw[] = {@“a”, @“b”};
NSArray *d = [NSArray arrayWithObjects:raw count:2];
NSArray *d = [[NSArray alloc] initWithObjects:@1, @2, nil];
//指明ObjectType
NSArray<NSString *> d = @[@“a”, @“b”];
放置非OC對象
NSArray只接受OC對象拭宁,因此非OC對象需要包裝
基本類型變量的包裝
- NSNumber
NSNumber 繼承與NAValue,用于包裝基本的數(shù)據(jù)類型:BOOl / char / double / float / int(NSInteger) / long / long long / short / unsigned char / unsigned int(NSUInteger) / unsigned long / unsigned long long / unsigned short
//語法糖
@1
//等同于
[NSNumber numberWithInteger:1]
//解包
[number integerValue];
- NSValue
NSValue 用于包裝一個C或OC的數(shù)據(jù)項瓣俯,比較常用的有
+ valueWithCGPoint:
+ valueWithCGSize:
+ valueWithCGRect:
+ valueWithRange: - 其它
const char *str = “haha”;
NSString *oc_str = @(str);
放置空對象
//Log打印輸出為1, 2, 3
NSArray *d = [[NSArray alloc] initWithObjects:@1, @2, @3, nil, @4, nil];
//NSNull也是一個類
//Log打印輸出為1, 2, 3, "<null>", 4
NSArray *d = [[NSArray alloc] initWithObjects:@1, @2, @3, [NSNull null], @4, nil];
取出元素
//語法糖
NSNumber *v = d[0];
// 等同于
NSNumber *v = [d objectAtIndex:0];
NOTE1: 如果取出的元素不存在(超出范圍)杰标,會導(dǎo)致崩潰
但使用 .firstObject
或 .lastObject
訪問則不會,為空則返回null
迭代
for-in
- enumerateObjectsUsingBlock:
查找
//如果不存在彩匕,返回NSNotFound腔剂, NSNotFound為最大值
- indexOfObject:
可修改子類 NSMutableArray
- addObject:
- addObjectsFromArray:
- insertObject:atIndex:
- insertObjects:atIndexes:
- removeObject:
- removeObjectAtIndex:
- removeObject:inRange:
- removeLastObject
- removeAllObjects
- replaceObjectAtIndex:withObject:
- sortUsingDescriptors:
- sortUsingComparator:
NSArray *sortedArray = [array sortedArrayUsingComparator: ^(id obj1, id obj2) {
if ([obj1 integerValue] > [obj2 integerValue]) {
return (NSComparisonResult)NSOrderedDescending;
}
if ([obj1 integerValue] < [obj2 integerValue]) {
return (NSComparisonResult)NSOrderedAscending;
}
return (NSComparisonResult)NSOrderedSame;
}];
NSIndexSet & NSMutableIndexSet
NSIndexSet 表示一個索引的集合(NSRange & NSInteger),一組index
NSMutableArray *d = [[NSMutableArray alloc] initWithObjects:@1, @2, @3, @4, @5, nil];
NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSetWithIndex:1];
[indexSet addIndexesInRange:NSMakeRange(3, 1)];
//等同于
//NSRange range;
//range.location = 3;
//range.length = 1;
//[indexSet addIndexesInRange:range];
//結(jié)果為:1, 3, 5
[d removeObjectsAtIndexes:indexSet];
NSDictionary
NSDictionary的 key 需實現(xiàn)<NSCoping>
協(xié)議的 - copyWithZone:
方法驼仪,value必須為oc對象
創(chuàng)建
// 語法糖
NSDictionary *fruitToPrice = @{@"蘋果" : @1.5, @"草莓" : @1.2};
// 等同于
NSNumber *nums[] = {@1.5, @1.2};
NSString *strs[] = {@"蘋果", @"梨子"};
NSDictionary *dic = [NSDictionary dictionaryWithObjects:(id *)nums forKeys:(id *)str count:2];
NSArray *nums = @[@1.5, @1.2];
NSArray *strs = @[@"蘋果", @"梨子"];
NSDictionary *dic = [NSDictionary dictionaryWithObjects:nums forKeys:strs];
NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:@1.5, @"蘋果", @1.2, @"梨子", nil];
// copyItems 為 NO 時為淺復(fù)制桶蝎,為 YES 時為深復(fù)制驻仅,集合里的每個對象都會收到 copyWithZone: 消息。如果集合里的對象遵循 NSCopying 協(xié)議登渣,那么對象就會被深復(fù)制到新的集合噪服。如果對象沒有遵循 NSCopying 協(xié)議,而嘗試用這種方法進(jìn)行深復(fù)制胜茧,會在運行時出錯粘优。
- initWithDictionary:copyItems:
迭代
// NSDictionary 是無序的
for-in
for (id key in dic.allKeys) {
id value = [dic objectForKey:key];
}
- enumerateKeysAndObjectsUsingBlock:
可修改子類 NSMutableDictionary
- setObject:forKey:
NSMutableDictionary *fruitToPrice = [NSMutableDictionary dictionaryWithObjectsAndKeys:@1.5, @"蘋果", nil];
[fruitToPrice setObject:@1.2 forKey:@"草莓"];
// 等同于
fruitToPrice[@"草莓"] = @1.2;
NSSet
NSSet 無序且各個元素互不相等
//.count == 4
NSSet *set = [NSSet setWithOnject:@1, @2, @3, @4, @1, nil];
NSString *str = @“a”;
NSString *str2 = @“a”;
// set.count == 1
// isEqual:和 hash 是NSSet用來判斷兩個對象是不是同一個對象
NSSet *set = [NSSet setWithObjects:str, str2, nil];
// 深復(fù)制 copyItems每一項都會執(zhí)行 - copyWithZone:
- initWithSet:copyItems:
可修改子類 NSMutableSet
NSString / NSNumber / NSArray / NSSet / NSDictionary 的相互轉(zhuǎn)換
NSString <=> NSNumber
NSString *str = @"18";
NSNumber *num = @([str integerValue]);
NSString *str = @"18abc”;
//18
NSNumber *num = @([str integerValue]);
NSString *str = @"abc”;
//0
NSNumber *num = @([str integerValue]);
NSString *str = nil;
//0
NSNumber *num = @([str integerValue]);
NSNumber *num2 = @18;
NSString *str2 = [num2 stringValue];
NSString <=> NSArray
NSString *str = @"a, b, c";
NSArray *array = [str componentsSeparatedByString:@","];
NSArray *array2 = @[@"a", @"b", @"c"];
NSString *str2 = [array2 componentsJoinedByString:@", "];
NSArray <=> NSSet
//去重
NSArray *array = @[@"a", @"b", @"c", @"a"];
NSSet *set = [[NSSet alloc] initWithArray:array];
NSSet *set2 = [[NSSet alloc] initWithObjects:@"a", @"b", @"c", nil];
NSMutableArray *array2 = [NSMutableArray array];
for (id value in set2) {
[array2 addObject:value];
}
NSArray <=> NSDicytionary
NSArray *array = @[@"蘋果", @"草莓"];
NSArray *array2 = @[@1.5, @1.2];
NSDictionary *dic = [NSDictionary dictionaryWithObjects:array2 forKeys:array];
NSArray *keys = dic.allKeys;
NSArray *values = dic.allValues;
Block
Block 的聲明和使用
int (^myBlock)(int) = ^(int num) {
return num * multiplier;
};
//形參
- (CGFloat)calcPriceWithDiscountHandler:(CGFloat(^)(Fruit *fruit))handler; // CGFloat(^)(Fruit *fruit) 指block的類型
//實參
[fruit calcPriceWithDiscountHandler:^CGFloat(Fruit *fruit) {
return 1.0;
}];
// typedef Block
typedef int(^myBlock)(NSString *name);
@property (nonatomic, copy) myBlock mBlock;
- 使用copy關(guān)鍵字,因為Block對象是在棧中創(chuàng)建的
- 可以使用其封閉作用域的所有變量
- 對捕獲的變量具有強引用循環(huán)呻顽,如果捕獲變量也對Block對象具有強引用雹顺,就會導(dǎo)致強引用循環(huán)
__wesk & __block
NSData
NSData 表示一塊內(nèi)存區(qū)域
NSString *str = @"abc";
NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
NSString *str2 = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSError
NSError 包含
- code 錯誤代碼,一般為 enum 內(nèi)容
- domain 錯誤分類
- userinfo 為 NSDictionary 類型廊遍,為 NSError 帶入更多信息
/+ errorWithDomain:code:userInfo:
NS_ENUM & NS_OPTIONS
typedef NS_ENUM(NSInteger, CYLSex) {
CYLSexMan,
CYLSexWoman
};