Objective-C学习备忘单

FROM http://www.cocoachina.com/industry/20140428/8255.html



终极版本的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开发者们提供重要的额外信息。注释通常会被编译器忽视因此它们不会增加编译器程序的大小。

 

有两种方式添加注释

// This is an inline comment内嵌注释 /* This is a block comment  and it can span multiple lines. */块注释可用于多行注释 // You can also use it to comment out code也可以用来注释掉代码  /* - (SomeOtherClass *)doWork {     // Implement this } */

 

使用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无符号代表只能是正整数。

 

整数类型以及它们的字节大小

// Char (1 byte for both 32-bit and 64-bit)  unsigned char anUnsignedChar = 255; NSLog(@"char size: %zu", sizeof(char));  // Short (2 bytes for both 32-bit and 64-bit) short aShort = -32768; unsigned short anUnsignedShort = 65535; NSLog(@"short size: %zu", sizeof(short));  // Integer (4 bytes for both 32-bit and 64-bit) int anInt = -2147483648; unsigned int anUnsignedInt = 4294967295; NSLog(@"int size: %zu", sizeof(int));  // Long (4 bytes for 32-bit, 8 bytes for 64-bit) long aLong = -9223372036854775808; // 32-bit unsigned long anUnsignedLong = 18446744073709551615; // 32-bit NSLog(@"long size: %zu", sizeof(long));  // Long Long (8 bytes for both 32-bit and 64-bit) long long aLongLong = -9223372036854775808; unsigned long long anUnsignedLongLong = 18446744073709551615; NSLog(@"long long size: %zu", sizeof(long long));

 

固定宽度的整数类型和字节大小来作为变量名

// Exact integer types  int8_t aOneByteInt = 127;  uint8_t aOneByteUnsignedInt = 255;  int16_t aTwoByteInt = 32767;  uint16_t aTwoByteUnsignedInt = 65535;  int32_t aFourByteInt = 2147483647;  uint32_t aFourByteUnsignedInt = 4294967295;  int64_t anEightByteInt = 9223372036854775807;  uint64_t anEightByteUnsignedInt = 18446744073709551615;   // Minimum integer types  int_least8_t aTinyInt = 127;  uint_least8_t aTinyUnsignedInt = 255;  int_least16_t aMediumInt = 32767;  uint_least16_t aMediumUnsignedInt = 65535;  int_least32_t aNormalInt = 2147483647;  uint_least32_t aNormalUnsignedInt = 4294967295;  int_least64_t aBigInt = 9223372036854775807;  uint_least64_t aBigUnsignedInt = 18446744073709551615;   // The largest supported integer type  intmax_t theBiggestInt = 9223372036854775807;  uintmax_t theBiggestUnsignedInt = 18446744073709551615;

 

浮点类型

浮点没有signed或者unsigned

// Single precision floating-point (4 bytes for both 32-bit and 64-bit)单精度浮点float aFloat = 72.0345f;  NSLog(@"float size: %zu", sizeof(float));   // Double precision floating-point (8 bytes for both 32-bit and 64-bit)双精度浮点  double aDouble = -72.0345f;  NSLog(@"double size: %zu", sizeof(double));   // Extended precision floating-point (16 bytes for both 32-bit and 64-bit)扩展精度浮点  long double aLongDouble = 72.0345e7L;  NSLog(@"long double size: %zu", sizeof(long double));

 

Objective-C基础

id被定义为匿名或者动态对象类型它可以存储任何类型对象的引用不需要指定指针符号。

id delegate = self.delegate;

 

Class用来表示对象的类并能用于对象的内省。

Class aClass = [UIView class];

 

Method用来表示一个方法并可用于swizzling方法。

Method aMethod = class_getInstanceMethod(aClass, aSelector);

 

SEL用于指定一个selector它是编译器指定的代码用于识别方法的名称。

SEL someSelector = @selector(someMethodName);

 

IMP用于在方法开始时指向内存地址。你可能不会用到这个。

IMP theImplementation = [self methodForSelector:someSelector];

 

BOOL用来指定一个布尔类型布尔类型中0值被认 为是NOfalse任何非零值被认为是YEStrue。任何零值对象都被认为是NO因此不需要对零值进行同样的验证例如只要写 if(someObject)不需要写if (someObject !=nil)

// Boolean BOOL isBool = YES; // Or NO

 

nil用来指定一个空对象指针。当类第一次被初始化时类中所有的属性被设置为0这意味着都指向空指针。

 

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

 

Enum枚举和Bitmask位掩码类型

Objective-C的枚举类型可以用以下多个不同方式定义

// Specifying a typed enum with a name (recommended way)用一个别名来指定一个带有typedef关键字的枚举类型推荐方法  typedef NS_ENUM(NSInteger, UITableViewCellStyle) {      UITableViewCellStyleDefault,      UITableViewCellStyleValue1,      UITableViewCellStyleValue2,      UITableViewCellStyleSubtitle  };   // Specify a bitmask with a name (recommended way)用一个别名来指定一个bitmask推荐方法  typedef NS_OPTIONS(NSUInteger, RPBitMask) {      RPOptionNone      = 0,      RPOptionRight     = 1 << 0,      RPOptionBottom    = 1 << 1,      RPOptionLeft      = 1 << 2,      RPOptionTop       = 1 << 3  };   // Other methods:其他方法  // Untyped无类型  enum {      UITableViewCellStyleDefault,      UITableViewCellStyleValue1,      UITableViewCellStyleValue2,      UITableViewCellStyleSubtitle  };   // Untyped with a name 用一个别名来定义一个带有typedef关键字将枚举类型  typedef enum {      UITableViewCellStyleDefault,      UITableViewCellStyleValue1,      UITableViewCellStyleValue2,      UITableViewCellStyleSubtitle  } UITableViewCellStyle;

 

构造数据类型

有时为一个特定的类或者数据类型构造一个id或者不同的类型时很有必要的。例如从一个浮点型构造成整数或者从一个UITableViewCell构造成一个如RPTableViewCell的子类。

 

构造非对象数据类型cast

// Format: nonObjectType variableName = (nonObjectType)  variableNameToCastFrom;  int anInt = (int)anAnonymouslyTypedNonObjectOrDifferentDataType;

 

构造对象数据类型

// Format: ClassNameOrObjectType *variableName =(ClassNameOrObjectType *)variableNameToCastFrom;  UIViewController *aViewController = (UIViewController *)  anAnonymouslyTypedObjectOrDifferentDataType;

 

常量

使用常量通常是一个更好的方法因为常量为代码中的任何对象的引用指向相同的内存地址。#define定义了一个宏。在编译开始前宏用实际常量值来代替所有的引用而不是作为指向常量值的内存指针。

 

Objective-C常量可以这样定义

// Format: type const constantName = value; NSString *const kRPShortDateFormat = @"MM/dd/yyyy";  // Format: #define constantName value #define kRPShortDateFormat @"MM/dd/yyyy"

 

要是在扩展类中能使用常量你必须也将它添加在头文件.h中。

extern NSString *const kRPShortDateFormat;

 

如果你知道一个常量只可用于它包含的.m文件中可以这样指定它

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

#import "SomeClass.h"  // Used instead of #import to forward declare a class in property return types, etc.  @class SomeOtherClass;  // Place all global constants at the top extern NSString *const kRPErrorDomain;  // Format: YourClassName : ClassThatYouAreInheritingFrom  @interface MyClass : SomeClass  // Public properties first  @property (readonly, nonatomic, strong) SomeClass *someProperty;  // Then class methods  + (id)someClassMethod;  // Then instance methods  - (SomeOtherClass *)doWork;  @end

 

MyClass.m

#import "MyClass.h"  #import "SomeOtherClass.h"  // Declare any constants at the top  NSString *const kRPErrorDomain = @"com.myIncredibleApp.errors";  static NSString *const kRPShortDateFormat = @"MM/dd/yyyy";   // Class extensions for private variables / properties  @interface MyClass () {     int somePrivateInt;      // Re-declare as a private read-write version of the public read-only property      @property (readwrite, nonatomic, strong) SomeClass  *someProperty;  }  @end   @implementation MyClass   // Use #pragma mark - statements to logically organize your code  #pragma mark - Class Methods   + (id)someClassMethod {    return [[MyClass alloc] init]; }   #pragma mark - Init & Dealloc methods  - (id)init {     if (self = [super init]) {          // Initialize any properties or setup code here     }      return self;  }   // Dealloc method should always follow init method  - (void)dealloc {      // Remove any observers or free any necessary cache, etc.      [super dealloc]; }  #pragma mark - Instance Methods  - (SomeOtherClass *)doWork {     // Implement this }  @end

 

实例化

当想要创建类的新实例时你需要使用如下语法

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

alloc类方法返回一个指针指向一个新分配的内存块这个内存块空间足够大可以储存这个类的一个实例。这个分配的内存中包含了所有 Objective-C对象都必须有的实例变量和isa指针。isa指针变量自动初始化指向类对象类对象分配内存并使实例能够接收消息比如用来完成初 始化的init。

 

预处理器指令

本节仍有需要改进的地方

 

编译器指令

请参考literal字面章节

 

类和协议

 

属性

 

错误

 

实例变量的可变性

默认的是@protected类型所以不用明确地指定此类型。

 

其他

 

Literals字面语法

字面语法是的编译器指令它提供简化符号来创建对象。

NSArray访问语法

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

NSDictionary访问语法

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

注意事项与NSString类似通过常量数组和字典收集对象是不可变的。相反你必须在创建这个不可变的字典或者数组后创建一个可变的副本。此外你不能像使用NSString那样做静态初始化。

 

方法

声明语法

方法没有返回值类型时用void定义

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

 

用“+”调用之前声明的类方法

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

用“-”调用之前声明的类的实例方法

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

 

在“”后面声明方法参数方法签名应该描述参数类型

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

 

使用强制转换语法声明参数和返回值类型

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

 

方法调用

使用方括号语法调用方法 [self someMethod]或者[selfsometMethodWithObject:object];

 

self是对包含类的方法的一个引用。self变量存在于所有Objective-C方法中。它是传递给代码用以执行方法的两个隐藏参数之一。另外一个是_cmd用于识别接收到的消息。

 

有时很有必要使用[super someMethod];在父类中调用一个方法。

 

在高级选项下方法通过消息传递实现并且转换成了这两个C函数其中一个的

id objc_msgSend(id self, SEL op, ...); 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

 

测试选择器

如果你想要测试一个类是否在被发送或者可能会崩溃之前响应一个特定的选择器你可以这样做

if ([someClassOrInstance respondsToSelector:@selector (someMethodName)]) {      // Call the selector or do something here }

当你实现一个委托并且在委托对象上调用这些声明之前你需要对声明为@optional的方法进行测试这种模式很常见。

 

属性和变量

声明一个Objective-C属性允许你保留类中对象的一个引用或者在类间传递对象。

 

在头文件 ( .h)中声明公共属性

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

 

在实现文件 ( .m)的匿名类或者扩展类中声明私有属性

#import "MyClass.h"  // Class extension for private variables / properties  @interface MyClass () {     // Instance variable     int somePrivateInteger;      // Private properties     @property (nonatomic, strong) NSString *firstName;     @property (nonatomic, strong) NSString *lastName;     @property (readwrite, nonatomic, strong) NSString *fullName; } @end  @implementation MyClass  // Class implementation goes here  @end

 

LLVM编译器自动综合所有属性因此不再需要为属性写明@synthesize语句。当一个属性被综合时就会创建accessors允许你设置或者获得属性的值。尽管你可能不会看到它们因为它们是在构建时创建的但是一对getter/setter可以显示为

- (BOOL)finished {      return _finished; }  - (void)setFinished:(BOOL)aValue {      _finished = aValue; }

 

你可以重写属性的getter和seeter来创建自定义的行为或者甚至是使用这个模式来创建瞬态属性如下

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

 

属性后面通常会有一个带有前导下划线的实例变量因此创建一个名为fistName的属性会带有一个名为_firstName的实例变量。如果你重写getter/setter或者你需要在类的init方法中设置ivar你只需要访问私有实例变量。

 

属性特性

当指定一个属性时使用如下语法

@property SomeClass *someProperty; // Or @property (xxx) SomeClass *someProperty;

 

xxx可以与什么组合

 

 

访问属性

使用括号或者点操作都可以访问属性点操作读起来更清晰

[self myProperty]; // Or self.myProperty

 

局部变量

局部变量只存在于方法的范围内。

- (void)doWork  {    NSString *localStringVariable = @"Some local string variable.";     [self doSomethingWithString:localStringVariable]; }

 

命名约定

一般的经验法则: 清晰和简洁都是重要的但是清晰更重要。

 

方法和属性

都是用驼峰式拼写法第一个单词的首字母为小写其他单词的首字母都大写。

 

类名和协议

都是用大写字母每个单词的首字母都大写。

 

方法

如果执行一些动作那么应该使用动词如performInBackground。你应该推断出知道发生了什么方法需要什么参数或者只通过阅读一个方法签名就知道返回了什么。例如

// Correct - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {     // Code }  // Incorrect (not expressive enough) - (UITableViewCell *)table:(UITableView *)tableView cell:(NSIndexPath *)indexPath {     // Code }

 

属性和局部变量

使用属性时在内部创建一个带有前导下划线的实例变量因此创建myVariableName为_myVariableName。然而Objective-C现在为你综合这些属性因此你不再需要直接访问带有下划线的实例变量除非在一个自定义setter中。

 

相反通常用selfl访问和变异mutated实例变量。

 

局部变量不能有下划线。

 

常量

常量通常以k和 XXX命名方式 XXX是一个前缀或许你的首字母可以避免命名冲突。你不应该害怕你要表达的常量名称尤其是全局变量时。使用kRPNavigationFadeOutAnimationDuration比fadeOutTiming好的多。

 

Blocks

在Objective-C中块block实际上是一个匿名函数用来在方法间传递代码或者在函数回调时执行代码。由于Block实现为闭包周围的语句也被捕获有时会导致retain cycles。

 

语法

// As a local variable returnType (^blockName)(parameterTypes) = ^returnType(parameters) {      // Block code here };   // As a property @property (nonatomic, copy) returnType (^blockName)(parameterTypes);    // As a method parameter - (void)someMethodThatTakesABlock:(returnType (^)(parameterTypes))blockName {    // Block code here };   // As an argument to a method call  [someObject someMethodThatTakesABlock: ^returnType (parameters) {      // Block code here  }];   // As a typedef typedef returnType (^TypeName)(parameterTypes);  TypeName blockName = ^(parameters) {      // Block code here };

 

查看我们专门的文章了解更多细节Objective-C Programming with blocks

 

变异的block变量

由于block中的变量正是它们在block区域外部的一个快照你必须在block内将你的变量用_block变异例如

__block int someIncrementer = 0;  [someObject someMethodThatTakesABlock:^{      someIncrementer++;  }];

 

Retain cycles

由于block有力地捕捉区域内的所有变量你必须谨慎设置block代码以下为retain cycle的两个例子

[someObject someMethodThatTakesABlock:^{      [someObject performSomeAction];  // Will raise a warning  }];   [self someMethodThatTakesABlock:^{      [self performSomeAction];       // Will raise a warning  }];

 

这两个例子中执行block的对象都拥有blockblock也拥有对象。这行形成一个回路或者一个retain cycle这就意味着内存最终将会泄露。

 

为了解决这个警告你可以重构代码

[self someMethodThatTakesABlock:^{      [object performSomeAction];   // No retain cycle here  }];

 

或者你可以用一个_weak对象

__weak typeof(self) weakSelf = self;  [self someMethodThatTakesABlock:^{      [weakSelf performSomeAction];  // No retain cycle here  }];

 

控制语句

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

 

If-Else If-Else

if (someTestCondition) {      // Code to execute if the condition is true  } else if (someOtherTestCondition) {      // Code to execute if the other test condition is true  } else {      // Code to execute if the prior conditions are false  }

 

三元运算符

if-else的一个速记语句就是一个三元运算符形式someTestCondition ? doIfTrue : doIfFalse;

例如

- (NSString *)stringForTrueOrFalse:(BOOL)trueOrFalse {      return trueOrFalse ? @"True" : @"False"; }

这还有一个不太常用的形式A ?: B基本上是指如果A是正确的或者非空的就返回A反之返回B。

 

For循环

更多详解请看Objective-C: Loops

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

 

快速枚举

for (Person *person in arrayOfPeople) {     // Code to execute each time }

arrayOfPeople可以是符合NSFastEnumeration协议的任何对象. NSArray和NSSet列举它们的对象NSDictionary列举关键词NSManagedObjectModel列举实体。

 

While循环

while (someTextCondition) {    // Code to execute while the condition is true }

 

Do While循环

do {     // Code to execute while the condition is true } while (someTestCondition);

 

Switch转换

switch语句通常用来代替if语句如果需要测试一个指定的变量的值是否匹配另一个常量或者变量值。例如你或许想要测试你接收到的error代码整数是否匹配现有的常数值或者如果它是一个新的错误代码。

switch (errorStatusCode) {     case kRPNetworkErrorCode:          // Code to execute if it matches          break;     case kRPWifiErrorCode:          // Code to execute if it matches          break;     default:          // Code to execute if nothing else matched          break; }

 

退出循环

1. return停止执行返回到调用的函数。可以用于返回一个方法的值。

2. break用来停止执行一个循环。

 

新的枚举方法有一个特别的BOOL变量如BOOL *stop用来停止执行循环。在循环内将变量设置为YES类似于调用break。

 

枚举

在statements章节已经提高了快速枚举但是很多集合类也有他们自己的基于block的方法来枚举一个集合。

 

基于block的方法与快捷枚举执行方法几乎相同但是他们为枚举集合提供了额外的选择。在NSArray上基于block的枚举示例如下

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

 

扩展类

在Objective-C中有几个不同方法可以扩展类其中一些方法更简单。

 

继承

继承本质上允许你创建特定的子类这些子类继承父类头文件中所有的指定为@ public或@protected的方法和属性时通常有指定的用法。

 

通过任何框架或者开源项目你可以看到使用继承不仅获得开放的用法也整合代码使之很容易重用。可在任何可变框架类中看到这种方法的例子,如NSMutableString就是NSString的一个子类。

 

在Objective-C中通过类的继承所有对象都有很多NSObject class指定的行为。没有继承你需要自己实现基本的方法如检查对象或者类对等。最终你将会在类中会有很多重复性的代码。

// MyClass inherits all behavior from the NSObject class @interface MyClass : NSObject  // Class implementation @end

继承本质上创建了类之间的耦合因此要仔细想想这个用法。

 

类别

Objective-C类别是一个非常有用并且简单的扩展类的方法尤其是当你没有访问源码例如Cocoa Touch框架和类时。可以为任何一个类或者方法声明一个category并且在类别中声明的方法对原始类及其子类中所有实例都可用。在运行时通过 类别添加一个方法与通过原始类实现一个方法没有什么不同。

 

类别也可用于

1.声明非正式协议

2.为相关的方法分类似于有多个类

3.将大型类的实现分解成多个类别这有助于增量编译

4.很容易为不同的应用程序配置不同的类

 

实现

类别的命名格式为ClassYouAreExtending + DescriptorForWhatYouAreAdding

 

例如假设我们需要为UIImage类以及所有子类中添加一个新的方法以便我们可以很容易地调整实例大小或者获得实例 在这个类中很容易的重调或者拷贝实例。那么你需要用如下实现方法来创建一个名为UIImage+ResizeCrop的头文件和实现文件。

UIImage+ResizeCrop.h

@interface UIImage (ResizeCrop) - (UIImage *)croppedImageWithSize:(CGSize)size; - (UIImage *)resizedImageWithSize:(CGSize)size; @end

 

UIImage+ResizeCrop.m

#import "UIImage+ResizeCrop.h" @implementation UIImage (ResizeCrop) - (UIImage *)croppedImageWithSize:(CGSize)size {     // Implementation code here }  - (UIImage *)resizedImageWithSize:(CGSize)size {     // Implementation code here }

 

然后你可以在任何UIImage类或者其子类上调用这些方法

UIImage *croppedImage = [userPhoto croppedImageWithSize:photoSize];

 

关联引用

除非你在编译时访问类的源码否则不太可能通过使用category为那个class添加实例变量和属性。相反你必须通过使用Objective-C运行时的特性即关联引用。

 

例如假设我们想要在UIScrollView类中添加一个公共属性来存储一个UIView对象的引用但是我们不能访问UISCrollView 的源码。我们就需要在UIScrollView中创建一个类别然后为这个新属性创建一对getter/setter方法来存储这个引用如下

UIScrollView+UIViewAdditions.h

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

 

UIScrollView+UIViewAdditions.m

#import "UIScrollView+UIViewAdditions.h" #import <objc/runtime.h>  static char UIScrollViewMyCustomView;  @implementation UIScrollView (UIViewAdditions)  @dynamic myCustomView;  - (void)setMyCustomView:(UIView *)customView {     [self willChangeValueForKey:@"myCustomView"];      objc_setAssociatedObject(self, &UIScrollViewMyCustomView, customView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);      [self didChangeValueForKey:@"myCustomView"]; }  - (UIView *)myCustomView {     return objc_getAssociatedObject(self, &UIScrollViewMyCustomView); }  @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和指向静态密钥的指针来检索对象引用。

 

我们可以像使用其他属性一样使用这个属性

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

 

文档中更多的信息

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中。

// Class extensions for private variables / properties @interface MyClass () {     int somePrivateInt;     // Re-declare as a private read-write version of the public read-only property     @property (readwrite, nonatomic, strong) SomeClass *someProperty; } @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文件中

#import "RPLocationManager.h"  @interface MyViewController : UIViewController <RPLocationManagerDelegate, UITableViewDelegate, UITableViewDataSource>  @end

 

方法二 : 在 .m 文件中

#import "MyViewController.h" @interface MyViewController () <RPLocationManagerDelegate, UITableViewDelegate, UITableViewDataSource> {     // Class extension implementation } @end

 

创建你自己的协议

创建自己的协议使其他类遵守如下语法

RPLocationManager.h

#import <SomeFrameworksYouNeed.h> // Declare your protocol and decide which methods are required/optional // for the delegate class to implement  @protocol RPLocationManagerDelegate <NSObject> - (void)didAcquireLocation:(CLLocation *)location; - (void)didFailToAcquireLocationWithError:(NSError *)error;  @optional - (void)didFindLocationName:(NSString *)locationName; @end  @interface RPLocationManager : NSObject  // Create a weak, anonymous object to store a reference to the delegate class  @property (nonatomic, weak) id <RPLocationManagerDelegate>delegate;  // Implement any other methods here  @end

当我们声明名@protocol命名RPLocationManagerDelegate时所有的方法都默认成为@required因此不需要明确的声明。然而如果你想要@optional特定的方法来符合类的实现你必须声明它。

 

此外有必要弱声明一个名为delegate的匿名类型的属性这个属性也引用RPLocationManagerDelegate协议。

 

发送委托消息

在上面的例子中RPLocationManager.h声明了一些这个类作为委托方必须要实现的方法。在RPLocationManager.m中你可以以不同的方法实现这里我们只举两个例子

1. @required方法

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

2.@optional方法

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

@required与@optional方法唯一的区别是你经常需要检查下引用委托是否实现了一个optional方法在调用该方法之前。

 

实现委托方法

要实现一个委托方法只要遵守之前讨论的协议然后想一个正常的方法去定义就可以

MyViewController.m - (void)didFindLocationName:(NSString *)locationName {     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

#import <Foundation/Foundation.h>  @interface Car : NSObject  @property (nonatomic, strong) NSString *make;  @property (nonatomic, strong) NSString *model;  @property (nonatomic, assign) NSInteger year;  - (void)startEngine;  - (void)pressGasPedal;  - (void)pressBrakePedal;  @end

 

Car.m

#import "Car.h"  @implementation Car  - (void)startEngine {    NSLog(@"Starting the engine."); }  - (void)pressGasPedal {     NSLog(@"Accelerating..."); }  - (void)pressBrakePedal {     NSLog(@"Decelerating..."); }  @end

 

现在当我们想要生产一个全新的具有独一无二特性的汽车模型时我们用Car父类作为切入点然后在子类中添加自定义行为。

 

Toyota.h

#import "Car.h"  @interface Toyota : Car  - (void)preventAccident;  @end

 

Toyota.m

#import "Toyota.h"  @implementation Toyota  - (void)startEngine {     // Perform custom start sequence, different from the superclass     NSLog(@"Starting the engine."); }  - (void)preventAccident {     [self pressBrakePedal];     [self deployAirbags]; }  - (void)deployAirbags {    NSLog(@"Deploying the airbags."); } @end

即使pressBrakePedal是在Car类中声明的由于继承关系仍能在Toyota类中访问该方法。

 

指派初始值

通常为了简单的实例化类允许指定的类初始化。如果你为类重载了一个主要的指派初始值或者提供了一个指派初始值你需要确保也重载了其他指派初 始值以便他们可以使用新的实现而不是父类的实现。如果你忘记这么做了并在子类上调用二级指派初始值之一那么他们将得到父类的行为。

// The new designated initializer for this class  - (instancetype)initWithFullName:(NSString *)fullName {     if (self = [super init]) {         _fullName = fullName;         [self commonSetup];     }     return self; }  // Provide a sensible default for other initializers - (instancetype)init {     return [self initWithFullName:@"Default User"]; }

 

在比较特别的用例中如果你不使用默认的初始值你应该抛出一个异常并为他们提供一个替代的办法

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

 

重载方法

如果你创建了另一类的子类来重载函数你必须要谨慎。如果你想要保留父类的用法但是只做稍稍的修改你可以在重载内调用父类如下

- (void)myMethod {    [super myMethod];    // Provide your additional custom behavior here }

 

如果你不希望任何重载超类的方法只要不调用父类即可。但是注意没有任何内存或对象生命周期的影响来这样做。

 

此外如果父类有原始的方法其他派生类在原始方法上实现你必须确保你重载了所有必需的原始方法保证派生的方法能正常工作。

 

注意事项

某些类不能轻易地派生出子类因此在这种情况下不鼓励用子类。例如派生类似NSString和NSNumber的类簇。

 

类簇中有很多私有类所以很难确保你已经重载了所有基本方法并在类簇中适当地指派初始值。

 

Swizzling

通常来讲头脑清晰比聪明更重要。作为一个一般规则在方法实现中处理一个bug比用method swizzling代替这种方法更好一些。原因是别人使用你的代码时可能不会意识到你替换了方法实现然后他们会一直想不通为什么这个方法不响应默认的属性。

 

因此我们不在这里讨论method swizzling但是你可以在这里查阅link。

 

错误处理

通常有三个方式处理错误断言、异常和可恢复错误。断言和异常的情况通常只用在很少的用例上因为你的应用程序崩溃的话显然不是一个很好的用户体验。

 

断言

断言通常用于当你想要确定是什么值的时候。如果不是正确的值你就会被迫突出应用程序。

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密钥才能使用库。

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

 

Try-Catch语句

如果你担心一个代码块会抛出异常你可以将它放在一个Try-Catch语句块中但是一定要记住这会稍微影响性能。

@try {      // The code to try here }  @catch (NSException *exception) {      // Handle the caught exception  }  @finally {      // Execute code here that would run after both the @try and @catch blocks }

 

可恢复错误

很多时候方法会在一个故障代码块中返回一个NSError对象或者是指针的指针NSFileManager情况。这通常返回的是一个可恢复的错误并提供一个更好地用户交互因为他们可以提示用户哪里出错了。

[forecastr getForecastForLocation:location success:^(id JSON) {      NSLog(@"JSON response was: %@", JSON);  } failure:^(NSError *error, id response) {      NSLog(@"Error while retrieving forecast: %@", error.localizedDescription);  }];

 

创建自己的错误

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

// Error domain & enums  NSString *const kFCErrorDomain = @"com.forecastr.errors";  typedef NS_ENUM(NSInteger, ForecastErrorType) {      kFCCachedItemNotFound,      kFCCacheNotEnabled  };  @implementation Forecastr  - (void)checkForecastCacheForURLString:(NSString *)urlString                                 success:(void (^)(id cachedForecast))success                                 failure:(void (^)(NSError *error))failure  {      // Check cache for a forecast      id cachedItem = [forecastCache objectForKey:urlString];      if (cachedItem) {          success(cachedItem);      } else {          // Return an error since it wasn't found          failure([NSError errorWithDomain:kFCErrorDomain code:kFCCachedItemNotFound userInfo:nil]);      }  }  @end

 

信息传递

我们已经讨论很多类间信息传递的方法了例如通过方法或者委托但是我们在这里也稍作讨论并再举一个委托的例子。

 

通过委托传递信息

将数据从一个视图控制器传递到另一个视图控制器的常见方法是使用委托方法。例如如果你有一个带有表格的模态视图显示在你的视图控制器之上那么你需要知道用户点击的是哪个表格。

AddPersonViewController.h (the modal view)

#import <UIKit/UIKit.h>  #import "Person.h"  @protocol AddPersonTableViewControllerDelegate <NSObject>  - (void)didSelectPerson:(Person *)person;  @end  @interface AddPersonTableViewController : UITableViewController  @property (nonatomic, weak) id <AddPersonTableViewControllerDelegate>delegate;  @end

 

AddPersonViewController.m

// Other implementation details left out  - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath  {      Person *person = [people objectAtIndex:indexPath.row];      [self.delegate didSelectPerson:person];  }

 

GroupViewController.m (the normal view)

// Other implementation details left out, such as showing the modal view  // and setting the delegate to self  #pragma mark - AddPersonTableViewControllerDelegate  - (void)didSelectPerson:(Person *)person {      [self dismissViewControllerAnimated:YES completion:nil];      NSLog(@"Selected person: %@", person.fullName);  }

 

我们忽略了一些实现的细节。例如AddPersonTableViewControllerDelegate的规则但是你可以在委托章节学习到。

 

另外注意我们没有考虑到相同类中的最初显示它的模态视图控制器AddPersonViewController。这是苹果推荐使用的方法。

 

NSNotificationCenter通知中心

通知是广播消息用于在运行时分离类间耦合并在对象之间建立匿名通信。通知可以由任何数量的对象发布和接受因此在对象间可以建立一对多和多对多的关系。

 

注通知是同步发送的所以如果你的观察方法需要很长时间才能返回你实际上是阻止了给其他观察对象传递通知。

 

注册观察者

为了获得某一事件发生的通知你可以先注册。事件包括系统通知例如UITextField开始编辑。

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

 

当OS系统框架广播UITextFieldTextDidBeginEditingNotification通知时NSNotificationCenter将调用textFieldDidBeginEditing:并且对象与包含数据的通知一起发送。

 

textFieldDidBeginEditing:方法的一种可能实现是

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

 

删除观察者

释放类的同时删除观察者是很重要的否则NSNotificationCenter将试图在已经释放掉的类中调用方法这将会引起崩溃。

- (void)dealloc {     [[NSNotificationCenter defaultCenter] removeObserver:self]; }

 

发布通知

你也可以创建和发送自己的通知。最好将通知名称保存在一个常量文件中这样你就不会因为一不小心拼错了通知名称而坐在那里试图找出为什么不能发送或者接收通知。

 

通知的命名

通知是由NSString对象的识别的通知的名称是有以下方式组成的

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

 

声明一个字符串常量将通知的名称作为字符串的值

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

 

发布通知

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

 

视图控制器属性

当你准备显示一个新的视图控制器时你可以在展示之前为属性分配数据

MyViewController *myVC = [[MyViewController alloc] init];  myVC.someProperty = self.someProperty;  [self presentViewController:myVC animated:YES completion:nil];

 

Storyboard Segue

当你在storyboard中在两个视图控制器之间切换时使用prepareForSegue:sender:方法可以简单的实现数据的传递如下

#pragma mark - Segue Handler - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {     if ([segue.identifier isEqualToString:@"showLocationSearch"] {          [segue.destinationViewController setDelegate:self];     } else if ([segue.identifier isEqualToString:@"showDetailView"]) {          DetailViewController *detailView = segue.destinationViewController;          detailView.location = self.location;     } }

 

用户默认值

用户默认值是存储简单偏好的基本方法在应用程序启动时可以保存和还原这些偏好值。这并不意味着它被用作像Core Data或者sqlite那样的数据存储层。

 

存储值

NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];  [userDefaults setValue:@"Some value" forKey:@"RPSomeUserPreference"];  [userDefaults synchronize];

记住一定要在默认实例上调用synchronize以确保正确保存值。

 

检索值

NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];  id someValue = [userDefaults valueForKey:@"RPSomeUserPreference"];

NSUserDefaults实例中也有其他简单的方法例如boolForKey: stringForKey:等

 

 

常见模式

单例模式

单例模式是一种特殊的类在当前进程中只能一个类包含一个实例。单例模式对于在一个应用程序的不同部分中共享数据很便利并且不需要创建全局变量或者手动传递数据。但是因为它们经常创建类之间的紧耦合所以尽量少使用单例模式。

 

将一个类转换成单例模式你需要将下面的方法写到实现文件.m中方法名由shared加上一个单词组成这样可以很好的描述你的类。例如如果这个类是一个网络或位置管理器你可以将这个方法命名为sharedManager而不是sharedInstance。

+ (instancetype)sharedInstance {    static id sharedInstance = nil;    static dispatch_once_t onceToken;     dispatch_once(&onceToken, ^{       sharedInstance = [[self alloc] init];    });    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保留字

你可能感兴趣的:(Objective-C)