Objective-C學習備忘單

終極版本的Objective-C教程備忘單幫助你進行iOS開發。

終極版本的Objective-C教程備忘單幫助你進行iOS開發。

 

想開始創建你的第一個iOS應用程序麼?那麼看一下這篇很棒的教程吧:Create your first iOS 7 Hello World Application

 

注:這篇文章我寫了三天,可能在一些必要的地方使用了編輯和說明,所以如果有任何疑問和修改建議請在下方評論。

 

這不是一個初學者指南,也不是關於Objective-C的詳細討論,這是關於常見的和高水平的論題的快速索引。

 

如果這裏有些問題沒有涉及到,你也可以查閱以下文章:

Objective-C: A Comprehensive Guide for Beginners

The Objective-C Programming Language

Coding Guidelines for Cocoa

iOS App Programming Guide

 

內容目錄

Commenting

Data Types

Constants

Operators

Declaring Classes

Preprocessor Directives

Compiler Directives

Literals

Methods

Properties and Variables

Naming Conventions

Blocks

Control Statements

Enumeration

Extending Classes

Error Handling

Passing Information

User Defaults

Common Patterns

 

註釋

Objective-C中註釋用來組織代碼,併爲將來的代碼重構或者那些可能閱讀你代碼的iOS開發者們提供重要的額外信息。註釋通常會被編譯器忽視,因此它們不會增加編譯器程序的大小。

 

有兩種方式添加註釋:

  1. // This is an inline comment(內嵌註釋) 
  2. /* This is a block comment  and it can span multiple lines. */(塊註釋,可用於多行註釋) 
  3. // You can also use it to comment out code(也可以用來註釋掉代碼) 
  4.  
  5. /* 
  6. - (SomeOtherClass *)doWork 
  7. { 
  8.     // Implement this 
  9. } 
  10. */ 

 

使用pragma mark來組織代碼:

  1. #pragma mark - Use pragma mark to logically organize your code(使用#pragma mark可以對代碼進行邏輯組織) 
  2. // Declare some methods or variables here(在此處聲明方法和變量) 
  3.  
  4. #pragma mark - They also show up nicely in the properties/methods list in Xcode(在Xcode中#pragma mark還可以用來很好地顯示屬性及方法列表) 
  5. // Declare some more methods or variables here(在此處聲明方法和變量) 

 

數據類型

數據類型的大小

無論是在32位還是64位的系統環境中,允許的數據類型大小是由爲具體的類型分配了多少內存字節決定的。在32位的系統環境中,長整型(long)被分配4字節,相當於2^(4*8)(每個字節有8位)或者4294967295的內存範圍。在64爲的系統環境中,長整型(long)被分配8字節,相當於2^(8*8)或者1.84467440737096e19的範圍。

 

64位系統環境的變化的完全指南,請參考:transition document

 

C語言基礎

注:Objective-C繼承了所有的C語言基本類型,然後又新增了一些其他的類型。

 

Void(空類型)

Void是C語言的空數據類型。通常用於指定無返回值的函數的返回值類型(即,函數類型爲無返回值類型)。

 

整數

整數既可以是signed(有符號)的也可以是unsigned(無符號)的。signed(有符號)代表是正整數或者負整數,unsigned(無符號)代表只能是正整數。

 

整數類型以及它們的字節大小:

  1. // Char (1 byte for both 32-bit and 64-bit)  
  2. unsigned char anUnsignedChar = 255; 
  3. NSLog(@"char size: %zu", sizeof(char)); 
  4.  
  5. // Short (2 bytes for both 32-bit and 64-bit) 
  6. short aShort = -32768; 
  7. unsigned short anUnsignedShort = 65535; 
  8. NSLog(@"short size: %zu", sizeof(short)); 
  9.  
  10. // Integer (4 bytes for both 32-bit and 64-bit) 
  11. int anInt = -2147483648; 
  12. unsigned int anUnsignedInt = 4294967295; 
  13. NSLog(@"int size: %zu", sizeof(int)); 
  14.  
  15. // Long (4 bytes for 32-bit, 8 bytes for 64-bit) 
  16. long aLong = -9223372036854775808; // 32-bit 
  17. unsigned long anUnsignedLong = 18446744073709551615; // 32-bit 
  18. NSLog(@"long size: %zu", sizeof(long)); 
  19.  
  20. // Long Long (8 bytes for both 32-bit and 64-bit) 
  21. long long aLongLong = -9223372036854775808; 
  22. unsigned long long anUnsignedLongLong = 18446744073709551615; 
  23. NSLog(@"long long size: %zu", sizeof(long long)); 

 

固定寬度的整數類型和字節大小來作爲變量名:

  1. // Exact integer types 
  2.  
  3. int8_t aOneByteInt = 127; 
  4.  
  5. uint8_t aOneByteUnsignedInt = 255; 
  6.  
  7. int16_t aTwoByteInt = 32767; 
  8.  
  9. uint16_t aTwoByteUnsignedInt = 65535; 
  10.  
  11. int32_t aFourByteInt = 2147483647; 
  12.  
  13. uint32_t aFourByteUnsignedInt = 4294967295; 
  14.  
  15. int64_t anEightByteInt = 9223372036854775807; 
  16.  
  17. uint64_t anEightByteUnsignedInt = 18446744073709551615; 
  18.  
  19.  
  20. // Minimum integer types 
  21.  
  22. int_least8_t aTinyInt = 127; 
  23.  
  24. uint_least8_t aTinyUnsignedInt = 255; 
  25.  
  26. int_least16_t aMediumInt = 32767; 
  27.  
  28. uint_least16_t aMediumUnsignedInt = 65535; 
  29.  
  30. int_least32_t aNormalInt = 2147483647; 
  31.  
  32. uint_least32_t aNormalUnsignedInt = 4294967295; 
  33.  
  34. int_least64_t aBigInt = 9223372036854775807; 
  35.  
  36. uint_least64_t aBigUnsignedInt = 18446744073709551615; 
  37.  
  38.  
  39. // The largest supported integer type 
  40.  
  41. intmax_t theBiggestInt = 9223372036854775807; 
  42.  
  43. uintmax_t theBiggestUnsignedInt = 18446744073709551615; 

 

浮點類型

浮點沒有signed或者unsigned

  1. // Single precision floating-point (4 bytes for both 32-bit and 64-bit)單精度浮點float aFloat = 72.0345f; 
  2.  
  3. NSLog(@"float size: %zu", sizeof(float)); 
  4.  
  5.  
  6. // Double precision floating-point (8 bytes for both 32-bit and 64-bit)雙精度浮點 
  7.  
  8. double aDouble = -72.0345f; 
  9.  
  10. NSLog(@"double size: %zu", sizeof(double)); 
  11.  
  12.  
  13. // Extended precision floating-point (16 bytes for both 32-bit and 64-bit)擴展精度浮點 
  14.  
  15. long double aLongDouble = 72.0345e7L; 
  16.  
  17. NSLog(@"long double size: %zu", sizeof(long double)); 

 

Objective-C基礎

id:被定義爲匿名或者動態對象類型,它可以存儲任何類型對象的引用,不需要指定指針符號。

  1. id delegate = self.delegate; 

 

Class:用來表示對象的類,並能用於對象的內省。

  1. Class aClass = [UIView class]; 

 

Method:用來表示一個方法,並可用於swizzling方法。

  1. Method aMethod = class_getInstanceMethod(aClass, aSelector); 

 

SEL:用於指定一個selector,它是編譯器指定的代碼,用於識別方法的名稱。

  1. SEL someSelector = @selector(someMethodName); 

 

IMP:用於在方法開始時指向內存地址。你可能不會用到這個。

  1. IMP theImplementation = [self methodForSelector:someSelector]; 

 

BOOL:用來指定一個布爾類型,布爾類型中0值被認爲是NO(false),任何非零值被認爲是YES(true)。任何零值對象都被認爲是NO,因此不需要對零值進行同樣的驗證(例如,只要寫if(someObject),不需要寫if (someObject !=nil))

  1. // Boolean 
  2. BOOL isBool = YES; // Or NO 

 

nil:用來指定一個空對象指針。當類第一次被初始化時,類中所有的屬性被設置爲0,這意味着都指向空指針。

 

Objective-C中還有很多其他類型,如NSInteger, NSUInteger, CGRect,CGFloat, CGSize, CGPoint等。

 

Enum(枚舉)和Bitmask(位掩碼)類型

Objective-C的枚舉類型可以用以下多個不同方式定義:

  1. // Specifying a typed enum with a name (recommended way)用一個別名來指定一個帶有typedef關鍵字的枚舉類型(推薦方法) 
  2.  
  3. typedef NS_ENUM(NSInteger, UITableViewCellStyle) { 
  4.  
  5.     UITableViewCellStyleDefault, 
  6.  
  7.     UITableViewCellStyleValue1, 
  8.  
  9.     UITableViewCellStyleValue2, 
  10.  
  11.     UITableViewCellStyleSubtitle 
  12.  
  13. }; 
  14.  
  15.  
  16. // Specify a bitmask with a name (recommended way)用一個別名來指定一個bitmask(推薦方法) 
  17.  
  18. typedef NS_OPTIONS(NSUInteger, RPBitMask) { 
  19.  
  20.     RPOptionNone      = 0, 
  21.  
  22.     RPOptionRight     = 1 << 0, 
  23.  
  24.     RPOptionBottom    = 1 << 1, 
  25.  
  26.     RPOptionLeft      = 1 << 2, 
  27.  
  28.     RPOptionTop       = 1 << 3 
  29.  
  30. }; 
  31.  
  32.  
  33. // Other methods:(其他方法) 
  34.  
  35. // Untyped(無類型) 
  36.  
  37. enum { 
  38.  
  39.     UITableViewCellStyleDefault, 
  40.  
  41.     UITableViewCellStyleValue1, 
  42.  
  43.     UITableViewCellStyleValue2, 
  44.  
  45.     UITableViewCellStyleSubtitle 
  46.  
  47. }; 
  48.  
  49.  
  50. // Untyped with a name 用一個別名來定義一個帶有typedef關鍵字將枚舉類型 
  51.  
  52. typedef enum { 
  53.  
  54.     UITableViewCellStyleDefault, 
  55.  
  56.     UITableViewCellStyleValue1, 
  57.  
  58.     UITableViewCellStyleValue2, 
  59.  
  60.     UITableViewCellStyleSubtitle 
  61.  
  62. } UITableViewCellStyle; 

 

構造數據類型

有時爲一個特定的類或者數據類型構造一個id或者不同的類型時很有必要的。例如,從一個浮點型構造成整數或者從一個UITableViewCell構造成一個如RPTableViewCell的子類。

 

構造非對象數據類型:(cast)

  1. // Format: nonObjectType variableName = (nonObjectType) 
  2.  
  3. variableNameToCastFrom; 
  4.  
  5. int anInt = (int)anAnonymouslyTypedNonObjectOrDifferentDataType; 

 

構造對象數據類型:

  1. // Format: ClassNameOrObjectType *variableName =(ClassNameOrObjectType *)variableNameToCastFrom; 
  2.  
  3. UIViewController *aViewController = (UIViewController *) 
  4.  
  5. anAnonymouslyTypedObjectOrDifferentDataType; 

 

常量

使用常量通常是一個更好的方法,因爲常量爲代碼中的任何對象的引用指向相同的內存地址。#define定義了一個宏。在編譯開始前,宏用實際常量值來代替所有的引用,而不是作爲指向常量值的內存指針。

 

Objective-C常量可以這樣定義:

  1. // Format: type const constantName = value; 
  2. NSString *const kRPShortDateFormat = @"MM/dd/yyyy"
  3.  
  4. // Format: #define constantName value 
  5. #define kRPShortDateFormat @"MM/dd/yyyy" 

 

要是在擴展類中能使用常量,你必須也將它添加在頭文件(.h)中。

  1. extern NSString *const kRPShortDateFormat; 

 

如果你知道一個常量只可用於它包含的.m文件中,可以這樣指定它:

  1. static NSString *const kRPShortDateFormat = @"MM/dd/yyyy"

 

在方法中聲明一個靜態變量在調用時值不會改變。當爲一個屬性聲明一個singleton(單例)或者創建setter和getter器時這將很有用處。

 

運算符

算術運算符

 

關係運算符

 

邏輯運算符

 

複合賦值運算符

 

增值或減值運算符

 

位運算符

 

其他運算符

 

聲明類

聲明類需要兩個文件:一個頭文件(.h)和一個實現文件(.m)

 

頭文件應包含(按如下順序):

1. 所有需要#import的語句或者在前面@class聲明;

2. 任何協議聲明;

3. @interface聲明指定繼承自哪個類;

4. 所有可訪問的公共變量、屬性以及方法;

 

實現文件應包含(按如下順序):

1. 所有需要的#import語句;

2. 所有的私有變量或屬性的種類或者類擴展;

3. @implementation聲明指定一個類;

4. 所有的公共或私有方法;

 

如下例子:

MyClass.h

  1. #import "SomeClass.h" 
  2.  
  3. // Used instead of #import to forward declare a class in property return types, etc. 
  4.  
  5. @class SomeOtherClass; 
  6.  
  7. // Place all global constants at the top extern NSString *const kRPErrorDomain; 
  8.  
  9. // Format: YourClassName : ClassThatYouAreInheritingFrom 
  10.  
  11. @interface MyClass : SomeClass 
  12.  
  13. // Public properties first 
  14.  
  15. @property (readonly, nonatomic, strong) SomeClass *someProperty; 
  16.  
  17. // Then class methods 
  18.  
  19. + (id)someClassMethod; 
  20.  
  21. // Then instance methods 
  22.  
  23. - (SomeOtherClass *)doWork; 
  24.  
  25. @end 

 

MyClass.m

  1. #import "MyClass.h" 
  2.  
  3. #import "SomeOtherClass.h" 
  4.  
  5. // Declare any constants at the top 
  6.  
  7. NSString *const kRPErrorDomain = @"com.myIncredibleApp.errors"
  8.  
  9. static NSString *const kRPShortDateFormat = @"MM/dd/yyyy"
  10.  
  11.  
  12. // Class extensions for private variables / properties 
  13.  
  14. @interface MyClass () 
  15.     int somePrivateInt; 
  16.  
  17.     // Re-declare as a private read-write version of the public read-only property 
  18.  
  19.     @property (readwrite, nonatomic, strong) SomeClass 
  20.  
  21. *someProperty; 
  22.  
  23.  
  24. @end 
  25.  
  26.  
  27. @implementation MyClass 
  28.  
  29.  
  30. // Use #pragma mark - statements to logically organize your code 
  31.  
  32. #pragma mark - Class Methods 
  33.  
  34.  
  35. + (id)someClassMethod 
  36. { 
  37.    return [[MyClass alloc] init]; 
  38.  
  39.  
  40. #pragma mark - Init & Dealloc methods 
  41.  
  42. - (id)init 
  43.     if (self = [super init]) { 
  44.  
  45.         // Initialize any properties or setup code here 
  46.     }  
  47.     return self; 
  48.  
  49.  
  50.  
  51. // Dealloc method should always follow init method 
  52.  
  53. - (void)dealloc 
  54.  
  55.     // Remove any observers or free any necessary cache, etc. 
  56.  
  57.     [super dealloc]; 
  58.  
  59. #pragma mark - Instance Methods 
  60.  
  61. - (SomeOtherClass *)doWork 
  62.     // Implement this 
  63.  
  64. @end 

 

實例化

當想要創建類的新實例時,你需要使用如下語法:

  1. MyClass *myClass = [[MyClass alloc] init]; 

alloc類方法返回一個指針指向一個新分配的內存塊,這個內存塊空間足夠大可以儲存這個類的一個實例。這個分配的內存中包含了所有Objective-C對象都必須有的實例變量和isa指針。isa指針變量自動初始化指向類對象,類對象分配內存並使實例能夠接收消息,比如用來完成初始化的init。

 

預處理器指令

本節仍有需要改進的地方

 

編譯器指令

請參考literal(字面)章節

 

類和協議

 

屬性

 

錯誤

 

實例變量的可變性

默認的是@protected類型,所以不用明確地指定此類型。

 

其他

 

Literals(字面語法)

字面語法是的編譯器指令,它提供簡化符號來創建對象。

NSArray訪問語法:

  1. NSArray *example = @[ @"hi", @"there", @23, @YES ]; 
  2. NSLog(@"item at index 0: %@", example[0]); 

NSDictionary訪問語法:

  1. NSDictionary *example = @{ @"hi" : @"there", @"iOS" : @"people" }; 
  2. NSLog(@"hi %@", example[@"hi"]); 

注意事項:與NSString類似,通過常量數組和字典收集對象是不可變的。相反,你必須在創建這個不可變的字典或者數組後創建一個可變的副本。此外,你不能像使用NSString那樣做靜態初始化。

 

方法

聲明語法

方法沒有返回值類型時,用void定義:

  1. // Does not return anything or take any arguments 
  2. - (void)someMethod; 

 

用“+”調用之前聲明的類方法:

  1. // Call on a class (e.g. [MyClass someClassMethod]); 
  2. + (void)someClassMethod; 

用“-”調用之前聲明的類的實例方法:

  1. // Called on an instance of a class (e.g. [[NSString alloc] init]); 
  2. - (void)someClassInstanceMethod; 

 

在“:”後面聲明方法參數,方法簽名應該描述參數類型:

  1. // Does something with an NSObject argument 
  2. - (void)doWorkWithObject:(NSObject *)object; 

 

使用強制轉換語法聲明參數和返回值類型:

  1. // Returns an NSString object for the given NSObject arguments 
  2. - (NSString *)stringFromObject:(NSObject *)objectandSomeOtherObject:(NSObject *)otherObject; 

 

方法調用

使用方括號語法調用方法: [self someMethod]或者[selfsometMethodWithObject:object];

 

self是對包含類的方法的一個引用。self變量存在於所有Objective-C方法中。它是傳遞給代碼用以執行方法的兩個隱藏參數之一。另外一個是_cmd,用於識別接收到的消息。

 

有時,很有必要使用[super someMethod];在父類中調用一個方法。

 

在高級選項下,方法通過消息傳遞實現,並且轉換成了這兩個C函數其中一個的:

  1. id objc_msgSend(id self, SEL op, ...); 
  2. id objc_msgSendSuper(struct objc_super *super, SEL op, ...); 

 

這有一個很棒的Objective-C教程系列的初學者指南,涵蓋了更多關於方法調用的細節。請訪問:Calling Methods in Objective-C。(http://ios-blog.co.uk/tutorials/objective-c-guide-for-developers-part-2/#methods)

 

測試選擇器

如果你想要測試一個類是否在被髮送(或者可能會崩潰)之前響應一個特定的選擇器,你可以這樣做:

  1. if ([someClassOrInstance respondsToSelector:@selector (someMethodName)]) 
  2.  
  3.     // Call the selector or do something here 

當你實現一個委託,並且在委託對象上調用這些聲明之前,你需要對聲明爲@optional的方法進行測試,這種模式很常見。

 

屬性和變量

聲明一個Objective-C屬性允許你保留類中對象的一個引用或者在類間傳遞對象。

 

在頭文件 ( .h)中聲明公共屬性:

  1. @interface MyClass : NSObject 
  2. @property (readonly, nonatomic, strong) NSString *fullName; 
  3. @end 

 

在實現文件 ( .m)的匿名類或者擴展類中聲明私有屬性:

  1. #import "MyClass.h" 
  2.  
  3. // Class extension for private variables / properties 
  4.  
  5. @interface MyClass () 
  6.     // Instance variable 
  7.     int somePrivateInteger; 
  8.  
  9.     // Private properties 
  10.     @property (nonatomic, strong) NSString *firstName; 
  11.     @property (nonatomic, strong) NSString *lastName; 
  12.     @property (readwrite, nonatomic, strong) NSString *fullName; 
  13. @end 
  14.  
  15. @implementation MyClass 
  16.  
  17. // Class implementation goes here 
  18.  
  19. @end 

 

LLVM編譯器自動綜合所有屬性,因此不再需要爲屬性寫明@synthesize語句。當一個屬性被綜合時,就會創建accessors,允許你設置或者獲得屬性的值。儘管你可能不會看到它們,因爲它們是在構建時創建的,但是一對getter/setter可以顯示爲:

  1. - (BOOL)finished 
  2.  
  3.     return _finished; 
  4.  
  5. - (void)setFinished:(BOOL)aValue 
  6.  
  7.     _finished = aValue; 

 

你可以重寫屬性的getter和seeter來創建自定義的行爲,或者甚至是使用這個模式來創建瞬態屬性,如下:

  1. - (NSString *)fullName 
  2.  return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName]; 

 

屬性後面通常會有一個帶有前導下劃線的實例變量,因此創建一個名爲fistName的屬性會帶有一個名爲_firstName的實例變量。如果你重寫getter/setter或者你需要在類的init方法中設置ivar,你只需要訪問私有實例變量。

 

屬性特性

當指定一個屬性時,使用如下語法:

  1. @property SomeClass *someProperty; 
  2. // Or 
  3. @property (xxx) SomeClass *someProperty; 

 

xxx可以與什麼組合:

 

 

訪問屬性

使用括號或者點操作都可以訪問屬性,點操作讀起來更清晰:

  1. [self myProperty]; 
  2. // Or 
  3. self.myProperty 

 

局部變量

局部變量只存在於方法的範圍內。

  1. - (void)doWork  
  2.    NSString *localStringVariable = @"Some local string variable."
  3.  
  4.    [self doSomethingWithString:localStringVariable]; 

 

命名約定

一般的經驗法則: 清晰和簡潔都是重要的,但是清晰更重要。

 

方法和屬性

都是用駝峯式拼寫法,第一個單詞的首字母爲小寫,其他單詞的首字母都大寫。

 

類名和協議

都是用大寫字母,每個單詞的首字母都大寫。

 

方法

如果執行一些動作,那麼應該使用動詞(如performInBackground)。你應該推斷出知道發生了什麼,方法需要什麼參數,或者只通過閱讀一個方法簽名就知道返回了什麼。例如:

  1. // Correct 
  2. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
  3.     // Code 
  4.  
  5. // Incorrect (not expressive enough) 
  6. - (UITableViewCell *)table:(UITableView *)tableView cell:(NSIndexPath *)indexPath 
  7.     // Code 

 

屬性和局部變量

使用屬性時,在內部創建一個帶有前導下劃線的實例變量,因此創建myVariableName爲_myVariableName。然而,Objective-C現在爲你綜合這些屬性,因此你不再需要直接訪問帶有下劃線的實例變量,除非在一個自定義setter中。

 

相反,通常用selfl訪問和變異(mutated)實例變量。

 

局部變量不能有下劃線。

 

常量

常量通常以k和 XXX命名方式, XXX是一個前綴,或許你的首字母可以避免命名衝突。你不應該害怕你要表達的常量名稱,尤其是全局變量時。使用kRPNavigationFadeOutAnimationDuration比fadeOutTiming好的多。

 

Blocks

在Objective-C中,塊(block)實際上是一個匿名函數,用來在方法間傳遞代碼或者在函數回調時執行代碼。由於Block實現爲閉包,周圍的語句也被捕獲(有時會導致retain cycles)。

 

語法

  1. // As a local variable 
  2. returnType (^blockName)(parameterTypes) = ^returnType(parameters) {  
  3.     // Block code here 
  4. }; 
  5.  
  6.  
  7. // As a property 
  8. @property (nonatomic, copy) returnType (^blockName)(parameterTypes); 
  9.   
  10.  
  11. // As a method parameter 
  12. - (void)someMethodThatTakesABlock:(returnType (^)(parameterTypes))blockName { 
  13.    // Block code here 
  14. }; 
  15.  
  16.  
  17. // As an argument to a method call 
  18.  
  19. [someObject someMethodThatTakesABlock: ^returnType (parameters) { 
  20.  
  21.     // Block code here 
  22.  
  23. }]; 
  24.  
  25.  
  26. // As a typedef 
  27. typedef returnType (^TypeName)(parameterTypes); 
  28.  
  29. TypeName blockName = ^(parameters) { 
  30.  
  31.     // Block code here 
  32. }; 

 

查看我們專門的文章瞭解更多細節Objective-C Programming with blocks

 

變異的block變量

由於block中的變量正是它們在block區域外部的一個快照,你必須在block內將你的變量用_block變異,例如:

  1. __block int someIncrementer = 0; 
  2.  
  3. [someObject someMethodThatTakesABlock:^{ 
  4.  
  5.     someIncrementer++; 
  6.  
  7. }]; 

 

Retain cycles

由於block有力地捕捉區域內的所有變量,你必須謹慎設置block代碼,以下爲retain cycle的兩個例子:

  1. [someObject someMethodThatTakesABlock:^{ 
  2.  
  3.     [someObject performSomeAction];  // Will raise a warning 
  4.  
  5. }]; 
  6.  
  7.  
  8. [self someMethodThatTakesABlock:^{ 
  9.  
  10.     [self performSomeAction];       // Will raise a warning 
  11.  
  12. }]; 

 

這兩個例子中,執行block的對象都擁有block,block也擁有對象。這行形成一個迴路,或者一個retain cycle,這就意味着內存最終將會泄露。

 

爲了解決這個警告你可以重構代碼:

  1. [self someMethodThatTakesABlock:^{ 
  2.  
  3.     [object performSomeAction];   // No retain cycle here 
  4.  
  5. }]; 

 

或者你可以用一個_weak對象:

  1. __weak typeof(self) weakSelf = self; 
  2.  
  3. [self someMethodThatTakesABlock:^{ 
  4.  
  5.     [weakSelf performSomeAction];  // No retain cycle here 
  6.  
  7. }]; 

 

控制語句

Objective-C使用其他語言所有相同的控制語句:

 

If-Else If-Else:

  1. if (someTestCondition) { 
  2.  
  3.     // Code to execute if the condition is true 
  4.  
  5. else if (someOtherTestCondition) { 
  6.  
  7.     // Code to execute if the other test condition is true 
  8.  
  9. else { 
  10.  
  11.     // Code to execute if the prior conditions are false 
  12.  

 

三元運算符

if-else的一個速記語句就是一個三元運算符形式:someTestCondition ? doIfTrue : doIfFalse;

例如:

  1. - (NSString *)stringForTrueOrFalse:(BOOL)trueOrFalse 
  2.  
  3.     return trueOrFalse ? @"True" : @"False"

這還有一個不太常用的形式:A ?: B,基本上是指如果A是正確的或者非空的,就返回A;反之,返回B。

 

For循環

更多詳解,請看Objective-C: Loops

  1. for (int i = 0; i < totalCount; i++) { 
  2.     // Code to execute while i < totalCount 

 

快速枚舉

  1. for (Person *person in arrayOfPeople) { 
  2.     // Code to execute each time 

arrayOfPeople可以是符合NSFastEnumeration協議的任何對象. NSArray和NSSet列舉它們的對象,NSDictionary列舉關鍵詞,NSManagedObjectModel列舉實體。

 

While循環

  1. while (someTextCondition) { 
  2.    // Code to execute while the condition is true 

 

Do While循環

  1. do { 
  2.     // Code to execute while the condition is true 
  3. while (someTestCondition); 

 

Switch(轉換)

switch語句通常用來代替if語句,如果需要測試一個指定的變量的值是否匹配另一個常量或者變量值。例如,你或許想要測試你接收到的error代碼整數是否匹配現有的常數值,或者如果它是一個新的錯誤代碼。

  1. switch (errorStatusCode) 
  2. { 
  3.     case kRPNetworkErrorCode: 
  4.          // Code to execute if it matches 
  5.          break
  6.     case kRPWifiErrorCode: 
  7.          // Code to execute if it matches 
  8.          break
  9.   default
  10.          // Code to execute if nothing else matched 
  11.          break

 

退出循環

1. return:停止執行,返回到調用的函數。可以用於返回一個方法的值。

2. break:用來停止執行一個循環。

 

新的枚舉方法有一個特別的BOOL變量(如BOOL *stop)用來停止執行循環。在循環內將變量設置爲YES,類似於調用break。

 

枚舉

在statements章節已經提高了快速枚舉,但是很多集合類也有他們自己的基於block的方法來枚舉一個集合。

 

基於block的方法與快捷枚舉執行方法幾乎相同,但是他們爲枚舉集合提供了額外的選擇。在NSArray上基於block的枚舉示例如下:

  1. NSArray *people = @[ @"Bob", @"Joe", @"Penelope", @"Jane" ]; 
  2.  
  3. [people enumerateObjectsUsingBlock:^(NSString *nameOfPerson, NSUInteger idx, BOOL *stop) { 
  4.     NSLog(@"Person's name is: %@", nameOfPerson); 
  5. }]; 

 

擴展類

在Objective-C中有幾個不同方法可以擴展類,其中一些方法更簡單。

 

繼承

繼承本質上允許你創建特定的子類,這些子類繼承父類頭文件中所有的指定爲@ public或@protected的方法和屬性時,通常有指定的用法。

 

通過任何框架或者開源項目,你可以看到使用繼承不僅獲得開放的用法,也整合代碼使之很容易重用。可在任何可變框架類中看到這種方法的例子,如NSMutableString就是NSString的一個子類。

 

在Objective-C中,通過類的繼承,所有對象都有很多NSObject class指定的行爲。沒有繼承,你需要自己實現基本的方法,如檢查對象或者類對等。最終你將會在類中會有很多重複性的代碼。

  1. // MyClass inherits all behavior from the NSObject class 
  2. @interface MyClass : NSObject 
  3.  
  4. // Class implementation 
  5. @end 

繼承本質上創建了類之間的耦合,因此要仔細想想這個用法。

 

類別

Objective-C類別是一個非常有用並且簡單的擴展類的方法,尤其是當你沒有訪問源碼(例如Cocoa Touch框架和類)時。可以爲任何一個類或者方法聲明一個category,並且在類別中聲明的方法對原始類及其子類中所有實例都可用。在運行時,通過類別添加一個方法與通過原始類實現一個方法沒有什麼不同。

 

類別也可用於:

1.聲明非正式協議;

2.爲相關的方法分類,似於有多個類

3.將大型類的實現分解成多個類別,這有助於增量編譯

4.很容易爲不同的應用程序配置不同的類

 

實現

類別的命名格式爲:ClassYouAreExtending + DescriptorForWhatYouAreAdding

 

例如,假設我們需要爲UIImage類(以及所有子類)中添加一個新的方法,以便我們可以很容易地調整實例大小或者獲得實例 在這個類中很容易的重調或者拷貝實例。那麼你需要用如下實現方法來創建一個名爲UIImage+ResizeCrop的頭文件和實現文件。

UIImage+ResizeCrop.h

  1. @interface UIImage (ResizeCrop) 
  2. - (UIImage *)croppedImageWithSize:(CGSize)size; 
  3. - (UIImage *)resizedImageWithSize:(CGSize)size; 
  4. @end 

 

UIImage+ResizeCrop.m

  1. #import "UIImage+ResizeCrop.h" 
  2. @implementation UIImage (ResizeCrop) 
  3. - (UIImage *)croppedImageWithSize:(CGSize)size 
  4.     // Implementation code here 
  5.  
  6. - (UIImage *)resizedImageWithSize:(CGSize)size 
  7.     // Implementation code here 

 

然後你可以在任何UIImage類或者其子類上調用這些方法:

  1. UIImage *croppedImage = [userPhoto croppedImageWithSize:photoSize]; 

 

關聯引用

除非你在編譯時訪問類的源碼,否則不太可能通過使用category爲那個class添加實例變量和屬性。相反,你必須通過使用Objective-C運行時的特性,即關聯引用。

 

例如,假設我們想要在UIScrollView類中添加一個公共屬性來存儲一個UIView對象的引用,但是我們不能訪問UISCrollView的源碼。我們就需要在UIScrollView中創建一個類別,然後爲這個新屬性創建一對getter/setter方法來存儲這個引用,如下:

UIScrollView+UIViewAdditions.h

  1. #import <UIKit/UIKit.h> 
  2. @interface UIScrollView (UIViewAdditions) 
  3. @property (nonatomic, strong) UIView *myCustomView; 
  4. @end 

 

UIScrollView+UIViewAdditions.m

  1. #import "UIScrollView+UIViewAdditions.h" 
  2. #import <objc/runtime.h> 
  3.  
  4. static char UIScrollViewMyCustomView; 
  5.  
  6. @implementation UIScrollView (UIViewAdditions) 
  7.  
  8. @dynamic myCustomView; 
  9.  
  10. - (void)setMyCustomView:(UIView *)customView 
  11.     [self willChangeValueForKey:@"myCustomView"]; 
  12.  
  13.     objc_setAssociatedObject(self, &UIScrollViewMyCustomView, customView, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
  14.  
  15.     [self didChangeValueForKey:@"myCustomView"]; 
  16.  
  17. - (UIView *)myCustomView { 
  18.     return objc_getAssociatedObject(self, &UIScrollViewMyCustomView); 
  19.  
  20. @end 

 

讓我們解釋一下這裏發生了什麼:

1.我們創建了一個靜態密鑰UIScrollViewMyCustomView,可用於獲得和設置一個關聯的對象。之所以把它聲明爲靜態是爲了確保它始終指向相同的內存地址。

2.接下來,我們聲明一個帶有@dynamic的屬性,這告訴了編譯器getter/setter不是UIScrollView類自己實現的。

3.在setter中,我們用willChangeValueForKey緊跟didChangeValueForKey,以確保在屬性改變時通知了任意的key-value observers。

4.在setter中,我們使用objc_setAssociatedObject來存儲一個我們真正需要的對象引用,在靜態密鑰下我們創建了customView。&是用來表示一個指向UIScrollViewMyCustomView的指針(給UIScrollViewMyCustomView的指針獲取地址)。

5.在setter中,我們使用objc_getAssociatedObject和指向靜態密鑰的指針來檢索對象引用。

 

我們可以像使用其他屬性一樣使用這個屬性:

  1. UIScrollView *scrollView = [[UIScrollView alloc] init]; 
  2. scrollView.myCustomView = [[UIView alloc] init]; 
  3. NSLog(@"Custom view is %@", scrollView.myCustomView); 
  4. // Custom view is <UIView: 0x8e4dfb0; frame = (0 0; 0 0); layer = <CALayer: 0x8e4e010>> 

 

文檔中更多的信息:

  1. The [objc_setAssociatedObject] function takes four parameters: the source object, a key, the value, and an association policy constant. The key is a void pointer. 
 The key for each association must be unique. A typical pattern is to use a static variable. The policy specifies whether the associated object is assigned, retained, or copied, and whether the association is be made atomically or non-atomically. This pattern is similar to that of the attributes of a declared property

 

可能的屬性聲明中的屬性特徵:

1. OBJC_ASSOCIATION_RETAIN_NONATOMIC,

2. OBJC_ASSOCIATION_ASSIGN,

3. OBJC_ASSOCIATION_COPY_NONATOMIC,

4. OBJC_ASSOCIATION_RETAIN,

5. OBJC_ASSOCIATION_COPY

 

類擴展類別

在聲明類的小節中,它展示了類中的私有實例變量和屬性如何通過匿名類別(也稱類擴展)被添加到一個class中。

  1. // Class extensions for private variables / properties 
  2. @interface MyClass () 
  3.     int somePrivateInt; 
  4.     // Re-declare as a private read-write version of the public read-only property 
  5.     @property (readwrite, nonatomic, strong) SomeClass *someProperty; 
  6. @end 

 

類擴展是使用類別添加變量和屬性的唯一方法。爲了使用這個方法,你必須在編譯時訪問源碼。

 

核心數據類別

當你使用核心數據模型並想要添加額外的方法到NSManagedObject子類中時,並且不想擔心每次遷移一個模型時Xcode覆蓋模型類時,類別是非常有用的。

 

模型類應該保持在最低限度,只包含屬性模型和核心數據生成的訪問方法。其他的方法(如瞬態方法)應該在模型類的類別中實現。

 

命名衝突

蘋果文檔的建議:

 Because the methods declared in a category are added to an existing class, you need to be very careful about method names.

If the name of a method declared in a category is the same as a method in the original class, or a method in another category on the same class (or even a superclass), the behavior is undefined as to which method implementation is used at runtime. This is less likely to be an issue if you’re using categories with your own classes, but can cause problems when using categories to add methods to standard Cocoa or Cocoa Touch classes.

 

委託

Objective-C中委託是一個基本方法,這個方法允許一個類對另一個類的改變做出反應,或者在在兩個類之間最小化耦合時影響另個一類的用法。

 

iOS中最常見的已知的委託模式的例子是UITableViewDelegate和UITableViewDataSource。當你告訴編譯器你的類符合這些協議,你實際上是同意在你的類中實現特定的方法,UITableView需要這個類發揮適當的作用。

 

符合現有的協議

爲了符合現有的協議,導入包含協議聲明(對框架類沒有必要)的頭文件。然後用<>符號插入協議名,用逗號分開多個協議。下面的兩種方法都行得通,但是我更喜歡把他們寫在頭文件中,因爲這對我來講更清晰。

方法一: 在 .h文件中:

  1. #import "RPLocationManager.h" 
  2.  
  3. @interface MyViewController : UIViewController <RPLocationManagerDelegate, UITableViewDelegate, UITableViewDataSource> 
  4.  
  5. @end 

 

方法二 : 在 .m 文件中:

  1. #import "MyViewController.h" 
  2. @interface MyViewController () <RPLocationManagerDelegate, UITableViewDelegate, UITableViewDataSource> 
  3.     // Class extension implementation 
  4. @end 

 

創建你自己的協議

創建自己的協議使其他類遵守,如下語法:

RPLocationManager.h

  1. #import <SomeFrameworksYouNeed.h> 
  2. // Declare your protocol and decide which methods are required/optional 
  3. // for the delegate class to implement 
  4.  
  5. @protocol RPLocationManagerDelegate <NSObject> 
  6. - (void)didAcquireLocation:(CLLocation *)location; 
  7. - (void)didFailToAcquireLocationWithError:(NSError *)error; 
  8.  
  9. @optional 
  10. - (void)didFindLocationName:(NSString *)locationName; 
  11. @end 
  12.  
  13. @interface RPLocationManager : NSObject 
  14.  
  15. // Create a weak, anonymous object to store a reference to the delegate class 
  16.  
  17. @property (nonatomic, weak) id <RPLocationManagerDelegate>delegate; 
  18.  
  19. // Implement any other methods here 
  20.  
  21. @end 

當我們聲明名@protocol命名RPLocationManagerDelegate時,所有的方法都默認成爲@required,因此不需要明確的聲明。然而,如果你想要@optional特定的方法來符合類的實現,你必須聲明它。

 

此外,有必要弱聲明一個名爲delegate的匿名類型的屬性,這個屬性也引用RPLocationManagerDelegate協議。

 

發送委託消息

在上面的例子中,RPLocationManager.h聲明瞭一些這個類作爲委託方必須要實現的方法。在RPLocationManager.m中,你可以以不同的方法實現,這裏我們只舉兩個例子:

1. @required方法

  1. - (void)updateLocation 
  2.     // Perform some work 
  3.     // When ready, notify the delegate method 
  4.     [self.delegate didAcquireLocation:locationObject]; 

2.@optional方法

  1. - (void)reverseGeoCode 
  2.     // Perform some work 
  3.     // When ready, notify the delegate method 
  4.     if ([self.delegate respondsToSelector:@selector(didFindLocationName:)]) { 
  5.         [self.delegate didFindLocationName:locationName]; 
  6.     } 

@required與@optional方法唯一的區別是:你經常需要檢查下引用委託是否實現了一個optional方法,在調用該方法之前。

 

實現委託方法

要實現一個委託方法,只要遵守之前討論的協議,然後想一個正常的方法去定義就可以:

  1. MyViewController.m 
  2. - (void)didFindLocationName:(NSString *)locationName 
  3.     NSLog(@"We found a location with the name %@", locationName); 

 

子類化

子類本質上和繼承相同,但是你通常以以下兩種方式創建子類:

1.在父類中重載方法或者屬性實現;

2.爲子類創建特定的用法(例如,Toyota是Car的子類,但它仍然有輪胎、發動機等,但是它還有額外的獨特的屬性)。

 

存在很多設計模式,如類別和委託,因此你不需要創建一個類的子類。例如,UITableViewDelegate協議允許你在自己的類中提供方法的實現,例如tableView:didSelectRowAtIndexPath:,而不需要創建UITableView的子類來重載方法。

 

另外,類似NSManagedObject的類很容易派生子類。一般的經驗法則是從另外一個類派生一個子類,只要你能滿足Liskov代換原則

 If S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program.

示例

假設我們需要汽車模型,所有的車有相同的功能和屬性,所以讓我們在名爲Car的子類中添加一些。

Car.h

  1. #import <Foundation/Foundation.h> 
  2.  
  3. @interface Car : NSObject 
  4.  
  5. @property (nonatomic, strong) NSString *make; 
  6.  
  7. @property (nonatomic, strong) NSString *model; 
  8.  
  9. @property (nonatomic, assign) NSInteger year; 
  10.  
  11. - (void)startEngine; 
  12.  
  13. - (void)pressGasPedal; 
  14.  
  15. - (void)pressBrakePedal; 
  16.  
  17. @end 

 

Car.m

  1. #import "Car.h" 
  2.  
  3. @implementation Car 
  4.  
  5. - (void)startEngine 
  6.    NSLog(@"Starting the engine."); 
  7.  
  8. - (void)pressGasPedal 
  9.     NSLog(@"Accelerating..."); 
  10.  
  11. - (void)pressBrakePedal 
  12.     NSLog(@"Decelerating..."); 
  13.  
  14. @end 

 

現在,當我們想要生產一個全新的,具有獨一無二特性的汽車模型時,我們用Car父類作爲切入點,然後在子類中添加自定義行爲。

 

Toyota.h

  1. #import "Car.h" 
  2.  
  3. @interface Toyota : Car 
  4.  
  5. - (void)preventAccident; 
  6.  
  7. @end 

 

Toyota.m

  1. #import "Toyota.h" 
  2.  
  3. @implementation Toyota 
  4.  
  5. - (void)startEngine 
  6.     // Perform custom start sequence, different from the superclass 
  7.     NSLog(@"Starting the engine."); 
  8.  
  9. - (void)preventAccident 
  10.     [self pressBrakePedal]; 
  11.     [self deployAirbags]; 
  12.  
  13. - (void)deployAirbags 
  14.    NSLog(@"Deploying the airbags."); 
  15. @end 

即使pressBrakePedal是在Car類中聲明的,由於繼承關係,仍能在Toyota類中訪問該方法。

 

指派初始值

通常,爲了簡單的實例化,類允許指定的類初始化。如果你爲類重載了一個主要的指派初始值,或者提供了一個指派初始值,你需要確保也重載了其他指派初始值,以便他們可以使用新的實現而不是父類的實現。如果你忘記這麼做了,並在子類上調用二級指派初始值之一,那麼他們將得到父類的行爲。

  1. // The new designated initializer for this class 
  2.  
  3. - (instancetype)initWithFullName:(NSString *)fullName 
  4.     if (self = [super init]) { 
  5.         _fullName = fullName; 
  6.         [self commonSetup]; 
  7.     } 
  8.     return self; 
  9.  
  10. // Provide a sensible default for other initializers 
  11. - (instancetype)init 
  12.     return [self initWithFullName:@"Default User"]; 

 

在比較特別的用例中,如果你不使用默認的初始值,你應該拋出一個異常併爲他們提供一個替代的辦法:

  1. - (instancetype)init { 
  2.         [NSException raise:NSInvalidArgumentException 
  3.   format:@"%s Using the %@ initializer directly is not supported. Use %@ instead.", __PRETTY_FUNCTION__, 
  4. NSStringFromSelector(@selector(init)), NSStringFromSelector(@selector(initWithFrame:))]; 
  5.     return nil; 

 

重載方法

如果你創建了另一類的子類來重載函數,你必須要謹慎。如果你想要保留父類的用法但是隻做稍稍的修改,你可以在重載內調用父類,如下:

  1. - (void)myMethod 
  2.    [super myMethod]; 
  3.    // Provide your additional custom behavior here 

 

如果你不希望任何重載超類的方法,只要不調用父類即可。但是注意沒有任何內存或對象生命週期的影響來這樣做。

 

此外,如果父類有原始的方法,其他派生類在原始方法上實現,你必須確保你重載了所有必需的原始方法,保證派生的方法能正常工作。

 

注意事項

某些類不能輕易地派生出子類,因此在這種情況下,不鼓勵用子類。例如,派生類似NSString和NSNumber的類簇。

 

類簇中有很多私有類,所以很難確保你已經重載了所有基本方法,並在類簇中適當地指派初始值。

 

Swizzling

通常來講頭腦清晰比聰明更重要。作爲一個一般規則,在方法實現中處理一個bug比用method swizzling代替這種方法更好一些。原因是別人使用你的代碼時可能不會意識到你替換了方法實現,然後他們會一直想不通爲什麼這個方法不響應默認的屬性。

 

因此,我們不在這裏討論method swizzling,但是你可以在這裏查閱(link)。

 

錯誤處理

通常有三個方式處理錯誤:斷言、異常和可恢復錯誤。斷言和異常的情況通常只用在很少的用例上,因爲你的應用程序崩潰的話顯然不是一個很好的用戶體驗。

 

斷言

斷言通常用於當你想要確定是什麼值的時候。如果不是正確的值,你就會被迫突出應用程序。

  1. NSAssert(someCondition, @"The condition was false, so we are exiting the app."); 

Important: Do not call functions with side effects in the condition parameter of this macro. The condition parameter is not evaluated when assertions are disabled, so if you call functions with side effects, those functions may never get called when you build the project in a non-debug configuration. 

 

異常

異常通常用於編程或者不能預料的運行錯誤。例如,試圖調用一個有五個元素的數組的第六個元素(越界訪問)、試圖改變不可變對象、給對象發送一個無效消息。通常你會在創建應用程序而不是運行時來處理異常錯誤。

 

正如這樣一個例子:你需要使用API密鑰才能使用庫。

  1. // Check for an empty API key 
  2.  
  3. - (void)checkForAPIKey 
  4.     if (!self.apiKey || !self.apiKey.length) {  
  5.         [NSException raise:@"Forecastr" format:@"Your Forecast.io API key must be populated before you can access the API.", nil];  
  6.     }  

 

Try-Catch語句

如果你擔心一個代碼塊會拋出異常,你可以將它放在一個Try-Catch語句塊中,但是一定要記住這會稍微影響性能。

  1. @try {  
  2.     // The code to try here 
  3.  
  4. @catch (NSException *exception) {  
  5.     // Handle the caught exception  
  6.  
  7. @finally {  
  8.     // Execute code here that would run after both the @try and @catch blocks 

 

可恢復錯誤

很多時候,方法會在一個故障代碼塊中返回一個NSError對象或者是指針的指針(NSFileManager情況)。這通常返回的是一個可恢復的錯誤,並提供一個更好地用戶交互,因爲他們可以提示用戶哪裏出錯了。

  1. [forecastr getForecastForLocation:location success:^(id JSON) { 
  2.  
  3.     NSLog(@"JSON response was: %@", JSON); 
  4.  
  5. } failure:^(NSError *error, id response) { 
  6.  
  7.     NSLog(@"Error while retrieving forecast: %@", error.localizedDescription); 
  8.  
  9. }]; 

 

創建自己的錯誤

創建自己的NSError對象返回方法也很有可能。

  1. // Error domain & enums 
  2.  
  3. NSString *const kFCErrorDomain = @"com.forecastr.errors"
  4.  
  5. typedef NS_ENUM(NSInteger, ForecastErrorType) { 
  6.  
  7.     kFCCachedItemNotFound, 
  8.  
  9.     kFCCacheNotEnabled 
  10.  
  11. }; 
  12.  
  13. @implementation Forecastr 
  14.  
  15. - (void)checkForecastCacheForURLString:(NSString *)urlString 
  16.  
  17.                                success:(void (^)(id cachedForecast))success 
  18.  
  19.                                failure:(void (^)(NSError *error))failure  
  20.  
  21.     // Check cache for a forecast 
  22.  
  23.     id cachedItem = [forecastCache objectForKey:urlString]; 
  24.  
  25.     if (cachedItem) { 
  26.  
  27.         success(cachedItem); 
  28.  
  29.     } else { 
  30.  
  31.         // Return an error since it wasn't found 
  32.  
  33.         failure([NSError errorWithDomain:kFCErrorDomain code:kFCCachedItemNotFound userInfo:nil]); 
  34.  
  35.     } 
  36.  
  37.  
  38. @end 

 

信息傳遞

我們已經討論很多類間信息傳遞的方法了,例如通過方法或者委託,但是我們在這裏也稍作討論,並再舉一個委託的例子。

 

通過委託傳遞信息

將數據從一個視圖控制器傳遞到另一個視圖控制器的常見方法是使用委託方法。例如,如果你有一個帶有表格的模態視圖顯示在你的視圖控制器之上,那麼你需要知道用戶點擊的是哪個表格。

AddPersonViewController.h (the modal view)

  1. #import <UIKit/UIKit.h> 
  2.  
  3. #import "Person.h" 
  4.  
  5. @protocol AddPersonTableViewControllerDelegate <NSObject> 
  6.  
  7. - (void)didSelectPerson:(Person *)person; 
  8.  
  9. @end 
  10.  
  11. @interface AddPersonTableViewController : UITableViewController 
  12.  
  13. @property (nonatomic, weak) id <AddPersonTableViewControllerDelegate>delegate; 
  14.  
  15. @end 

 

AddPersonViewController.m

  1. // Other implementation details left out 
  2.  
  3. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 
  4.  
  5.  
  6.     Person *person = [people objectAtIndex:indexPath.row]; 
  7.  
  8.     [self.delegate didSelectPerson:person]; 
  9.  

 

GroupViewController.m (the normal view)

  1. // Other implementation details left out, such as showing the modal view 
  2.  
  3. // and setting the delegate to self 
  4.  
  5. #pragma mark - AddPersonTableViewControllerDelegate 
  6.  
  7. - (void)didSelectPerson:(Person *)person 
  8.  
  9.     [self dismissViewControllerAnimated:YES completion:nil]; 
  10.  
  11.     NSLog(@"Selected person: %@", person.fullName); 
  12.  

 

我們忽略了一些實現的細節。例如,AddPersonTableViewControllerDelegate的規則,但是你可以在委託章節學習到。

 

另外,注意我們沒有考慮到相同類中的最初顯示它的模態視圖控制器(AddPersonViewController)。這是蘋果推薦使用的方法。

 

NSNotificationCenter(通知中心)

通知是廣播消息,用於在運行時分離類間耦合併在對象之間建立匿名通信。通知可以由任何數量的對象發佈和接受,因此在對象間可以建立一對多和多對多的關係。

 

注:通知是同步發送的,所以如果你的觀察方法需要很長時間才能返回,你實際上是阻止了給其他觀察對象傳遞通知。

 

註冊觀察者

爲了獲得某一事件發生的通知,你可以先註冊。事件包括系統通知,例如UITextField開始編輯。

  1. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textFieldDidBeginEditing:) 
  2.  
  3. name:UITextFieldTextDidBeginEditingNotification object:self]; 

 

當OS系統框架廣播UITextFieldTextDidBeginEditingNotification通知時,NSNotificationCenter將調用textFieldDidBeginEditing:並且對象與包含數據的通知一起發送。

 

textFieldDidBeginEditing:方法的一種可能實現是:

  1. #pragma mark - Text Field Observers 
  2. - (void)textFieldDidBeginEditing:(NSNotification *)notification 
  3.     // Optional check to make sure the method was called from the notification 
  4.     if ([notification.name isEqualToString:UITextFieldTextDidBeginEditingNotification]) 
  5.     { 
  6.        // Do something 
  7.     } 

 

刪除觀察者

釋放類的同時刪除觀察者是很重要的,否則NSNotificationCenter將試圖在已經釋放掉的類中調用方法,這將會引起崩潰。

  1. - (void)dealloc 
  2.     [[NSNotificationCenter defaultCenter] removeObserver:self]; 

 

發佈通知

你也可以創建和發送自己的通知。最好將通知名稱保存在一個常量文件中,這樣你就不會因爲一不小心拼錯了通知名稱而坐在那裏試圖找出爲什麼不能發送或者接收通知。

 

通知的命名

通知是由NSString對象的識別的,通知的名稱是有以下方式組成的:

[Name of associated class] + [Did | Will] + [UniquePartOfName] + Notification

 

聲明一個字符串常量,將通知的名稱作爲字符串的值:

  1. // Remember to put the extern of this in the header file 
  2. NSString *const kRPAppDidResumeFromBackgroundNotification = @"RPAppDidResumeFromBackgroundNotification"

 

發佈通知

  1. [[NSNotificationCenter defaultCenter] postNotificationName:kRPAppDidResumeFromBackgroundNotification object:self]; 

 

視圖控制器屬性

當你準備顯示一個新的視圖控制器時,你可以在展示之前爲屬性分配數據:

  1. MyViewController *myVC = [[MyViewController alloc] init]; 
  2.  
  3. myVC.someProperty = self.someProperty; 
  4.  
  5. [self presentViewController:myVC animated:YES completion:nil]; 

 

Storyboard Segue

當你在storyboard中在兩個視圖控制器之間切換時,使用prepareForSegue:sender:方法可以簡單的實現數據的傳遞,如下:

  1. #pragma mark - Segue Handler 
  2. - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender 
  3.     if ([segue.identifier isEqualToString:@"showLocationSearch"] { 
  4.  
  5.         [segue.destinationViewController setDelegate:self]; 
  6.     } else if ([segue.identifier isEqualToString:@"showDetailView"]) { 
  7.  
  8.         DetailViewController *detailView = segue.destinationViewController; 
  9.  
  10.         detailView.location = self.location; 
  11.     } 

 

用戶默認值

用戶默認值是存儲簡單偏好的基本方法,在應用程序啓動時可以保存和還原這些偏好值。這並不意味着它被用作像Core Data或者sqlite那樣的數據存儲層。

 

存儲值

  1. NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; 
  2.  
  3. [userDefaults setValue:@"Some value" forKey:@"RPSomeUserPreference"]; 
  4.  
  5. [userDefaults synchronize]; 

記住一定要在默認實例上調用synchronize以確保正確保存值。

 

檢索值

  1. NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; 
  2.  
  3. id someValue = [userDefaults valueForKey:@"RPSomeUserPreference"]; 

NSUserDefaults實例中也有其他簡單的方法,例如boolForKey:, stringForKey:等

 

 

常見模式

單例模式

單例模式是一種特殊的類,在當前進程中只能一個類包含一個實例。單例模式對於在一個應用程序的不同部分中共享數據很便利,並且不需要創建全局變量或者手動傳遞數據。但是,因爲它們經常創建類之間的緊耦合,所以儘量少使用單例模式。

 

將一個類轉換成單例模式,你需要將下面的方法寫到實現文件(.m)中,方法名由shared加上一個單詞組成,這樣可以很好的描述你的類。例如,如果這個類是一個網絡或位置管理器,你可以將這個方法命名爲sharedManager,而不是sharedInstance。

  1. + (instancetype)sharedInstance 
  2.    static id sharedInstance = nil; 
  3.    static dispatch_once_t onceToken; 
  4.  
  5.    dispatch_once(&onceToken, ^{ 
  6.       sharedInstance = [[self alloc] init]; 
  7.    }); 
  8.    return sharedInstance; 

使用dispatch_once可以保證這個方法只被執行一次,即使它在很多類或者進程之間調用很多次。

 

如果在MyClass中替換上面的代碼,那麼用下面的代碼你將會在另一個類中得到一個單例模式類的引用:

  1. MyClass *myClass = [MyClass sharedInstance]; 
  2.  
  3. [myClass doSomething]; 
  4.  
  5. NSLog(@"Property value is %@", myClass.someProperty); 

原文:Objective-C: Cheat Sheet

 

推薦閱讀:

Objective-C基礎:Objective-C速成  


Objective-C相關Category的收集 


Objective-C初學者速查表  


iOS應用開發最佳實踐:編寫高質量的Objective-C代碼  


(譯)Objective-C的動態特性  


談Objective-C Block的實現 


談談Objective-C的警告  


對Objective-C中Block的追探  


Objective-C 的“多繼承” 


那些被遺漏的Objective-C保留字 

 


CocoaChina是全球最大的蘋果開發中文社區,官方微信每日定時推送各種精彩的研發教程資源和工具,介紹app推廣營銷經驗,最新企業招聘和外包信息,以及Cocos2d引擎、Cocostudio開發工具包的最新動態及培訓信息。關注微信可以第一時間瞭解最新產品和服務動態,微信在手,天下我有!

請搜索微信號“CocoaChina”關注我們!

發佈了40 篇原創文章 · 獲贊 4 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章