OC語言基礎

第一課

1.對象方法和類方法區別

  • 對象方法
    • 對象方法是屬于對象的
    • 以減號-開頭
    • 只能讓對象調用,沒有對象,這個方法根本不可能被執行
    • 對象方法能訪問實例變量(成員變量)
    • 對象方法中可以調用當前對象的對象方法
    • 對象方法中可以調用其他對象的對象方法
    • 對象方法中不可以調用類方法
  • 類方法

    • 類方法是屬于類的
    • 以加號+開頭
    • 只能用類名調用,對象不能調用
    • 類方法中不能直接訪問實例變量(成員變量)
    • 類方法中不能直接調用對象方法,要想調用對象方法,必須創建或傳入對象。
  • 使用場合:

    • 當不需要訪問成員變量的時候,盡量用類方法
    • 類方法和對象方法可以同名

2.函數與方法對比

函數屬于整個文件,可以寫在文件中的任何位置,包括@implementation...@end中,但寫在 @interface...@end會無法識別,函數的聲明可以在main函數內部也可以在main函數外部。

3.通過類創建對象

通過類創建對象

  • 1.開辟存儲空間, 通過new方法創建對象會在堆 內存中開辟一塊存儲空間

  • 2.初始化所有屬性

  • 3.返回指針地址

  • 創建對象的時候返回的地址其實就是類的第0個屬性的地址

  • 但是需要注意的是: 類的第0個屬性并不是我們編寫的_age, 而是一個叫做isa的屬性

  • isa是一個指針, 占8個字節

  • 其實類也是一個對象, 也就意味著Person也是一個對象

  • 平時我們所說的創建對象其實就是通過一個 類對象 來創建一個 新的對象

  • 類對象是系統自動幫我們創建的, 里面保存了當前對象的所有方法

  • 而實例對象是程序自己手動通過new來創建的, 而實例對象中有一個isa指針就指向了創建它的那個類對象



  • 寫在類聲明的大括號中的變量, 我們稱之為 成員變量(屬性, 實例變量)
  • 成員變量只能通過對象來訪問
  • 注意: 成員變量不能離開類, 離開類之后就不是成員變量 ,成員變量不能在定義的同時進行初始化
  • 存儲: 堆(當前對象對應的堆的存儲空間中)
  • 存儲在堆中的數據, 不會被自動釋放, 只能程序員手動釋放


第二課

1.對象和方法之間的關系

對象作為方法參數傳遞是地址傳遞,因為對象是一個指針變量
在方法內部,可以通過對象形參,訪問該對象的成員變量(如果該對象的該成員變量的訪問權限是public的)
在方法內部,可以通過對象形參,調用該對象上的方法(給這個對象發送消息)

2.xcode項目模版的修改

  • 1.應用程序中,找到Xcode, 右鍵"顯示包內容"
    • 打開"/Applications/Xcode.app/Contents/Developer/Library/Xcode/Templates/Project\ Templates/Mac/Application" 文件夾

      • 在/Application文件夾中能夠找到所有和OS X Application界面對應的文件夾
    • 修改Command Line Tool模板

      • 打開"Command Line Tool.xctemplate"文件夾, 發現和"改Command Line Tool模板"一一對應
      • 打開"TemplateInfo.plist文件"發現和"改Command Line Tool模板"中內容對應

3.修改類的頭部信息

  • 找到對應類對應的類文件模板. (因為類是創建項目之后手動創建的, 而不是隨著項目的創建自動創建的, 所以修改類文件模板和項目模板并不是修改同一個文件)
    • 打開"/Applications/Xcode.app/Contents/Developer/Library/Xcode/Templates File Templates/Source/Cocoa Class.xctemplate"文件夾

4.NSString的基本使用

// C語言中的字符串不是對象
    char *name1 = "lnj";
    char name2[] = "lmj";
    
    // OC中的字符串是一個對象
    // 正是因為OC中的字符串是一個對象, 所以它就具備了很多功能
    NSString *str = @"lk";
    
    Iphone *p = [Iphone new];
    // 注意: 輸出C語言的字符串使用%s
    //      輸出OC的字符串使用%@,  %@就專門用于輸出對象類型的
//    NSLog(@"content = %s", [p loadMessage]);
    NSLog(@"content = %@", [p loadMessage]);

第三課

1.self關鍵字

  • 類方法中可以直接調用類方法
  • 類方法中不可以直接調用對象方法
  • 類方法中不能訪問成員變量
+ (void)carameWithFlahlightStatus:(FlahlightStatus)status
{
    if (status == kFlahlightStatusOpen) {
//        [Iphone openFlahlight];
        ** // 其實在類方法中調用類方法除了可以使用類名調用以外, 還可以使用self來調用**
        [self openFlahlight];
    }else
    {
//        [Iphone closeFlahlight];
        // self == Iphone
        [self closeFlahlight];
    }
    NSLog(@"拍照");
    
}

  • 如果self在對象方法中, 那么self就代表調用當前對象方法的那個對象
  • 如果self在類方法中, 那么self就代表調用當前類方法的那個類
  • 總結:
    • 我們只用關注self在哪一個方法中 , 如果在類方法那么就代表當前類, 如果在對象方法那么就代表"當前調用該方法的對象"
  • 注意:

    • self會自動區分類方法和對象方法, 如果在類方法中使用self調用對象方法, 那么會直接報錯
    • 不能在對象方法或者類方法中利用self調用當前self所在的方法
  • 使用場景:

    • 可以用于在對象方法之間進行相互調用
    • 可以用于在類方法之間進行相互調用
    • 可以用于區分成員變量和局部變量同名的情況

2.多態

  • 什么是多態:

  • 事物的多種表現形態

  • 在程序中如何表現:

  • 父類指針指向子類對象

  • 優點:

  • 提高了代碼的擴展性

  • 注意點:

  • 如果父類指針指向子類對象, 如果需要調用子類特有的方法, 必須先強制類型轉換為子類才能調用

多態: 事物的多種表現形態
動態類型: 在編譯的時候編譯器只會檢查當前類型對應的類中有沒有需要調用的方法
在運行時,系統會自動判斷a1的真實類型

  Animal *a1 = [Dog new];
    [a1 eat];

注意點: 在多態中, 如果想調用子類特有的方法必須強制類型轉換為子類才能調用

3.description關鍵字

  • %@是用來打印對象的, 其實%@的本質是用于打印字符串
  • 只要利用%@打印某個對象, 系統內部默認就會調用父類的description方法
  • 調用該方法, 該方法會返回一個字符串, 字符串的默認格式 <類的名稱: 對象的地址>

第四課

1.OC中的私有方法

OC中的私有變量

  • 在類的實現即.m文件中也可以聲明成員變量,但是因為在其他文件中通常都只是包含頭文件而不會包含實現文件,所以在.m文件中聲明的成員變量是@private的。在.m中定義的成員變量不能和它的頭文件.h中的成員變量同名,在這期間使用@public等關鍵字也是徒勞的。
@implementation Dog
{
    @public
    int _age;
}
@end

2.OC中的私有方法

  • 私有方法:只有實現沒有聲明的方法

  • 原則上:私有方法只能在本類的方法中才能調用。

    • 注意: OC中沒有真正的私有方法

3.id類型

  • 我們知道NSObject是OC中的基類

  • 那么任何對象的NSObject類型的指針可以指向任意對象,都沒有問題

  • 但是NSObject是靜態類型,如果通過它直接調用NSObject上面不存在的方法,編譯器會報錯。

  • 你如果想通過NSObject的指針調用特定對象的方法,就必須把NSObject * 這種類型強轉成特定類型。然后調用。如下

    //定義NSObject * 類型
     NSObject* obj = [Cat new];
     Cat *c = (Cat*)obj;
     [c eat];
    
  • id 是一種通用的對象類型,它可以指向屬于任何類的對象,也可以理解為萬能指針 ,相當于C語言的 void *

  • 因為id是動態類型,所以可以通過id類型直接調用指向對象中的方法, 編譯器不會報錯

```
/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;
```


```
 id obj = [C at new];
 [obj eat]; // 不用強制類型轉換

 [obj test]; //可以調用私有方法
```
  • 注意:
    • 在id的定義中,已經包好了*號。id指針只能指向OC中的對象
    • 為了盡可能的減少編程中出錯,Xcode做了一個檢查,當使用id 類型的調用本項目中所有類中都沒有的方法,編譯器會報錯
    • id類型不能使用.語法, 因為.語法是編譯器特性, 而id是運行時特性
  • id == NSObject * 萬能指針
  • id和NSObject *的區別:
  • NSObject *是一個靜態數據類型
  • id 是一個動態數據類型
  • 通過靜態數據類型定義變量, 不能調用子類特有的方法(需要強制轉成子類類型才能調用方法)

  • 通過動態數據類型定義變量, 可以調用子類特有的方法

  • 通過動態數據類型定義的變量, 可以調用私有方法

    • 弊端: 由于動態數據類型可以調用任意方法, 所以有可能調用到不屬于自己的方法, 而編譯時又不會報錯, 所以可能導致運行時的錯誤
    • 應用場景: 多態, 可以減少代碼量, 避免調用子類特有的方法需要強制類型轉換
  • 為了避免動態數據類型引發的運行時的錯誤, 一般情況下如果使用動態數據類型定義一個變量, 在調用這個對象的方法之前會進行一次判斷, 判斷當前對象是否能夠調用這個方法

id obj = [Student new];
    /*
    if ([obj isKindOfClass:[Student class]]) {
        // isKindOfClass , 判斷指定的對象是否是某一個類, 或者是某一個類的子類
        [obj eat];
    }
     */
   
    if ([obj isMemberOfClass:[Student class]]) {
        // isMemberOfClass : 判斷指定的對象是否是當前指定的類的實例
        [obj eat];
    }

4.構造方法

重寫init方法

- (id)init {
    self = [super init];
    if (self) {
        // Initialize self.
    }
    return self;
}
  • 重寫init方法其它格式
- (id)init {
    if (self = [super init]) {
        // Initialize self.
    }
    return self;
}

構造方法使用注意

  • 子類擁有的成員變量包括自己的成員變量以及從父類繼承而來的成員變量,在重寫構造方法的時候應該首先對從父類繼承而來的成員變量先進行初始化。

  • 原則:先初始化父類的,再初始化子類的。

  • 先調用父類的構造方法[super init];

  • 再進行子類內部成員變量的初始化。

  • 千萬不要把self = [super init]寫成self == [super init]

  • 重寫構造方法的目的:為了讓對象方法一創建出來,成員變量就會有一些固定的值。

  • (1)自己做自己的事情

  • (2)父類的屬性交給父類的方法來處理,子類的方法處理子類自己獨有的屬性

  • 自定義構造方法必須以initWith開頭,并且’W’必須大寫

5.自定義類工廠方法

  • 由于父類的類工廠方法創建實例對象時是使用父類的類創建的, 所以如果子類調用父類的類工廠方法創建實例對象,創建出來的還是父類的實例對象
  • 為了解決這個問題, 以后在自定義類工廠時候不要利用父類創建實例對象, 改為使用self創建, 因為self誰調用當前方法self就是誰

@interface Person : NSObject
+ (id)person;
@end

@implementation Person
+ (id)person
{
//   return  [[Person alloc]init];
//     誰調用這個方法,self就代表誰
//    注意:以后寫類方法創建初始化對象,寫self不要直接寫類名
    return  [[self alloc]init];
}
@end

@interface Student : Person
@property NSString *name;
@end

@implementation Student
@end

int main(int argc, const char * argv[])
{
    Student *stu = [Student person];// [[Student alloc] init]
    [stu setName:@"lnj"];
}

6.類的本質

  • 類的本質其實也是一個對象(類對象)
  • 程序中第一次使用該類的時候被創建,在整個程序中只有一份。
  • 此后每次使用都是這個類對象,它在程序運行時一直存在。
  • 類對象是一種數據結構,存儲類的基本信息:類大小,類名稱,類的版本,繼承層次,以及消息與函數的映射表等
  • 類對象代表類,Class類型,對象方法屬于類對象
  • 如果消息的接收者是類名,則類名代表類對象
  • 所有類的實例都由類對象生成,類對象會把實例的isa的值修改成自己的地址,每個實例的isa都指向該實例的類對象

7.類的啟動過程

+load方法

  • 在程序啟動的時候會加載所有的類和分類,并調用所有類和分類的+load方法(只會調用一次)
  • 先加載父類,再加載子類;也就是先調用父類的+load,再調用子類的+load
  • 先加載元原始類,再加載分類
  • 不管程序運行過程有沒有用到這個類,都會調用+load加載
```
@implementation Person

+ (void)load
{
    NSLog(@"%s", __func__);
}
@end

@implementation Student : Person

+ (void)load
{
    NSLog(@"%s", __func__);
}
@end

輸出結果:
+[Person load]
+[Student load]
```

+initialize

  • 在第一次使用某個類時(比如創建對象等),只會調用一次+initialize方法
  • 一個類只會調用一次+initialize方法,先調用父類的,再調用子類的

8.property的增強

  • 如果利用@property來生成getter/setter方法, 那么我們可以不寫成員變量, 系統會自動給我們生成一個_開頭的成員變量

  • 注意: @property自動幫我們生成的成員變量是一個私有的成員變量, 也就是說是在.m文件中生成的, 而不是在.h文件中生成的

  • 如果重寫了setter方法, 那么property就只會生成getter方法

  • 如果重寫了getter方法, 那么property就只會生成setter方法

  • 如果同時重寫了getter/setter方法, 那么property就不會自動幫我們生成私有的成員變量

9.SEL類型

  • a.SEL類型的第一個作用, 配合對象/類來檢查對象/類中有沒有實現某一個方法
```
    SEL sel = @selector(setAge:);
    Person *p = [Person new];
    // 判斷p對象中有沒有實現-號開頭的setAge:方法
    // 如果P對象實現了setAge:方法那么就會返回YES
    // 如果P對象沒有實現setAge:方法那么就會返回NO
    BOOL flag = [p respondsToSelector:sel];
    NSLog(@"flag = %i", flag);
```
  • b.SEL類型的第二個作用, 配合對象/類來調用某一個SEL方法
```
   SEL sel1 = @selector(signalWithNumber:);
    // withObject: 需要傳遞的參數
    // 注意: 如果通過performSelector調用有參數的方法, 那么參數必須是對象類型,
    // 也就是說方法的形參必須接受的是一個對象, 因為withObject只能傳遞一個對象
    [p performSelector:sel1 withObject:@"13838383438"];
    
    SEL sel2 = @selector(setAge:);
    [p performSelector:sel2 withObject:@(5)];
    NSLog(@"age = %i", p.age);
    
    // 注意:performSelector最多只能傳遞2個參數
```
  • c.配合對象將SEL類型作為方法的形參
```
/    Car *c = [Car new];
//    SEL sel = @selector(run);
//    
//    Person *p = [Person new];
//    [p makeObject:c andSel:sel];
```

第五課

1.內存管理

  • 只有OC對象才需要進行內存管理的本質原因

    • OC對象存放于堆里面

    • 非OC對象一般放在棧里面(棧內存會被系統自動回收)

    • OC中的ARC和java中的垃圾回收機制不太一樣, java中的垃圾回收是系統干得, 而OC中的ARC是編譯器干得

2.堆和棧

  • 棧(操作系統):由操作系統自動分配釋放,存放函數的參數值,局部變量的值等。其操作方式類似于數據結構中的棧(先進后出);

  • 堆(操作系統):一般由程序員分配釋放,若程序員不釋放,程序結束時可能由OS回收,分配方式類似于鏈表。

3.dealloc方法

  • dealloc方法的重寫

    • 一般會重寫dealloc方法,在這里釋放相關資源,dealloc就是對象的遺言
    • 一旦重寫了dealloc方法, 就必須調用[super dealloc],并且放在最后面調用
  • 使用注意

    • 不能直接調用dealloc方法
    • 一旦對象被回收了, 它占用的內存就不再可用,堅持使用會導致程序崩潰(野指針錯誤)

4.野指針/空指針

  • 1.僵尸對象

    • 已經被銷毀的對象(不能再使用的對象)
  • 2.野指針

    • 指向僵尸對象(不可用內存)的指針
    • 給野指針發消息會報EXC_BAD_ACCESS錯誤
  • 3.空指針

    • 沒有指向存儲空間的指針(里面存的是nil, 也就是0)
    • 給空指針發消息是沒有任何反應的
  • 為了避免野指針錯誤的常見辦法

    • 在對象被銷毀之后, 將指向對象的指針變為空指針
  • 只要一個對象被釋放了, 我們就稱這個對象為 "僵尸對象"

  • 當一個指針指向一個僵尸對象, 我們就稱這個指針為野指針

  • 只要給一個野指針發送消息就會報錯

5.多對象內存管理原則

  • 管理規律:

    只要還有人在用某個對象,那么這個對象就不會被回收
    只要你想用這個對象,就讓對象的計數器+1
    當你不再使用這個對象時,就讓對象的計數器-1

在mrc環境下

// 當A對象想使用B對象一定要對B對象進行一次retain, 這樣才能保證A對象存在B對象就存在, 也就是說這樣才能保證無論在什么時候在A對象中都可以使用B對象
// 當A對象釋放的時候, 一定要對B對應進行一次release, 這樣才能保證A對象釋放了, B對應也會隨之釋放, 避免內存泄露
// 總結一句話: 有增就有減


換房了, 如果set方法中沒有release舊值, 就會內存泄露
- (void)setRoom:(Room *)room // room = r
{
    // 只有房間不同才需用release和retain
    if (_room != room) {// 0ffe1 != 0ffe1
        
        // 將以前的房間釋放掉 -1
        [_room release];
        
        /*
        // 對房間的引用計數器+1
        [room retain];
        
        _room = room;
         */
        // retain不僅僅會對引用計數器+1, 而且還會返回當前對象
        _room = [room retain];
    }
}

- (void)dealloc
{
    // 人釋放了, 那么房間也需要釋放
    [_room release];
    NSLog(@"%s", __func__);
    [super dealloc];
}

6.class和#import

作用上的區別

  • import會包含引用類的所有信息(內容),包括引用類的變量和方法
  • @class僅僅是告訴編譯器有這么一個類, 具體這個類里有什么信息, 完全不知

  • 效率上的區別

  • 如果有上百個頭文件都#import了同一個文件,或者這些文件依次被#import,那么一旦最開始的頭文件稍有改動,后面引用到這個文件的所有類都需要重新編譯一遍 , 編譯效率非常低

  • 相對來講,使用@class方式就不會出現這種問題了

  • 總結:
    • 如果兩個類相互拷貝, 例如A拷貝B, B拷貝A, 這樣會報錯
    • 如何解決: 在.h中用@class, 在.m中用import
    • 因為如果.h中都用import, 那么A拷貝B, B又拷貝A, 會形成死循環
    • 如果在.h中用@class, 那么不會做任何拷貝操作, 而在.m中用import只會拷貝對應的文件, 并不會形成死循環

7.property修飾符

  • 1.相同類型的property修飾符不能同時使用
  • 2.不同類型的property修飾符可以多個結合在一起使用, 多個之間用,號隔開
  • 3.iOS開發中只要寫上property, 那么就立刻寫上nonatomic
/*
retain: 就會自動幫我們生成getter/setter方法內存管理的代碼
assign: 不會幫我們生成set方法內存管理的代碼, 僅僅只會生成普通的getter/setter方法, 默認什么都不寫就是assign
*/
@property(nonatomic, retain) Room *room;

第六課

1.autorelease

  • 并不是放到自動釋放池代碼中,都會自動加入到自動釋放池

     @autoreleasepool {
        // 因為沒有調用 autorelease 方法,所以對象沒有加入到自動釋放池
        Person *p = [[Person alloc] init];
        [p run];
    }
    
  • 在自動釋放池的外部發送autorelease 不會被加入到自動釋放池中

  • autorelease是一個方法,只有在自動釋 放池中調用才有效。

     @autoreleasepool {
     }
     // 沒有與之對應的自動釋放池, 只有在自動釋放池中調用autorelease才會放到釋放池
     Person *p = [[[Person alloc] init] autorelease];
     [p run];
    
     // 正確寫法
      @autoreleasepool {
        Person *p = [[[Person alloc] init] autorelease];
     }
    
     // 正確寫法
     Person *p = [[Person alloc] init];
      @autoreleasepool {
        [p autorelease];
     }
    
  • 自動釋放池是以棧的形式存在

    • 由于棧只有一個入口, 所以調用autorelease會將對象放到棧頂的自動釋放池
    • 棧頂就是離調用autorelease方法最近的自動釋放池
    ```
    @autoreleasepool { // 棧底自動釋放池
      @autoreleasepool {
          @autoreleasepool { // 棧頂自動釋放池
              Person *p = [[[Person alloc] init] autorelease];
          }
          Person *p = [[[Person alloc] init] autorelease];
      }
    }
    ```

2.ARC(Automatic Reference Counting)

ARC的判斷原則

  • ARC的判斷原則

    • 只要還有一個強指針變量指向對象,對象就會保持在內存中
  • 強指針

    • 默認所有指針變量都是強指針
    • 被__strong修飾的指針
  • 弱指針

    • 被__weak修飾的指針

注意:當使用ARC的時候,暫時忘記“引用計數器”,因為判斷標準變了。

  • ARC機制下有幾個明顯的標志:
    • 不允許調用對象的 release方法
    • 不允許調用 autorelease方法
    • 再重寫父類的dealloc方法時,不能再調用 [super dealloc];

ARC下多對象內存管理

  • ARC和MRC一樣, 想擁有某個對象必須用強指針保存對象, 但是不需要在dealloc方法中release
```
@interface Person : NSObject

// MRC寫法
//@property (nonatomic, retain) Dog *dog;

// ARC寫法
@property (nonatomic, strong) Dog *dog;

@end
```
  • ARC和MRC一樣, 如果A擁有B, B也擁有A, 那么必須一方使用弱指針
@interface Person : NSObject

//@property (nonatomic, retain) Dog *dog;
@property (nonatomic, strong) Dog *dog;

@end

@interface Dog : NSObject

// 錯誤寫法, 循環引用會導致內存泄露
//@property (nonatomic, strong) Person *owner;

// 正確寫法, 當如果保存對象建議使用weak
//@property (nonatomic, assign) Person *owner;
@property (nonatomic, weak) Person *owner;
@end
  • ARC下@property參數

    • strong : 用于OC對象, 相當于MRC中的retain

    • weak : 用于OC對象, 相當于MRC中的assign

    • assign : 用于基本數據類型, 跟MRC中的assign一樣

    • 在ARC中如果保存對象不要用assign, 用weak

    • assign是專門用于保存基本數據類型的, 如果保存對象用weak

3.ARC模式下如何兼容非ARC的類

  • 轉變為非ARC -fno-objc-arc
  • 轉變為ARC的, -f-objc-arc (不常用)

4.Category

  • 分類只能增加方法, 不能增加成員變量
  • 分類中寫property只會生成方法聲明,不會生成實現和私有成員變量
  • 分類可以訪問原來類中的成員變量
  • 如果分類和原來類出現同名的方法, 優先調用分類中的方法, 原來類中的方法會被忽略
  • 方法調用的優先級(從高到低)
    • 分類(最后參與編譯的分類優先)
    • 原來類
    • 父類

5.類擴展(Class Extension)

  • 延展類別又稱為擴展(Extendsion),Extension是Category的一個特例

  • 可以為某個類擴充一些私有的成員變量和方法

    • 寫在.m文件中
    • 英文名是Class Extension
    • 對比分類, 就少了一個分類名稱,因此也有人稱它為”匿名分類”

6.Block

Block的定義格式

//定義的格式類似c語言中指向函數的指針

返回值類型 (^block變量名)(形參列表) = ^(形參列表) {

};

7.Block的注意事項

  • 1.block中可以訪問外面的變量

  • 2.block中可以定義和外界同名的變量, 并且如果在block中定義了和外界同名的變量, 在block中訪問的是block中的變量

  • 3.默認情況下, 不可以在block中修改外界變量的值

    • 因為block中的變量和外界的變量并不是同一個變量
    • 如果block中訪問到了外界的變量, block會將外界的變量拷貝一份到堆內存中
    • 因為block中使用的外界變量是copy的, 所以在調用之前修改外界變量的值, 不會影響到block中copy的值
  • 如果想在block中修改外界變量的值, 必須在外界變量前面加上__block,如果沒有添加__block是值傳遞

  • 如果在block中修改了外界變量的值, 會影響到外界變量的值,如果加上__block之后就是地址傳遞, 所以可以在block中修改外界變量的值

  • block是存儲在堆中還是棧中
  • 默認情況下block存儲在棧中, 如果對block進行一個copy操作, block會轉移到堆中
  • 如果block在棧中, block中訪問了外界的對象, 那么不會對對象進行retain操作
  • 但是如果block在堆中, block中訪問了外界的對象, 那么會對外界的對象進行一次retain
  • 如果在block中訪問了外界的對象, 一定要給對象加上__block, 只要加上了__block, 哪怕block在堆中, 也不會對外界的對象進行retain
  • 如果是在ARC開發中就需要在前面加上__weak

第七課

1.protocol

  • 繼承之后默認就有實現, 而protocol只有聲明沒有實現

  • 相同類型的類可以使用繼承, 但是不同類型的類只能使用protocol

  • protocol可以用于存儲方法的聲明, 可以將多個類中共同的方法抽取出來, 以后讓這些類遵守協議即可

  • protocol 的使用注意

    • Protocol:就一個用途,用來聲明一大堆的方法(不能聲明成員變量),不能寫實現。
    • 只要父類遵守了某個協議,那么子類也遵守。
    • OC不能繼承多個類(單繼承)但是能夠遵守多個協議。繼承(:),遵守協議(< >)
    • 協議可以遵守協議,一個協議遵守了另一個協議,就可以擁有另一份協議中的方法聲明
  • NSObject是一個基類,最根本最基本的類,任何其他類最終都要繼承它

  • 還有名字也叫NSObject的協議,它是一個基協議,最根本最基本的協議

  • NSObject協議中聲明很多最基本的方法

  • 建議每個新的協議都要遵守NSObject協議

@protocol SportProtocol <NSObject> // 基協議

- (void)playFootball;
- (void)playBasketball;
@end
  • 協議中有2個關鍵字可以控制方法是否要實現(默認是@required,在大多數情況下,用途在于程序員之間的交流)
* @required:這個方法必須要實現(若不實現,編譯器會發出警告)
* @optional:這個方法不一定要實現
  • 協議的編寫規范:
  • 1.一般情況下, 當前協議屬于誰, 我們就將協議定義到誰的頭文件中
  • 2.協議的名稱一般以它屬于的那個類的類名開頭, 后面跟上protocol或者delegate
  • 3.協議中的方法名稱一般以協議的名稱protocol之前的作為開頭
  • 4.一般情況下協議中的方法會將觸發該協議的對象傳遞出去
  • 5.一般情況下一個類中的代理屬于的名稱叫做 delegate
  • 6.當某一個類要成為另外一個類的代理的時候, 一般情況下在.h中用@protocol 協議名稱;告訴當前類 這是一個協議.在.m中用#import真正的導入一個協議的聲明

2.代理設計模式

  • 代理設計模式的場合:

    • 當對象A發生了一些行為,想告知對象B(讓對象B成為對象A的代理對象)
    • 對象B想監聽對象A的一些行為(讓對象B成為對象A的代理對象)
    • 當對象A無法處理某些行為的時候,想讓對象B幫忙處理(讓對象B成為對象A的代理對象)

3.NSString

  • 通過不同的方式創建字符串,字符串對象儲存的位置也不一樣

如果是通過字符串常量創建,那么字符串對象存儲在常量區中
如果是通過alloc initWithFormat/stringWithFormat創建,那么字符串對象存儲在堆區中
而且需要注意:
不同的平臺存儲的方式也不一樣,如果是Mac平臺系統會自動對字符串對象進行優化,但是如果是iOS平臺就是兩個對象
不同的編譯器存儲的方式也不一樣,如果是Xcode6以下并且是在iOS平臺,那么每次alloc都會創建一個新的對象,如果是在Xcode6以上那么alloc多次指向同一塊存儲空間

  • 注意:一般情況下,只要是通過alloc或者類工廠方法創建的對象,每次都會在堆內存中開辟一塊新的存儲空間
  • 但是如果是通過alloc的initWithString方法除外,因為這個方法是通過copy返回一個字符串對象給我們
  • 而copy又分為深拷貝和淺拷貝,如果是深拷貝會創建一個新的對象,如果是淺拷貝不會創建一個新的對象,而是直接返回被拷貝的對象的地址給我們
  • 首字母變大寫,其他字母都變小寫

    • (NSString *)capitalizedString
  • 字符串搜索

    • (BOOL)hasPrefix:(NSString *)aString;

    • 是否以aString開頭

    • (BOOL)hasSuffix:(NSString *)aString;

    • 是否以aString結尾

    • (NSRange)rangeOfString:(NSString *)aString;

    • 用來檢查字符串內容中是否包含了aString

    • 如果包含, 就返回aString的范圍

    • 如果不包含, NSRange的location為NSNotFound, length為0

  • 補充:NSRange的創建

  • NSRange是Foundation框架中比較常用的結構體, 它的定義如下:

```
typedef struct _NSRange {
    NSUInteger location;
    NSUInteger length;
} NSRange;
// NSUInteger的定義
typedef unsigned int NSUInteger;
```

```
NSRange range = NSMakeRange(7, 3);
```
  • (BOOL)isAbsolutePath;

    • 是否為絕對路徑
  • (NSString *)lastPathComponent;

    • 獲得最后一個目錄
  • (NSString *)stringByDeletingLastPathComponent;

    • 刪除最后一個目錄
  • (NSString *)pathExtension;

    • 獲得拓展名
  • 轉為C語言中的字符串

  • (char *)UTF8String;

    NSString *str = @"abc";
    const char *cStr = [str UTF8String];
    NSLog(@"cStr = %s", cStr);
    
    
    char *cStr = "abc";
    NSString *str = [NSString stringWithUTF8String:cStr];
    NSLog(@"str = %@", str);
    
    

4.NSMutableString

  • 不可變:指的是字符串在內存中占用的存儲空間固定,并且存儲的內容不能發生變化

  • 可變:指的是字符串在內存中占用的存儲空間可以不固定,并且存儲的內容可以被修改

5.NSArray

  • NSArray的使用注意

    • 只能存放任意OC對象, 并且是有順序的
    • 不能存儲非OC對象, 比如int\float\double\char\enum\struct等
    • 它是不可變的,一旦初始化完畢后,它里面的內容就永遠是固定的, 不能刪除里面的元素, 也不能再往里面添加元素
  • NSArray直接使用NSLog()作為字符串輸出時是小括號括起來的形式。

    • NSArray中不能存儲nil,因為NSArray認為nil是數組的結束(nil是數組元素結束的標記)。nil就是0。0也是基本數據類型,不能存放到NSArray中。
```
    NSArray *arr = [NSArray arrayWithObjects:@"lnj", nil ,@"lmj",@"jjj", nil];
    NSLog(@"%@", arr);
輸出結果:
(
    lnj
)
```
// 創建一個空的數組
    NSMutableArray *arrM = [NSMutableArray array];
    NSLog(@"%@", arrM);
    // 如何添加
    [arrM addObject:@"lnj"];
    // 將指定數組中的元素都取出來, 放到arrM中
    // 并不是將整個數組作為一個元素添加到arrM中
    [arrM addObjectsFromArray:@[@"lmj", @"jjj"]];
    // 注意: 以下是將整個數組作為一個元素添加
//    [arrM addObject:@[@"lmj", @"jjj"]];
    NSLog(@"%@", arrM);

第八課

1.NSDictionary

  • 鍵值對集合的特點

    • 字典存儲的時候,必須是"鍵值對"的方式來存儲(同時鍵不要重復)
    • 鍵值對中存儲的數據是"無序的".
    • 鍵值對集合可以根據鍵, 快速獲取數據.
  • NSArray和NSDictionary的區別

    • NSArray是有序的,NSDictionary是無序的
    • NSArray是通過下標訪問元素,NSDictionary是通過key訪問元素
  • NSArray的用法

    • 創建
    ```
    @[@"Jack", @"Rose"] (返回是不可變數組)
    
    ```
    
* NSDictionary的用法 +創建

     ```
           
        NSDictionary *dict = @{key:value};
        NSDictionary *dict = @{@"name": @"lnj"};
        NSLog(@"%@", dict[@"name"]);
            
        NSDictionary *dict = @{@"name":@"lnj", @"age":@"30", @"height":@"1.75"};

        @{ @"name" : @"Jack", @"phone" : @"10086" } (返回是不可變字典)
   
   
* 字典的遍歷

        
    ```
    for (int i = 0; i < dict.count; ++i) {
    // 獲取字典中所有的key
    NSArray *keys = [dict allKeys];
    // 取出當前位置對應的key

// NSLog(@"%@", keys[i]);
NSString *key = keys[i];
NSString *value = dict[key];
NSLog(@"key = %@, value = %@", key, value);
}

    ```
    
    * **字典的遍歷(2)(常用)**

    
    ```
    //如何通過forin遍歷字典, 會將所有的key賦值給前面的obj

for (NSString *key in dict) {
NSLog(@"%@", key);
NSString *value = dict[key];
NSLog(@"key = %@, value = %@", key, value);
}
```

    * 字典的遍歷(3)
   [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
        NSLog(@"key = %@, value = %@", key, obj);
    }];

注意:

  • 如果是不可變數組, 那么key不能相同
  • 如果是不可變字典出現了同名的key, 那么后面的key對應的值不會被保存
  • 如果是在可變數組中, 后面的會覆蓋前面的

2.NSNumber

  • NSNumber的創建

    • 以前
    ```
    + (NSNumber *)numberWithInt:(int)value;
    + (NSNumber *)numberWithDouble:(double)value;
    + (NSNumber *)numberWithBool:(BOOL)value;
    
    ```   
* 現在


    ```
       @10;
       @10.5;
       @YES;
       @(num);
    ```
    
* 基本數據類型轉換對象類型簡寫
    * 注意: 如果傳入的是變量那么必須在@后面寫上(), 如果傳入的常量, 那么@后面的()可以省略

        ```    
            NSNumber *temp = @(number);
            NSNumber *temp  =@10.10;
            NSLog(@"%@", temp);
        ```

3.NSValue

  • NSNumber是NSValue的子類, 但NSNumber只能包裝數字類型

  • NSValue可以包裝任意值

    • 因此, 可以用NSValue將結構體包裝后,加入NSArray\NSDictionary中
  • 為了方便 結構體 和NSValue的轉換,Foundation提供了以下方法

  • 將結構體包裝成NSValue對象

    + (NSValue *)valueWithPoint:(NSPoint)point;
    + (NSValue *)valueWithSize:(NSSize)size;
    + (NSValue *)valueWithRect:(NSRect)rect;
    
  • 從NSValue對象取出之前包裝的結構體

    - (NSPoint)pointValue;
    - (NSSize)sizeValue;
    - (NSRect)rectValue;
    
```

// 1.利用NSValue包裝常用的結構體
    /*
    CGPoint point = NSMakePoint(10, 20);
    NSValue *value = [NSValue valueWithPoint:point];
    NSArray *arr = @[value];
    NSLog(@"%@", arr);
     */
    
    // 2.利用NSValue包裝自定義的結構體
    typedef struct{
        int age;
        char *name;
        double height;
    }Person;
    
    Person p = {30, "lnj", 1.75};
    // valueWithBytes: 接收一個指針, 需要傳遞需要包裝的結構體的變量的地址
    // objCType: 需要傳遞需要包裝的數據類型
    NSValue *pValue = [NSValue valueWithBytes:&p objCType:@encode(Person)];
    NSArray *arr = @[pValue];
    NSLog(@"%@", arr);
    // 從NSValue中取出自定義的結構體變量
    Person res;
    [pValue getValue:&res];
    NSLog(@"age = %i, name = %s, height = %f", res.age, res.name, res.height);

```

4.NSDate

  • 結合NSCalendar和NSDate能做更多的日期\時間處理

  • 獲得NSCalendar對象

```
NSCalendar *calendar = [NSCalendar currentCalendar];
```
  • 獲得年月日
// 創建一個時間格式化對象
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    // 告訴時間格式化對象, 按照什么樣的格式來格式化時間
    // yyyy 年
    // MM 月
    // dd 日
    // HH 24小時  hh 12小時
    // mm 分鐘
    // ss 秒鐘
    // Z 時區
- (NSDateComponents *)components:(NSCalendarUnit)unitFlags fromDate:(NSDate *)date;

5.NSFileManager

  • NSFileManager使用了單例模式
    • 使用defaultManager方法可以獲得那個單例對象

      [NSFileManager defaultManager]
      
  • NSFileManager的文件操作
    • (BOOL)copyItemAtPath:(NSString )srcPath toPath:(NSString )dstPath error:(NSError **)error;

      • 拷貝
    • (BOOL)moveItemAtPath:(NSString )srcPath toPath:(NSString )dstPath error:(NSError **)error;

      • 移動(剪切)
    • (BOOL)removeItemAtPath:(NSString )path error:(NSError *)error;

      • 刪除
    • (BOOL)createDirectoryAtPath:(NSString )path withIntermediateDirectories:(BOOL)createIntermediates attributes:(NSDictionary )attributes error:(NSError **)error;

      • 創建文件夾(createIntermediates為YES代表自動創建中間的文件夾)
  • 創建文件夾
// createDirectoryAtPath: 告訴系統文件夾需要創建到什么位置
    // withIntermediateDirectories: 如果指定的文件中有一些文件夾不存在, 是否自動創建不存在的文件夾
    // attributes: 指定創建出來的文件夾的屬性
    // error: 是否創建成功, 如果失敗會給傳入的參數賦值
    // 注意: 該方法只能用于創建文件夾, 不能用于創建文件
     BOOL flag = [manager createDirectoryAtPath:@"/Users/chenhengjun/Desktop/Beginlnj" withIntermediateDirectories:YES attributes:nil error:nil];
    NSLog(@"%i", flag);

判斷一個文件或者文件夾是否存在

 BOOL flag = [manager fileExistsAtPath:@"/Users/chenhengjun/Desktop/Begin"];
NSLog(@"flag = %i", flag);

判斷一個文件是否存在, 并且判斷它是否是一個文件夾

BOOL dir = NO;
//    BOOL flag = [manager fileExistsAtPath:@"/Users/chenhengjun/Desktop/Begin" isDirectory:&dir];
//    NSLog(@"flag = %i, dir = %i", flag, dir);

獲取文件或文件夾的屬性

注意:contentsOfDirectoryAtPath方法有一個弊端, 只能獲取當前文件夾下所有的文件, 不能獲取子文件夾下面的文件
//    NSArray *res = [manager contentsOfDirectoryAtPath:@"/Users/chenhengjun/Desktop/Begin" error:nil];
//    NSLog(@"res = %@", res);
 NSArray *res = [manager subpathsAtPath:@"/Users/chenhengjun/Desktop/Begin"];
////    NSArray *res = [manager subpathsOfDirectoryAtPath:@"/Users/chenhengjun/Desktop/Begin" error:nil];
//    NSLog(@"res = %@", res);

創建文件

createFileAtPath: 指定文件創建出來的位置
    // contents : 文件中的內容
    // attributes: 創建出來的文件的屬性
    
    // NSData : 二進制數據
    // 注意: 該方法只能用于創建文件, 不能用于創建文件夾
//    NSString *str = @"江哥真帥";
//    NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
//    [manager createFileAtPath:@"/Users/xiaomage/Desktop/abc.txt" contents:data attributes:nil];

6.Copy

/ 如果是通過不可變對象調用了copy方法, 那么不會生成一個新的對象
// 原因: 因為原來的對象是不能修改的, 拷貝出來的對象也是不能修改的
// 既然兩個都不能修改, 所以永遠不能影響到另外一個對象, 那么已經符合需求
// 所以: OC為了對內存進行優化, 就不會生成一個新的對象
NSString *srcStr = @"lnj";
NSString *copyStr = [srcStr copy];
NSLog(@"srcStr = %p, copyStr = %p", srcStr, copyStr);
    
/*
正是因為調用copy方法有時候會生成一個新的對象, 有時候不會生成一個新的對象
所以: 如果沒有生成新的對象, 我們稱之為淺拷貝, 本質就是指針拷貝
     如果生成了新的對象, 我們稱之為深拷貝, 本質就是會創建一個新的對象
*/
  • copy的三個用途
    * 1.copy的第一個用途, 防止外界修改內部的數據

       * 記住: 以后字符串(NSString)屬性都用copy
    
    • 2.可以使用copy保存block, 這樣可以保住block中使用的外界對象的命

    • 3.注意點: copy block之后引發循環引用

      • 如果對象中的block又用到了對象自己, 那么為了避免內存泄露, 應該將對象修飾為__block
  • 自定義類實現copy

    • 1.以后想讓自定義的對象能夠被copy只需要遵守NSCopying協議
    • 2.實現協議中的- (id)copyWithZone:(NSZone *)zone
    • 3.在- (id)copyWithZone:(NSZone *)zone方法中創建一個副本對象, 然后將當前對象的值賦值給副本對象即可
  • 注意點:

    • 如果想讓子類在copy的時候保留子類的屬性, 那么必須重寫copyWithZone方法, 在該方法中先調用父類創建副本設置值, 然后再設置子類特有的值

7.單例

+ (instancetype)shareTools
{
    Tools *instance = [[self alloc] init];
    return instance;
}

static Tools *_instance = nil;
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[super allocWithZone:zone] init];
    });
    return _instance;
}


- (id)copyWithZone:(NSZone *)zone{

    return _instance;
}

- (id)mutableCopyWithZone:(NSZone *)zone
{
    return _instance;
}

// MRC
- (oneway void)release
{

}

- (instancetype)retain
{
    return _instance;
}

- (NSUInteger)retainCount
{
    return  MAXFLOAT;
}

宏定義重構單例

#define interfaceSingleton(name)  +(instancetype)share##name


#if __has_feature(objc_arc)
// ARC
#define implementationSingleton(name)  \
+ (instancetype)share##name \
{ \
name *instance = [[self alloc] init]; \
return instance; \
} \
static name *_instance = nil; \
+ (instancetype)allocWithZone:(struct _NSZone *)zone \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instance = [[super allocWithZone:zone] init]; \
}); \
return _instance; \
} \
- (id)copyWithZone:(NSZone *)zone{ \
return _instance; \
} \
- (id)mutableCopyWithZone:(NSZone *)zone \
{ \
return _instance; \
}
#else
// MRC

#define implementationSingleton(name)  \
+ (instancetype)share##name \
{ \
name *instance = [[self alloc] init]; \
return instance; \
} \
static name *_instance = nil; \
+ (instancetype)allocWithZone:(struct _NSZone *)zone \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instance = [[super allocWithZone:zone] init]; \
}); \
return _instance; \
} \
- (id)copyWithZone:(NSZone *)zone{ \
return _instance; \
} \
- (id)mutableCopyWithZone:(NSZone *)zone \
{ \
return _instance; \
} \
- (oneway void)release \
{ \
} \
- (instancetype)retain \
{ \
return _instance; \
} \
- (NSUInteger)retainCount \
{ \
return  MAXFLOAT; \
}
#endif

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 國家電網公司企業標準(Q/GDW)- 面向對象的用電信息數據交換協議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 11,167評論 6 13
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,766評論 18 399
  • 1. 使用 #import 相對c的 include 是防止頭文件的重復導入 2. NSLog 相對于 print...
    迎風起飛的豬閱讀 1,779評論 6 9
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結起來就是把...
    Dove_iOS閱讀 27,211評論 30 472
  • 專一是一種偉大的力量,一個人一旦專一起來可以集中精力做出很多讓自己吃驚的事情。絕大多數人之所以一事無成,歸根結底的...
    耕耘生活閱讀 566評論 0 5