objective-c 基础学习 对C的扩展 Objective-C 程序 1 2 3 4 5 6 #import <Foundation/Foundation.h> int main (int argc, char *argv[]) { NSLog (@"Hello, Objective-C!" ); return 0 ; }
main.m文件,Xcode通过.m来表明文件是Objective-C类型,从而交给Objective-C编译器执行。.m代表的是message,这是Objective-C的一个重要特性。
解构 Objective-C 程序 #import语句保证头文件只会被引用一次。
NSLog() 和 @“字符串”字符串打印函数,与C语言中的printf类似,但是在此基础之上添加了一些新的特性:
NS前缀只是为了避免名称冲突。@表示引号内的字符串会被作为Cocoa的NSString元素来处理。
布尔类型 不是仅可以存储YES或NO值的真正布尔类型 是对signed char的类型重定义 一个字节,使用#define将YES定义为1,NO定义为0 面向对象编程 小知识 id是一种泛型,可以引用任意类型的对象[对象 方法]表示调用该对象的方法相关术语 类:表示对象类型的结构体 对象:包含值和指向其类的隐藏指针的结构体 实例:对象 消息:对象可以执行的动作 方法:响应消息而运行的代码 方法调度:推测执行什么方法以响应某个特定的消息 类的声明 @interface1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @interface Cicle : NSObject { ShapeColor fillColor; ShapeRect bounds; } - (void ) setFillColor: (ShapeColor) fillColor; - (void ) setBounds: (ShapeRect) bounds; - (void ) draw; @end
@implementation类方法的真正实现
1 2 3 4 5 6 @implementation Cicle - (void ) setFillColor: (ShapeColor) c { fillColor = c; } ......
@implementation 是编译器指令,用于说明为某个类提供执行的代码可以定义没有在 @interface 中声明的方法,相当于该类的私有方法。不过Objective-C中并不存在真正的私有方法。 @interface 与 @implementation 中的方法参数可以不同,但是,@implementation 中的方法参数不应与实例变量名相同,防止出现覆盖的情况继承 语法格式 不支持多继承 需要重写的方法仍然需要在 @implementation 中定义,什么都不写或者返回一个虚值(dummy) 方法调度 对象拥有一个指向类的指针,类拥有一个指向其执行代码的指针和一个指向其父类的指针。当对象去调用一个方法时,首先会通过它的指向类的指针去它父类的代码里面去寻找是否包含该方法,如果不包含,则转去它的父类的父类里面去寻找,以此类推,直到找到对应的方法。如果在顶层父类中仍旧没有找到该方法,则会报错。
实例变量 每个方法调用都会传入一个名 self 的隐藏参数用来指向对象本身 每一个对象都有一个isa指针指向其类对象,每一个类对象也有一个 isa 指针指向其父类对象。顶层父类 NSObject只有一个实例变量— isa 指针,用来指向本身 对象在访问其实例变量的时候,会使用 self 指针从继承链实例变量顶层一次往下寻找,即:从 NSObject 的isa开始往下找 super 关键字调用父类的方法
复合 通过将实例对象的指针作为当前对象的实例变量来实现
准确来说,是一种对象间的组合
栗子 1 2 3 4 5 6 @interface Car : NSObject { Tire *tires[4 ]; Engine *engine; } @end
此时,Car对象会给实例对象指针分配内存空间,但是并不会给响应的对象分配内存空间
使用 new 创建对象的过程 分配内存空间 调用类的init方法进行初始化 init 方法的简要讲解代码 1 2 3 4 5 6 7 @implementation Car - (id ) init { if (self = [super init]) { .... } } @end
说明 [super init] 让父类的初始化工作完成self = [super init] 将返回的已经初始化的对象指针赋予给当前的 self,防止出现父类初始化返回的对象与一开始创建的对象不一致的问题存取方法 用来存取或者改变对象属性的方法
setter 方法的命名格式是在其所要修改的变量名称前面加 setgetter 方法的命名格式是直接将所要读取的变量作为方法名所有对象间的交互都是通过指针来实现的
复合还是继承 继承: is a 复合: has a 源文件组织 拆分接口与实现 .h 头文件:存放类的 @interface 指令、公共 struct 定义、enum 常量、#define 和 extern 全局变量等.m 文件:存放所有的实现内容.mm 文件可以同时使用 C++ 和 Objective-C 编程
.m 文件中使用 @import 导入创建的 .h 文件,若是系统文件,使用尖括号 ,若是项目中自定义的文件,使用双引号 @class 关键字前向引用。告诉编译器这是一个类,以后会知道这个类具体的内容。
可以避免 .h 文件中的循环引用问题
注意 如果一个类继承另外一个类,那么,父类就不可以使用 @class 来进行前向引用。因为,编译器要编译子类的时候,需要预先知道其父类的详细信息,从而在其父类的基础之上对子类进行编译。而且,@class 实际上是通过告诉编译器这是一个使用指针来指向对象的变量,所以只需要申请对应的内存空间即可,但是子类继承父类则不是这么回事。故而,我们不可以使用上述方法来引入父类。
Xcode 按键 描述 Control + I 选中代码,点击自动缩进 Command + [ 将选中的代码左移 Command + ] 将选中的代码右移 Command + shift + O open quickly 选中 + option + 点击 打开开发者文档 Tab键 接受代码自动完成提示 Control + . 循环浏览代码提示 shift + Control + . 反向循环浏览代码提示 Command + Comtrol + S 创建快照 Control + F 前移光标 Control + B 后移光标 Control + P 移动光标到上一行 Control + N 移动光标到下一行 Control + A 移动光标到本行行首 Control + E 移动光标带本行行位 Control + T 交换光标左右两边的字符 Control + D 删除光标右边的字符 Control + K 删除本行 Control + L 将光标置于窗口正中央 Command + Control + 向上方向键 打开配套的文件 Command + Y 激活/禁用断点 Command + Control + Y 继续运行(在调试器中有效) F6 跳过 F7 跳入 F8 跳出 command+option + ⬅️ 局部折叠 command+option + shift + ⬅️ 全部折叠
Foundation Kit struct 结构体 NSRange 范围 1 2 3 4 5 typedef struct _NSRange{ unsigned int location; unsigned int length; } NSRange ;
表示事物的范围。location 表示范围的起始位置,length 表示范围的长度。注意:location 还可以使用 NSNotFound 来表示没有范围
通常用于表示字符串或者数组的范围
创建 NSRange 方式 1 2 3 4 5 6 7 8 9 10 11 NSRange range;range.location = 10 ; range.length = 10 ; NSRange range = {10 , 10 };NSRange range = NSMakeRange (10 , 10 );
几何数据类型 由 Core Graphics 框架(由C语言所写)提供,用于进行2D渲染。带有前缀 CG
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 struct CGPoint { float x; float y; }; struct CGSize { float width; float height; }; struct CGRect { CGPoint origin; CGSize size; };
使用结构体的原因 在 Objective-C 中,对象都是动态分配的,而动态分配会消耗大量的时间。而在程序中,尤其是GUI程序中,存在着大量的临时的坐标、大小和矩形区域的创建和使用。如果将结构体换成对象的话,会大大增加系统的开销。
字符串 NSString 创建字符串 1 2 3 4 5 6 7 8 NSString *height;height = [NSString stringWithFormat: @"Your height is %d feet, %d inches" , 5 , 11 ];
stringWidthFormat 方法中的参数列表中的最后一个参数是 ... ,这说明这个方法可以接受多个以逗号隔开的其他参数
类方法 Objective-C 在生成一个类的时候,会创建一个代表该类的类对象(class object)。其包含了指向父类、类名和类方法列表的指针,还包含一个 long 类型的数据,用来给新创建的对象指定大小在声明方法的时候添加了加号,那么,就声明了一个类方法,该方法属于类对象。这个方法通常会用来创建新的类的实例对象,这种方法被称为工厂方法。 关于大小 length 返回字符串的字符个数
- (NSUInteger) length;
1 NSUInteger length = [height length];
相比于C语言中的 strlen 方法,该方法可以精确地计算各种语言的字符串的字符个数。因为有些字符的字节数可能会大于一个字节,而 strlen 函数只是单纯地返回字节数而导致计算。
字符串比较 1 - (BOOL ) isEqualToString: (NSString *) aString;
返回一个BOOL值来表示两个字符串是否相同。如果要比较两个字符串是否是同一个,需要使用 == 来直接比较对象指针的地址
1 - (NSComparisonResult ) compare: (NSString *) aString;
将当前的对象与传递的字符串中的字符进行逐个比较,返回一个 NSComparisonResult (一个 enum 型枚举)来表示最终的比较结果。 区分大小写
1 2 3 4 5 6 7 8 enum { NSOrderedAscending = -1 , NSOrderedSame , NSOrderedDescending }; typedef NSInteger NSComparisonResult ;
栗子 1 2 3 4 5 6 7 NSString *thing1 = @"hello 5" ;NSString *thing2 = [NSString stringWithFormat: @"hello %d" , 5 ];if ([thing1 isEqualToString: thing2]) { NSLog (@"They are same!" ); }
不区分大小写的比较 1 - (NSComparisonResult ) compare: (NSString *) aString options: (NSStringCompareOptions ) mask;
options: 参数是一个掩位码,可以使用位或 | 来添加选项标记。可用的标记如下:
NSCaseInsensitiveSearch: 不区分大小写NSLiteralSearch: 区分大小写NSNumericSearch: 比较字符串的个数而不是字符串的值栗子 1 2 3 4 if ([thing1 compare:options: NSCaseInsesitiveSearch | NSNumericSearch ] == NSOrderedSame ) { NSLog (@"They match!" ); }
字符串内是否还包含别的字符串 判断字符串是否以另一个字符串开头1 - (BOOl) hasPrefix: (NSString *) aString;
判断字符串是否以另一个字符串结尾1 - (BOOl) hasSuffix: (NSString *) aString;
判断字符串内部是否包含另一个字符串1 - (NSRange ) rangeOfString: (NSString *) aString;
返回一个 NSRange 结构体,说明字符串匹配的部分在哪里以及能够匹配上的字符个数。如果没有匹配,那么结构体里面的 location 值为 NSNotFound 可变性 NSString 是不可变的。不能以删除字符或者添加字符的方式来改变字符串。
注意 :NSString 的子类 NSMutableString 可变
创建 NSMutableString 1 2 3 4 - (id ) stringWithCapacity: (NSUInteger ) capacity; NSMutableString *string = [NSMutableString stringWithCapacity: 47 ];
这里的容量只是一个建议值, 可以超过这个大小。
添加字符串 1 - (void ) appendString: (NSString *) aString;
将要添加的字符串添加到当前字符串的结尾
1 - (void ) appendFormat: (NSString *) aString, ...;
并不会创建新的字符串,而是将格式化后的字符串添加到当前字符串的结尾
1 2 3 NSMutableString *string = [NSMutableString stringWithCapacity: 47 ];[string appendString: @"Hello there " ]; [string appendFormat: @"human %d" , 39 ];
删除字符串 1 - (void ) deleteCharactersInRange: (NSRange *) aRange;
集合大家族 NSArray是一个 Cocoa 类,用来存储任意类型对象 的有序列表
只能存储 Objective-C 对象 不能存储 nil 不可变数组 创建 1 2 3 4 NSArray *array = [NSArray arrayWithObjects:@"one" , @"two" , @"three" , nil ];NSArray *array2 = @[@"one" , @"two" , @"three" ];
发送一个以逗号分隔的对象列表 列表末尾添加 nil 代表结束,这也是数组无法存储 nil 的原因 内置方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 - (void ) count; - (id ) objectAtIndex: (NSUInteger ) index; id *myObject = array[1 ];for (NSInteger i = 0 ; i < [array count]; ++i) { NSLog (@"index %d has %@." , i, [array objectAtIndex:i]); NSLog (@"index %d has %@." , i, array[i]); }
异常(exception) 是 Cocoa 说明“我不知道该如何处理”的方式
1 2 3 4 5 6 NSString *string = @"oop:ack:bork:greeble:ponies" ;NSArray *chunks = [string componentsSeparatedByString:@":" ]; string = [chunks componentsJoinedByString:@"-" ];
可变数组:NSMutableArray 创建 1 2 3 4 5 6 + (id ) arrayWithCapacity: (NSUInteger ) numItems; NSMutableArray *array = [NSMutableArray arrayWithCapacity:17 ];
容量只是最终容量的一个参考 没有用来创建 NSMutableArray 的字面量语法 添加元素或删除元素的时候会动态地调整容量大小 添加对象 1 2 3 4 5 6 - (void ) addObject: (id ) anObject; for (NSIngeter i = 0 ; i < 4 ; ++i) { [array addObject: @"123" ]; }
在数组末尾添加元素 特定位置删除元素 1 2 3 4 - (void ) removeObejectAtIndex: (NSUIngeter ) index; [array removeObjectAtIndex: 0 ];
删除元素之后,数组后面的所有元素自动向前移动 枚举 NSEnumerator,Cocoa 用它来表示集合中迭代出的对象。需要提前获取数组的枚举器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 - (NSEnumerator *)objectEnumerator; NSEnumerator *enumerator = [array objectEnumerator];- (NSEnumerator *) reverseObjectEnumerator; - (id ) nextObject; NSEnumerator *enumerator = [array objectEnumerator];while (id = thingie = [enumerator nextObject]) { NSLog (@"I found %@" , thingie); }
注意 :不能在使用迭代器的过程中添加或者删除数组中的元素,否则会导致迭代器混乱,获得未定义的结果
快速枚举 类似于foreach
1 2 3 4 for (NSString *string in array) { NSLog (@"I found %@" , string); }
无法在旧的机器上运行 NSDictionary不可变
创建 1 2 3 4 5 6 @{key:value,...} + (id ) dictionaryWithObjectsAndKeys: (id ) firstObject,...;
dictionaryWithObjectsAndKeys 后面的参数先是value,然后才是keydictionaryWithObjectsAndKeys 以 nil 值作为终止符号,所以,NSDictionary 不能存储 nil 值字面量语法不需要以 nil 结尾 1 2 3 4 5 6 7 8 9 Tire *t1 = [Tire new]; Tire *t2 = [Tire new]; Tire *t3 = [Tire new]; Tire *t4 = [Tire new]; NSDictionary *tires = [NSDictionary dictionaryWithObjectsAndKeys: t1, @"font-left" , t2, @"font-right" , t3, @"back-left" , t4, @"back-right" , nil ]; NSDictionary *tires = @{@"font-left" :t1, @"font-right" :t2, @"back-right" :t3, @"back-right" :t4};
访问 1 2 3 4 5 6 - (id ) objectForKey: (id ) aKey; tires[key];
可变字典 NSMutableDictionary 1 2 3 4 5 6 7 8 9 10 11 12 + (id ) dictionaryWithCapacity: (NSUInteger ) numItems; [NSMutableDictionary dictionary]; - (void ) setObject: (id ) anObject forKey: (id ) aKey; - (void ) removeObjectForKey: (id ) aKey;
不要乱来 不要尝试创建 NSString 、 NSArray、或 NSDictionary 的子类。因为在 Cocoa 中,许多的类都是以类簇的方式来实现的,他们是一群隐藏在通用接口之下的与实际实现相关的类。我们在创建 NSString 对象的时候,实际上可能得到是 NSLiteralString 或者其他类型的类。
其他数值 NSNumber1 2 3 4 5 6 + (NSNumber *) numberWithChar: (char ) value; + (NSNumber *) numberWithInt: (int ) value; + (NSNumber *) numberWithFloat: (float ) value; + (NSNumber *) numberWithBool: (BOOL ) value;
1 2 3 4 5 6 7 8 9 10 NSNumber *number;number = @'X' ; number = @12345 ; number = @12345 ul; number = @12345 ll; number = @123.45 f; number = @123.45 ; number = @YES;
1 2 3 4 5 6 7 - (char ) charValue; - (int ) intValue; - (float ) floatValue; - (BOOL ) boolValue; - (NSString *) stringValue;
Objective-C 不支持自动装箱功能。
NSValue可以封装任意值
1 2 + (NSValue *) valueWithBytes: (const void *) value objcType: (const char *) type;
传递的参数是要封装的数值的地址 objcType 指的是一个描述数据类型的字符串,通常使用 @encode(数据类型) 来获取该字符串1 2 3 4 5 6 7 8 9 10 - (void ) getValue: (void *)buffer; NSRect rect = NSMakeRect (1 , 2 , 30 , 40 );NSValue *value = [NSValue valueWithBytes:&rect objcType:@encode (NSRect )];[array addObject:value]; value = [array objectAtIndex:0 ]; [value getValue:&rect];
getValue 中传入的参数是要存储数据所要存储的空间的地址1 2 3 4 5 6 7 8 9 10 11 12 13 + (NSValue *)valueWithPoint: (NSPoint ) aPoint; + (NSValue *)valueWithSize: (NSSize ) size; + (NSValue *)valueWithRect: (NSRect ) rect; - (NSPoint ) pointValue; - (NSSize ) sizeValue; - (NSRect ) rectValue; value = [NSValue valueWithRect: rect]; [array addObject: value]; ... NSRect anotherRect = [value rectValue];
NSNull 代表什么都没有,用于消除 nil 带来的歧义,并可以作为对象存储到容器中
1 2 3 4 5 6 7 8 + (NSNull *) null; [contact setObject: [NSNull null] forKey: @"home fax machine" ]; id homefax = [contact objectForKey: @"home fax machine" ];if (homefax == [NSNull null]) { }
内存管理 对象生命周期 诞生(alloc、new) --》 生存(接收消息并执行操作) --》 交友(复合、想方法传递参数) --》 死去
引用计数 每一个对象都会有一个引用计数器 与之关联,当其被访问时,引用计数器会加1,访问结束后,引用计数器会减1
当使用 alloc new 或者 copy 创建一个对象时,对象的引用计数器会被设置成1
给对象发送 retain 消息,引用计数器会加1;给对象发送 release 消息,引用计数器会减一
对象的引用计数器减为0时,编译器会自动调用对象的 dealloc 方法来释放资源。我们也可以重写 dealloc 方法
1 2 3 - (id ) retain ; - (oneway void ) release; - (NEUInteger) retainCount;
retain 会返回一个 id 类型的值,从而可以实现对象在引用计数器加1的同时还可以完成其他的动作自动释放池和销毁时间 创建 @autoreleasepool 关键字
NSAutoreleasePool 对象
1 2 3 4 NSAutoreleasePool *pool;pool = [NSAutoreleasePool new]; ... [pool release];
优先使用关键字语法
自动释放池的分配和销毁的代价很小 自动释放池以栈的形式实现 垃圾回收 自动内存管理机制
启用垃圾回收机制后,平常的内存管理指令就不起作用了
对象初始化 分配对象 向某个类发送 alloc 消息,可以为该类分配一块足够大的内存空间,用来存放该类的全部实例变量。 同时,alloc 方法还会将这块内存空间全部初始化为0
初始化对象 1 2 3 4 Car *car = [[Car alloc] init]; Car *car = [Car alloc]; [Car init];
如果不使用嵌套,那么会导致初始化方法返回的对象和分配的对象不一致
编写初始化方法 1 2 3 4 5 6 7 8 9 10 (id ) init { if (self = [super init]) { engine = [Engine new]; tires[0 ] = [Tire new]; tires[1 ] = [Tire new]; tires[2 ] = [Tire new]; tires[3 ] = [Tire new]; } return (self ); }
if (self = [super init]) 的解释:先运行 [super init] 以完成父类自身的初始化操作。这个赋值操作只影响 init 方法中的 self 值,不会影响该方法之外的任何内容
如果在初始化对象时出现问题,那么 init 方法可能会返回 nil,表示没有成功初始化对象 惰性求值 :即使目前没有设置自定义属性的值,也应该等到调用者需要时再创建对象
构造初始化方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @interface Car : NSObject { ... } - (id ) initWithPresure: (float ) p treadDepth: (float ) td ... @end @implementation Car - (id ) initWithPresure: (float ) p treadDepth: (float ) td { if (self = [super init]) { presure = p; treadDepth = td; } return self ; } ... @end Car *car = [[Car alloc] initWithPresue:20 treadDepth:33 ];
属性 语法 可以通过调用 -set属性名 来设置属性值 可以通过调用 -属性名 来获取属性值 @property 的作用是让编译器自动添加 setter 和 getter 方法创建了该属性的访问代码 Xcode4.5 以后的版本不需要再使用该语法了 变量的声明位置的区别 实例变量如果在 .h 文件中声明,那么,其子类也可以直接通过属性来访问实例变量 实例变量如果在 .m 文件中声明,那么,其子类不可以直接通过属性来访问实例变量 如果我们并没有声明实例变量,那么编译器会自动帮我们创建实例变量 点表达式的妙用 属性扩展 1 2 @property (copy ) 属性名; @property (assign ) 属性名;
如果没有手动指定属性的特性,那么会默认使用 assign 和 nonatomic nonatomic 属性如果不在多线程中使用,可以提高访问方法的调用速度自己定义了 setter 或 getter 方法,就不可以使用 atomic 特性 名称的使用 属性的名称和实例变量的名称可以不相同
语法 1 2 3 4 5 6 7 8 9 10 11 12 13 @synthesize 属性名 = 实例变量名@interface Car : NSObject { NSString *appellation; ... } @property (copy ) NSString *name;@synthesize name = appellation;
编译器仍旧会创建 -setName 和 -name 方法,只不过里面操作的实例变量变为了 appellation
只读属性 1 @property (readonly ) 属性类型 属性名;
@dynamic告诉编译器不要生成任何代码或者创建相应的实例变量
类别 一种为现有的类添加新方法的方式
类别代码通常单独放在一个文件中,并且按照“类名+类别名”命名文件 声明 1 2 3 4 5 6 7 8 9 10 @interface NSString (NumberConvenience )- (NSNumber *) lenghtAsNumber; @end @implementation NSString (NumberConvenience )- (NSString *) lengthAsNumber { NSUInteger length = [self length]; return ([NSNumber numberWithUnsignedInt:length]); } @end
类名后面的括号里面的新名称就是类别的名称 只要保证类别名称唯一,你可以向一个类添加任意数量的类别 可以添加方法但是不能添加实例变量 可以添加属性,但是属性必须是 @dynamic 类型的。好处是你可以通过点来访问 setter 和 getter 方法 **注意:**任何 NSString 对象都可以响应 lengthAsNumber 消息,包括字面量字符串、description 方法返回的字符串、可变字符串、其他工具集部分字符串、文件中加载的字符串、从因特网海量内容中提取的字符串等
类别的局限性 无法添加新的实例变量 名称冲突,类别的优先级高于类中方法的优先级。当类别的方法和类中的方法重名的时候,类别的方法会取代类中的方法,导致类中的方法不再可用。 类别的优势 将类的实现代码分散到不同的文件中 创建对私有方法的前向引用 向对象添加非正式协议 类扩展 特殊的类别 无需类别名 可以在自己的类中使用 可以添加实例变量 可以将只读的权限改为可读可写的权限 创建的数量不限 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @interface Things : NSObject @property (assign ) NSInteger things;@property (readonly , assign ) NSInteger things;... - (void ) resetAllValues; @end @interface Things (){ NSInteger thing4; } @property (readwrite , assign ) NSInteger thing2;@property (assign ) NSInteger thing3;@end
所添加的都是私有的属性和方法 类别可以访问继承的类的实例变量 Cocoa 并没有真正的私有方法,我们可以直接使用对象支持的却没有在 @interface 中声明的方法,不过会导致编译器警告
通过在类别中声明和实现私有方法来解决
非正式协议和委托类别 委托(delegate)是一种对象,由另一个类请求执行某些工作。
被发送给委托对象的方法可以声明为一个 NSObject 的类别 也就是说,只要实现了委托方法,任何类的对象都可以成为委托对象 创建一个 NSObjct 的类别被称为“创建一个非正式协议” 响应选择器 选择器只是一个方法名称,但是它会以 Objc 运行时使用的特殊方式编码,以快速查询。可以使用 @selector() 圆括号中的方法名称来指定选择器
1 2 3 4 5 6 7 8 @selector (setTire:atIndex)if ([car respondsToSelector:@selector (setEngine)]) { NSLog (@"sdfk" ); }
协议 正式协议 包含了方法和属性的有名称列表
使用协议的方式是在类的 @interface 声明中列出协议的名称。采用协议就意味着你必须实现该协议中的所有 方法。
声明协议 1 2 3 @protocol NSCopying - (id ) copyWithZone: (NSZone *) zone; @end
协议名称必须唯一 可以继承父协议,后面使用尖括号包裹父协议 1 2 3 @protocol MySuperDuberProtocol <MyPerentProtocol >@end
协议中不会引入新的实例变量 采用了协议的类必须实现协议中的所有的方法,包括协议继承父协议的方法 采用协议 在类的声明中使用尖括号包裹协议
1 2 3 4 5 6 @interface Car : NSObject <NSCopying , NSCoding >{ } ... @end
一个类只能继承一个直接父类 一个类可以继承多个协议 复制 copy 消息会告诉对象创建一个全新的与接收 copy 消息的对象完全一样的对象
1 2 3 4 5 6 7 8 9 10 11 12 @interface Engine : NSObject <NSCopying >@end @implementation Engine - (id ) copyWithZone: (NSZone *) Zone { Engine *engineCopy; engineCopy = [[[self class ] allocWithZone: zone] init]; return (engineCopy); } @end
[self class] 获取 self 所属的类,这里的 self 指的是正在接收 copy 消息的对象allocWithZone 是一个类方法,根据类来创建对象的内存空间Engine 的子类对象也可以被复制协议和数据类型 可以在使用的数据类型中为实例变量和方法参数指定协议名称。
1 - (void ) setobjectValue: (id <NSCopying >) object;
传入的参数类型必须遵循 NSCopying 协议 objc 2.0 的新特性 1 2 3 4 5 6 7 8 9 10 @protocol BaseballPlayer - (void ) drawHugeSallary; @optional - (void ) slideHome; - (void ) catchBall; - (void ) throwBall; @required - (void ) swingBat; @end
委托方法 委托就是一个对象指定另外一个对象处理某些特定任务的设计模式
代码块和并发性 代码块(闭包) 对c语言函数的扩展
由c语言实现
两种绑定类型 自动绑定 托管绑定 代码块和函数指针 代码块的特征 返回类型可以手动声明也可以由编译器推导 具有指定类型的参数列表 拥有名称 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <returntype> (^blockname) (list of arguments) = ^(arguments) {body;}; void (^my_block)(void ); int (^square_block) (int number) = ^(int number) { return number * number; }; int result = square_block(5 );... void (^theBlock)() = ^{...};
代码块声明成了变量,可以像函数一样使用 代码块可以替换原先的函数:代码块可以访问与它相同的有效范围内声明的变量,也就是说代码块可以访问与它同时创建的变量 1 2 3 4 5 6 int value = 6 ;int (^multiply_block)(int number) = ^(int number) { return value * number; }; int result = multiply_block(7 );...
作为参数直接使用代码块 1 2 3 4 5 NSArray *array = [NSArray arrayWithObjects: @"Amir" , @"Mishal" , @"Irrum" , nil ];NSLog (@"Unsorted Array %@" , array);NSArray *sortedArray = [array sortedArrayUsingComparator:^(NSString *object1, NSString *object2){ return [object1 compare: object2]; }];
使用 typedef 关键字 1 2 3 4 5 6 typedef double (^MKSampleMutiply2BlockRef ) (double c, double d);MKSampleMutiply2BlockRef mutiply2 = ^(double c, double d) { return c * d; };
代码块和变量 代码块被声明后会捕捉创建点时的状态。代码块可以访问函数用到的标准类型的变量:
全局变量 全局函数 封闭范围内的参数 函数级别的 __block 变量。用于修饰在代码块中可被修改的变量 不可被声明为 __block 的类型 封闭范围内的非静态变量会被捕获为常量 objc的实例变量 代码块内部的本地变量 并发性 能够在同一时间执行多项任务的程序称为并发程序
1 2 3 4 @synchronized (theObject){ }
可以确保不同的线程会连续地访问临界区的代码 如果属性没有指定 nonatomic 特性,那么生成的 getter 和 setter 是彼此互斥的 选择性能 让一些代码在后台执行
创建在线程执行的方法 1 2 - (void ) myMethod; - (void ) myMethod:(id )myObject;
不能有返回值 要么没有参数,要么只有一个参数 需要在方法中创建自动释放池 1 2 3 4 5 6 7 8 - (void ) myBackgroundMethod { @autoreleasepool { NSLog (@"My Background Method" ); } } [self performSelectorInBackground:@selector (myBackgroundMethod) withObject:nil ];
当方法执行结束之后,objc 运行时会特定地清理并弃掉线程 调度队列 连续队列:根据指派的顺序执行任务 并发队列:并发执行一个或者多个任务,也是按照指派的顺序来执行任务 主队列:应用程序中有效的主队列,执行的是应用程序的主线程任务 连续队列 1 2 dispatch_queue_t my_serial_queue;my_serial_queue = dispatch_queue_create("com.apress.MySerialQueue1" , NULL );
第一个参数是队列名称,第二个参数是负责提供给队列的特性 并发队列 运行可以并行执行的任务 一次所运行的任务数是不确定的 三种并发队列 1 2 dispatch_queue_t myQueue;myQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 );
主队列 1 dispatch_queue_t main_queue = dispatch_get_current_queue(void );
通常要以同步方式 使用该队列,防止出现阻塞主应用程序运行 队列也要内存管理 调度队列也是引用计数对象,可以使用 dispatch_remain 和 dispatch_release 来修改引用计数器的值 只能对自己创建的队列使用上述方法,不能用自全局调度队列上 队列的上下文 可以给调度对象指派全局数据上下文 可以在数据上下文中指派任意类型的数据 上下文的内存管理需要自己做 1 2 3 4 NSMutableDictionary *myContext = [[NSMutableDictionary alloc] initWithCapacity:5 ];[myContent setObject:@"My Context" forKey:@"title" ]; [myContent setObject:[NSNumber numberWithInt:0 ] forKey:@"value" ]; dispatch_set_context(_serial_queue, (__bridge_retained void *)myContext);
使用字典来存储上下文 需要保证对象是有效的,所以使用 __bridge_retained 来给 myContext 的引用计数器加1 清理函数 对象在弃用之前调用的函数
1 2 3 4 5 6 7 8 9 void function_name (void *context);void myFinalizerFunction(void *contect) { NSLog (@"myFinalizerFunction" ); NSMutableDictionary *theData = (__bridge_transfer NSMutableDictionary *)contex; [theData removeAllObjects]; }
__bridge_transfer 把对象的内存管理由全局释放池变成函数获取上下文 1 NSMutableDictionary *myContext = (__bridge NSMutableDictionary *) dispatch_get_context(dispatch_get_current_queue());
添加 __bridge 是为了告诉 ARC 我们自己不想管理内存,交由系统来进行管理 添加任务 同步:一直等待前面的任务结束 异步:不必等待任务,会立即返回。优先使用这种方式 调度程序 最简单的方法是通过代码块
代码块必须是 dispatch_block_t 类型 没有返回值 没有参数 1 typedef void (^dispatch_block_t) (void )
使用代码块对象 1 2 3 4 dispatch_block_t myBlcok = ^{ NSLog (@"My Predfriend block" ); }; dispatch_async (_serial_queue, myBlock);
使用函数 1 2 3 4 void myDispatchFunction(void *argument) { ... } dispatch_async_f(_serial_queue, (__bridge void *)[NSNumber numberWithInt:3 ], (dispatch_function_t)myDispatchFunction);
其他 1 2 dispatch_suspend(_serial_queue); dispatch_resume(_serial_queue);
操作队列 创建调用操作 NSInvocationOperation 会给执行任务的类调用选择器
1 2 3 4 5 6 7 8 @implementation MyCustomClass - (NSOperation *) operationWithData:(id ) data { return [[NSInvocationOperation alloc] initWithTarget:self selector:@selector (myWorkerMethod:) object:data]; } - (void ) myWorkerMethod: (id )data { NSLog (@"My Worker Method %@" , data); } @end
创建代码块操作 1 2 3 4 5 6 NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{ }]; [blockOperation addExecutionBlock:^{ }];
向队列中添加操作 NSOperationQueue 一般会并发执行。并且具有相关性,如果此操作是在别的操作的基础之上进行的,那么,这两个操作会相应地执行
可以通过设置最大并发数为1来确保操作是连续执行的 1 2 3 4 5 6 7 8 9 NSOperationQueue *currentQueue = [NSOperationQueue currentQueue]; NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; NSOperationQueue *_operationQueue = [[NSOperationQueue alloc] init]; [theQueue addOperation:blockOperation]; [theQueue addOperationWithBlock:^{ NSLog (@"My Block" ); }];
文件加载与保存 属性列表 用来放置一些 Cocoa 可以处理(主要是存储到文件和从文件中加载)的对象
属性列表类 NSArray NSDictionary NSString NSNumber NSDate NSData 上述类的可修改形态(Mutable) NSDate 用于处理时间和日期的基础类
1 2 NSDate *date = [NSdate date]; NSLog (@"today is %@" , date);
1 2 3 NSDate *yesterday = [NSDate dateWithTimeIntervalSinceNow: -(24 * 60 * 60 )];NSLog (@"yesterday is %@" , yesterday);
+dateWithTimeIntervalSinceNow 接收一个 NSTimeInterval 类型的参数参数是一个双精度值 以秒为单位 正数表示未来,负数表示过去 NSData 包含大量的字节 可以获取数据的长度 可以获取指向字节起始位置的指针 不可变更。如果需要变更,使用 NSMutableData 即可 1 2 3 4 const char *string = "Hi there, this is a C string!" ;NSData *data = [NSData dataWithBytes:string length:strlen(string) + 1 ];NSLog (@"data is %@" , data);NSLog (@"%d byte string is '%s'" , [data length], [data bytes]);
length 之所以加1是为了将c语言末尾的 ‘/0’ 包含进去-length 方法返回字节数量-bytes 返回字符串起始位置的指针写入和读取属性列表 集合型属性列表类( NSArray 和 NSDictionary )具有一个 -writeToFile:atomically 方法 NSString 和 NSData 也具有该方法,但是只能写出字符串或数据块 1 2 [[NSArray arrayWithObjects:@"I" , @"seem" , @"to" , @"be" , @"a" , @"verb" , nil ] writeToFile:@"./tmp/verbiage.txt" atomically:YES ]; NSLog (@"%@" , [NSArray arrayWithContentsOfFile:@"./tmp/verbiage.txt" ]);
atomically 表示是否将文件内容暂存到临时文件中。如果在保存过程中出现意外,那么不会破坏源文件。但是代价是会使用双倍的磁盘空间缺点是不会返回任何错误信息 修改对象类型 遍历集合并修改对象类型保存 使用 NSPropertyListSerialization 类 1 2 3 propertyListFromData:mutabilityOption:format:errorDescription:
编码对象 将对象转换成某种格式并保存到磁盘中的机制
对象可以将它们的实例变量和其他数据编码为数据块,然后保存到磁盘中 这些数据块以后还可以重新读回内存,并且基于保存的数据创建新对象 过程叫做编码和解码,或者序列化和反序列化 1 2 3 4 @protocol NSCoding - (void ) encodeWithCoder: (NSCoder *) encoder; - (id ) initWithCoder: (NSCoder *) decoder; @end
通过 encodeWithCoder 序列化自身 通过 initWithCoder 加载自身 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 - (void ) encoderWithCoder: (NSCoder *) coder { [coder encodeObject: name forKey: @"name" ]; [coder encodeInt: magicNumber forKey: @"magicNumber" ]; [coder encodeFloat: shoeSize forKey: @"shoeSize" ]; [coder encodeObject: subThingies forKey: @"subThingies" ]; } - (id ) initWithCoder: (NSCoder *) decoder { if (self = [super init]) { self .name = [decoder decodeObjectForKey: @"name" ]; self .magicNumber = [decoder decodeIntForKey: @"magicNumber" ]; self .shoeSize = [decoder decodeFloatForKey: @"shoeSize" ]; self .subThingies = [decoder decodeObjectForKey: @"subThingies" ]; } }
是否使用 super init 取决于父类是否采用了 NSCoding 协议。没有:super init 有:super initWithCoder 数组被编码时,里面的所有对象均会被编码 会智能地解决循环问题——数组中包含指向该数组的指针变量 键/值编码 一种间接更改对象状态的方式
KVC 简介 valueForKey: 方法给对象发送消息并传递属性名称参数,获取其属性 NSString *name = [car valueForKey:@"name"];先去寻找与参数名称相同的 getter 方法,没找到会接着寻找与参数名称相同的实例变量 通过元数据打开对象进入其中寻找所需要的信息,无需通过对象指针来访问实例变量 会自动装箱和拆箱 setValue:forKey: 方法设置对象实例变量的值 第一个参数必须是对象指针 其余与 valueForKey: 中的说明相同 键路径 键/值编码还支持指定路径,就像文件系统路径一样,可以遵循一系列关系来指定该路径
假设类 Engine 的实例变量 horsepower 没有设置为属性,也没有提供 getter 方法 和 setter 方法 1 2 [car setValue: [NSNumber numberWithInt:10 ] forKey: @"engine.horsepower" ]; NSLog (@"horsepower is %@" , [car valueforKey: @"engine.horsepower" ]);
整体操作 如果使用某个键值来访问一个 NSSrray 数组,它实际上会查询响应数组中的每一个对象,然后将查询结果打包到另一个数组中返回
1 2 3 4 NSArray *presures = [car valueForKey:@"tires.presure" ];NSLog (@"presures %@" , presures);
不能在键路径索引数组,比如:tires[0].presure 快速运算 可以引用一些运算符来进行一些运算
1 2 NSNumber *count;count = [garage valueForKeyPath: @"cars.@count" ];
cars 用来获取 garage 对象的属性@count 用来告诉 KVC 机制计算键路径上的左侧的对象总数@sum 计算总和@avg 计算平均值@min 计算最小值@max 计算最大值@distinctUnionOfObjects 获取左侧集合对应右侧属性的集合,这个集合中没有重复的元素不要滥用 KVC KVC 需要解析字符串,速度比较慢 无法进行错误检查,可能会出现键路径错误 批处理 dictionaryWithValuesForKeys:传入的字符串数组中的每一个字符串作为 key ,对象中每一个属性名为 key 的 属性名的值作为 value ,然后凭此生成一个字典
1 2 3 car = [[garage valueForKeyPath:@"cars" ] lastObject]; NSArray *keys = [NSArray arrayWithObjects: @"make" , @"model" , @"modelYear" , nil ];NSDictionary *carValues = [car dictionaryWithValuesForKeys: keys];
setValuesForKeysWithDictionary:获取字典值并根据 key 设置对象与 key 对应的的属性的值
1 2 3 4 5 6 7 8 NSDictionary *newValues = [ NSDictionary dictionaryWithObjectsAndKeys: @"Chevy" , @"make" , @"Nova" , @"model" , [NSNumber numberWithInt: 1964 ], @"modelYear" , mil ]; [car setValuesForKeysWithDictionary: newValues];
nil 仍然可用重写 setNilValueForKey: 方法以提供逻辑上有意义的任何值
1 2 3 4 5 6 7 - (void ) setNilValueForKey: (NSString *) key { if ([key isEqualToString: @"mileage" ]) { mileage = 0 ; } else { [super setNilValueForKey: key]; } }
处理未定义的键 通过重写 valueForUndefinedKey: 方法来处理未定义的键 如果要更改未知键的值,可以使用 setValue:forUndefinedKey: 方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @interface Garage : NSObject { NSString *name; NSMutableArray *cars; NSMutableDictionary *stuff; } - (void ) setValue: (id ) value forUndefinedKey: (NSString *) key { if (stuff == nil ) { stuff = [[NSMutableDictionary alloc] init]; } [stuff setValue:value forKey:key]; } - (id ) valueForUndefinedKey: (NSString *) key { id value = [stuff valueForKey: key]; return value; }
使用静态分析器 无需运行程序便可以从逻辑上检测代码的工具
可以分析出的错误 安全问题,内存泄漏、缓冲区溢出等 并发性问题 逻辑问题 缺点 需要耗费时间进行分析,会拖慢构建程序设计的过程 有时会误报错误 改变了熟悉的工作流程 NSPredicate用于指定过滤器的条件
创建谓词 创建许多对象,并将其组合起来 查询代码中的字符串 1 NSPredicate *predicate = [NSPredicate predicateWithFormat: @"name == 'Herbie'" ];
谓词字符串左侧的 name 是键路径 谓词字符串右侧的 'Herbie' 是字符串字面量 计算谓词 1 2 BOOL match = [predicate evaluateWithObject: car];NSLog (@"%s" , (match) ? "YES" : "NO" );
evaluateWithObjectt: 计算对象是否符合谓词数组过滤器 1 2 NSArray *array = [cars filteredArrayUsingPredicate: predicate];NSLog (@"%@" , array);
filteredArrayUsingPredicate: 是 NSArray 数组中的一种类别方法,会循环过滤数组内容,根据谓词计算每一个对象的值,并将值为 YES 的对象累计到将被返回的数组中NSMutableArray 可以使用 filteredUsingPredicate 方法格式说明符 方式一 1 2 predicate = [NSPredicate predicateWithFormat: @"engine.horsepower > %d" , 50 ]; predicate = [NSPredicate predicateWithFormat: @"%K == %@" , @"name" , @"Herbie" ];
方式二 1 2 3 NSPredicate *predicateTemplate = [NSPredicate predicateWithFormat: @"name == $NAME" ];NSDictionary *varDict = [NSDictionary dictionaryWithObjctsAndKeys: @"Herbie" , @"name" , nil ];NSPredicate *predicate = [predicateTemplate predicateWithSubstitutionVariables: varDict];
不能使用 $变量名 来作为键路径 谓词机制不会进行静态类型检查 运算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 > >= < <= != and or not && || ! between {} in {}self .属性名self beginswith endswith contains like ‘正则表达式’
曲终