Objective-C (temp)

基本概念

  • 類( 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)如圖
    對象的結(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)存銷毀步驟

  1. 對象的引用計數(shù)變?yōu)榱?/li>
  • 對象正在被銷毀忧设,生命周期即將結(jié)束
  • 不能再有新的 __weak 弱引用,否則將指向 nil
  • 調(diào)用 [self dealloc]
  1. 父類對象調(diào)用 - dealloc
  • 繼承關(guān)系最底層的父類調(diào)用 - dealloc
  • 如果是 MRC 代碼颠通,則手動釋放實例變量們( ivars )
  • 繼承關(guān)系中的每一層的父類址晕,都在調(diào)用 - dealloc
  1. 根類對象 NSObject 調(diào)用 - dealloc
  • 調(diào)用 OC Runtime 中的 object_dispose() 方法
  1. 調(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),分三種情況:

  1. 不改變
  2. 在 Foundation 中創(chuàng)建str担孔,在CoreFoundation中手動管理
  3. 在 CoreFoundation 中創(chuàng)建str江锨,在Foundation中自動管理

原則:明確告知由哪個系統(tǒng)來管理內(nèi)存資源

  1. __bridge —— 跨層級使用,但不更改對象的擁有權(quán)
  2. __bridge_retained —— 內(nèi)存歸屬修改糕篇,需手動release
  3. __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ā)送該消息
@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 的聲明和使用

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

參考

  1. iOS 集合的深復(fù)制與淺復(fù)制
  2. Objective-C Associated Objects 的實現(xiàn)原理
  3. Method Swizzling
  4. 《招聘一個靠譜的iOS》面試題參考答案(上)
  5. 《招聘一個靠譜的iOS》面試題參考答案(下)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嬉愧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子喉前,更是在濱河造成了極大的恐慌没酣,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件卵迂,死亡現(xiàn)場離奇詭異裕便,居然都是意外死亡,警方通過查閱死者的電腦和手機见咒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進(jìn)店門偿衰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人改览,你說我怎么就攤上這事下翎。” “怎么了宝当?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵漏设,是天一觀的道長。 經(jīng)常有香客問我今妄,道長,這世上最難降的妖魔是什么鸳碧? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任盾鳞,我火速辦了婚禮,結(jié)果婚禮上瞻离,老公的妹妹穿的比我還像新娘腾仅。我一直安慰自己,他們只是感情好套利,可當(dāng)我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布推励。 她就那樣靜靜地躺著鹤耍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪验辞。 梳的紋絲不亂的頭發(fā)上稿黄,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天,我揣著相機與錄音跌造,去河邊找鬼杆怕。 笑死,一個胖子當(dāng)著我的面吹牛壳贪,可吹牛的內(nèi)容都是我干的陵珍。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼违施,長吁一口氣:“原來是場噩夢啊……” “哼互纯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起磕蒲,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤留潦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后亿卤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體愤兵,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年排吴,在試婚紗的時候發(fā)現(xiàn)自己被綠了秆乳。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡钻哩,死狀恐怖屹堰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情街氢,我是刑警寧澤扯键,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站珊肃,受9級特大地震影響荣刑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜伦乔,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一厉亏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧烈和,春花似錦爱只、人聲如沸招刹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至训柴,卻和暖如春哑舒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背畦粮。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工散址, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人宣赔。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓预麸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親儒将。 傳聞我的和親對象是個殘疾皇子吏祸,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容