Objective-C 03 Foundation Kit

编程入门 行业动态 更新时间:2024-10-24 01:59:35
Foundation Kit    Foundation框架只是Cocoa的一部分,没有内置于Objective-C的语言中。   Cocoa 实际上是由许多个不同的框架组成的,其中最常用于桌面端(OS X) 应用程序的是Foundation和Application Kit。它包含了所有的用户界面(UI)对象和高级类。如果打算开发iOS平台上的应用程序,那么将会用到User Interface Kit(UIKit)框架。UIKit可与OS X平台的AppKit框架相提并论,它包含了iOS应用程序所需要的所有界面对象。   Foundation就是两类UI框架的基础, 因为它不包含UI对象,所以它的对象可以在iOS或OS X应用程序中兼容。   Foundation 框架是以另一个框架CoreFoundation为基础创建的。CoreFoundation框架是用纯C语言写的。如果函数或变量的名称以CF开头,那 么它们就是CoreFoundation框架中的。其中很多都可以在Foundation框架中找到相对应的,它们之间的转换也非常简单。   一些有用的数据类型   范围: 第一个结构体(struct)是NSRange。   typedef struct _NSRange {      unsigned int location;      unsigned int length; } NSRange; 这个结构体用来表示相关事物的范围,通常是字符串里的字符范围或者数组里的元素范围。Location字段存放该范围的起始位置,而length字段则是该范围内所含元素的个数。 在字符串"Objective-C is a cool language"中,单词cool可以用location为17,length为4的范围来表示。location还可以用NSNotFound这个值来表示没有范围,比如变量没有初始化。   创建新的NSRange有三种方式。 第一种,直接给字段赋值: NSRange range; range.location = 17; range.length = 4;   第二种,应用C语言的聚合结构赋值机制 NSRange range = {17, 4};   第三种方式是Cocoa提供的一个快捷函数NSMakeRange(): NSRange range = NSMakeRange(17, 4);   使用NSMakeRange()好处是你可以在任何能够使用函数的地方使用它,例如在方法调用中将其作为参数进行传递。 [anObject flarbulateWithRange:NSMakeRange (13, 15)];   几何数据类型 用 来处理集合图形的数据类型,它们的名称都带有CG前缀,如CGPoint和CGSize.这些类型是由Core Graphics框架提供的,用来进行2D渲染。Core Graphics是用C语言所写的,因此可以在代码中使用C语言的数据类型。CGPoint表示的是笛卡尔平面中的一个坐标(x, y)。   struct CGPoint {      float x;      float y; };   CGSize用来存储长度和宽度: struct CGSize {      float width;      float height; }; Cocoa提供了一个矩形数据类型,它由坐标和大小复合而成。 struct CGRect {      CGPoint origin;      CGSize size; }; Cocoa也为我们提供了创建这些数据类型的快捷函数:CGPointMake()、CGSizeMake()和CGRectMake().   字符串 NSString就是Cocoa中用来处理字符串的类。字符串其实就是一组人类可读的字符序列。 之前@"Hil"这些字面量字符串与你在编程过程中创建的NSString并无差别。   创建字符串,诸如printf()和NSLog()之类的函数会接受格式字符串和一些参数来输出格式化的结果。NSString的stringWithFormat:方法就是这样通过格式字符串和参数来创建NSString的。   + (id) stringWithFormat: (NSString *) format, ...;   可以按如下方式创建一个新的字符串: NSString *height;   height = [NSString stringWithFormat:@"Your height is %d feet, %d inches", 5, 11]; 得到的字符串是"Your height is 5 feet, 11 inches";   类方法 stringWithFormat:的声明中有两个值得注意的地方。第一个是定义最后的省略号(...),它告诉我们和编译器这个方法可以接受多个以逗号隔开的其他参数,就像printf()和NSLog()一样。 另一个非常重要的地方是声明语句中有一个非常特别的起始字符:一个加号。Objective-C运行时生成一个类的时候,会创建一个代表该类的 类对象(class object)。类对象包含了指向超类、类名和类方法列表的指针, 还包含了一个long类型的数据,为新创建的实例对象指定大小(以字节为单位). 如果在声明方法时添加了加号,就是把这个方法定义为类方法(class method).这个方法属于类对象(而不是类的实例对象),通常用于创建新的实例。我们称这种用来创建新对象的类方法为工厂方法(factory method)。   stringWithFormat:就是一个工厂方法,它根据你提供的参数创建对象。用stringWithFormat:来创建字符串比创建空字符串然后生成所有元素要容易得多。   类方法也可以用来访问全局数据。AppKit(基于OS X平台)中的NSColor类和UIKit(基于iOS平台)中的UIColor类都拥有以各种颜色命名的类方法,比如redColor和blueColor。要用蓝色绘图,可以像下面这样编写代码。   NSColor *haveTheBlue = [NSColor blueColor]; 或 UIColor *blueMan= [UIColor blueColor];   你所创建的大部分方法都是实例方法,要用减号(-)作为前缀来进行声明。这些方法将会在指定的对象实例中起作用,比如获取一个Circle的颜色或者一个Tire的压强。 如果某个方法所实现的是很通用的功能,比如创建一个实例对象或者访问一些全局类数据,那么最好使用加号(+)作为前缀将它声明为类方法。 关于大小 NSString中另一个好用的方法(实例方法)是length,它返回的是字符串中的字符个数。   - (NSUInteger) length;   可以这样使用它: NSUInteger length = [height length];   也可以在表达式中使用它,如下: if ([height length] > 35) {      NSLog (@"wow, you're really tall!"); }   字符串比较 isEqualToString:可以用来比较接受方(receiver, 接受消息的对象)和作为参数传递过来的字符串。isEqualToString:返回一个BOOL值(YES或NO)来表示两个字符的内容是否相同。 声明如下:  - (BOOL) isEqualToString: (NSString *) aString;   方法的使用: NSString *thing1 = @"hello 5"; NSString *thing2 = [NSString stringWithFormat: @"hello %d", 5];   if ([thing1 isEqualToString: thing2]) {      NSLog(@"They are the same !"); }   要比较两个字符串,可以使用compare:方法,其声明如下:   -(NSComparisonResult) compare: (NSString *) aString;   compare:将接受对象和传递过来的字符串逐个进行比较,它返回一个NSComparisonResult(也就是一个enum型枚举)来显示比较结果。   enum {      NSOrderedAscending = -1;      NSOrderedSame,      NSOrderedDescending }; typedef NSInteger NSComparisonResult;     如 果compare:返回的结果是NSOrderedAscending,那么左侧的数值就小于右侧的数值,即比较的目标在字母表中的排序位置比传递进来的 字符串更靠前。NSOrderedDescending是左侧的数值大于右侧的数值,NSOrderedSame就是数值一样大。   compare: 进行的是区分大小写的比较。 另一个方法:compare:options: - (NSComparisonResult) compare: (NSString *) aString      options: (NSStringCompareOptions) mask;   options参数是一个掩位码。可以使用位或bitwise-OR运算符(|)来添加选项标记。 一些常用的选项: NSCaseInsensitiveSearch: 不区分大小写字符 NSLiteralSearch: 进行完全比较,区分大小写字符。 NSNumericSearch: 比较字符串的字符个数,而不是字符串值。如果没有这个选项,100会排在99的前面。 忽略大小写并按字符个数进行排序,那么应该这样做: if ([thing1 compare: thing2 options: NSCaseInsensitiveSearch | NSNumericSearch] == NSOrderedSame) {      NSLog(@"They match!!"); }   检查字符串是否以另一个字符串开头 -(BOOL) hasPrefix: (NSString *) aString; 判断字符串是否以另一个字符串结尾。 -(BOOL) hasSuffix: (NSString *) aString; 方法的使用: NSString *fileName = @"draft-chapter.pages";   if ([fileName hasPrefix: @"draft"]) {      // this is a draft }   if ([fileName hasSuffix: @".mov"]) {      // this is a movie }   如果想知道字符串内的某处是否包含其他字符串,使用rangeOfString:. -(NSRange) rangeOfString: (NSString *) aString; 将rangeOfString:发送给一个NSString对象时,传递的参数是要查找的字符串。它会返回一个NSRange结构体,告诉你与这个字符串相匹配的部分在哪里以及能够匹配上的字符个数。 示例: NSRange range = [fileName rangeOfString: @"chapter"]; 返回的range.location为6, range.length为7,如果传递的参数在接受字符串中没有找到,那么range.location则等于NSNotFound。   可变性 NSString是 不可变(immutable)的。这并不意味着你不能操作它们。不可变的意思是NSString一旦被创建,便不能改变。你可以对它执行各种各样的操作,但是不能以删除字符或添加字符的方式来改变它。   Cocoa提供了一个NSString的子类,叫做NSMutableString。想改变字符串,可以使用这个子类。   可以使用类方法stringWithCapacity:来创建一个新的NSMutableString, 声明如下: + (id) stringWithCapacity: (NSUInteger) capacity; 创建一个新的可变字符串: NSMutableString *string = [NSMutableString stringWithCapacity: 42]; 有了一个可变的字符串,就可以对它执行各种操作了。一种常见的操作是通过appendString:或appendFormat:来附加新字符串,如下: -(void) appendString: (NSString *) aString; -(void) appendFormat: (NSString *) format, ...;   appendString:接受参数aString,然后将其复制到接受对象的末尾。appendFormat:的工作方式与stringWithFormat:类似,但并没有创建新的字符串,而是将格式化的字符串附加在了接受字符串的末尾。如: NSMutableString *string = [NSMutableString stringWithCapacity: 50]; [string appendString: @"Hello there "]; [string appendFormat: @"human %d!", 39]; // string 为"Hello there human 39!"   -(void) deleteCharactersInRange: (NSRange) aRange;// 删除字符串中的字符。   deleteCharacterInRange:和rangeOfString:连在一起使用例子:   NSMutableString *friends = [NSMutableString stringWithCapacity: 50]; [friends appendString: @"James BethLynn Jack Evan"]; // 找到jack的名字在字符串中的范围   NSRange jackRange = [friends rangeOfString: @"Jack"]; jackRange.length++; // eat the space that follows 把空格去掉。 // 字符范围开始于15,长度为5.   //删除的语句: [friends deleteCharactersInRange: jackRange];   由于NSMutableString是NSString的子类,所以我们"无偿"获得了两个特性。第一个就是任何使用NSString的地方,都可以用NSMutableString来替代。任何NSString可行的场合NSMutableString也能畅通无阻。 另一个特性源于继承,与实例方法一样,继承对类方法也同样适用。所以NSString中非常方便的类方法stringWithFormat:也可以用来创建新的NSMutableString对象。 NSMutableString *string = [NSMutableString stringWithFormat: @"jo%dy", 2]; -- jo2y.   集合大家族   NSArray是一个Cocoa类,用来存储对象的有序列表。可以在NSArray中放入任意类型的对象:NSString、Car、Shape、Tire等。   NSArray类有两个限制。首先,它只能存储Objective-C的对象,而不能存储原始的C语言基础数据类型,如int、float、enum、struct和NSArray中的随机指针。同时,你也不能在NSArray中存储nil(对象的零值或NULL值)。   可以通过类方法arrayWithObjects:创建一个新的NSArray。发送一个以逗号分隔的对象列表,在列表结尾添加nil代表列表结束(这就是不能在数组中存储nil的一个原因)。   NSArray *array = [NSArray arrayWithObjects: @"one", @"two", @"three", nil]; 也可以使用数组字面量格式来创建一个数组: NSArray *array2 = @[@"one", @"two", @"three"]; 虽然array和array2对象不同,但它们的内容是一样的。 只要有了一个数组,就可以获取它所包含的对象个数: -(NSUInteger)count;   也可以获取特定索引处的对象: -(id) objectAtIndex: (NSUInteger)index;   通过字面量访问数组的语法与C语言中访问数组项的语法类似。 id *myObject = array1[1];   输出数组的内容: for (NSInteger i = 0; i < [array count]; i++) {      NSLog(@"index %d has %@.", i, [array objectAtIndex:i]); }   也可以使用数组字面量语法来写以上代码,如下:   for (NSInteger i = 0; i < [array count]; i++) {      NSLog(@"index %d has %@.", i, array[i]); }   如果你引用的索引大于数组中对象的个数,那么Cocoa在运行时会输出错误. 如: [array objectAtIndex:208000]; array[208000];   这个提示信息还提到终止的原因之一是:“未捕获异常”(uncaught excaption)。异常(exception)是Cocoa说明“我不知道该如何处理。”   可变数组: 与NSString一样, NSArray创建的是不可变对象的数组。一旦创建了一个包含特定数量的对象的数组,它就固定下来了:既不能添加任何元素也不能删除任何元素。当然数组中包含的对象是可以改变的,但数组本身是一直都不会改变的。   NSMutableArray是可变数组,创建新的可变数组: + (id) arrayWithCapacity: (NSUInteger) numItems;   NSMutableArray *array = [NSMutableArray arrayWithCapacity: 17];   使用addObject:在数组末尾添加对象: -(void) addObject: (id) anObject;   for (NSInteger i = 0; i < 4; i++) {      Tire *tire = [Tire new];      [array addObject: tire]; }   删除特定索引处的对象,可以用removeObjectAtIndex:来删除它。 -(void) removeObjectAtIndex: (NSUInteger) index; 这样使用: [array removeObjectAtIndex: 1]; 索引值是1的是数组的第二数,NSArray中的对象是从零开始编制索引的。与C数组的规范一样。 位于被删除对象后面的数组元素都被前移来填补空缺了。   枚举   NSEnumerator,Cocoa可以用它来表示集合中迭代出的对象。想要使用NSEnumerator,需通过objectEnumerator向数组请求枚举器: -(NSEnumerator *) objectEnumerator;   可以使用: NSEnumerator *enumerator = [array objectEnumerator];   如果想要从后往前浏览某个集合,有reverseObjectEnumerator方法可以使用。 在获得枚举器之后,便可以开始一个while循环,每次循环都向这个枚举器请求它的nextObject(下个对象): -(id) nextObject;   nextObject返回nil值时,循环结束。这也是不能在数组中存储nil值的另一个原因:没办法判断nil是存储在数组中的数值还是代表循环结束的标志。   整个循环代码: NSEnumerator *enumerator = [array objectEnumerator]; while(id thingie = [enumerator nextObject]) {      NSLog(@"I found %@", thingie); }   对可变数组进行枚举操作时,有一点要注意:不能通过添加或删除对象这类方式来改变数组的容量。   快速枚举(fast enumeration)它的语法: for(NSString *string in array) {      NSLog(@"I found %@", string); }   这个循环体将会遍历数组中的每一个元素,并且用变量string来存储每个数组值。 快速枚举跟Objective-C 2.0的其他新特性一样,不能在旧的Mac(Mac OS X 10.5之前的)系统上运行。   苹果编译器(基于Clang和LLVM项目)为纯C语言添加了一个叫做代码块(block)的特性。为了支持代码块功能,苹果公司添加了一个能在NSArray中通过代码块枚举对象的方法,代码如下:

-(void)enumerateObjectsUsingBlock: (void (ˆ)(id obj, NSUInteger idx, BOOL *stop)) block   可以用之前的那个数组来重写这个枚举方式: [array enumerateObjectsUsingBlock:ˆ(NSString *string, NSUInteger index, BOOL *stop) {      NSLog (@"I found %@", string); }]; 用代码块代替快递枚举,是因为通过代码块可以让循环操作并发执行,而通过快速枚举,执行操作要一项项地线性完成。   现在有4种方法来遍历数组:通过索引、使用NSEnumerator、使用快速枚举和最新的代码块方法。 最好使用快速枚举或代码块(如果系统支持),系统不支持就使用NSEnumerator。只有在真的需要用索引访问数组时才应使用 -objectAtInex方法。   NSDictionary:在编程中, 字典(dictionary)是关键字及其定义的集合。Cocoa中有一个实现这种功能的集合类NSDictionary。NSDictionary能在给定的关键字(通常是一个NSString字符串)下存储一个数值(可以是任何类型的Objective-C对象),然后你就可以用这个关键字来查找相应的数据。     NSDictionary是不可变的对象。NSMutableDictionary类允许你随意添加和删除字典元素。创建新的NSDictionary时,就要提供该字典所存储的全部对象和关键字。   字典的类方法:dictionaryWithObjectsAndKeys: 字典字面量语法即使用类似@{key:value,...}的方法来定义。注意它使用的是大括号而不是方括号。还要注意 dictionaryWithObjectsAndKeys:后面的参数先是要存储的对象,然后才是关键字,而字面量的语法则是关键字在前,数值在后。 关键字和数值之间以冒号分开,而每对键值之间则用逗号区分开。 +(id) dictionaryWithObjectsAndKeys: (id) firstObject, ...; 该方法接受对象和关键字交替出现的序列,以nil值作为终止符号。   创建字典: Tire *t1 = [Tire new]; Tire *t2 = [Tire new]; Tire *t3 = [Tire new]; Tire *t4 = [Tire new];   NSDictionary *tires = [NSDictionary dictionaryWithObjectsAndKeys: t1, @"front-left", t2, @"front-right", t3, @"back-left", t4, @"back-right", nil];   也可以: NSDictionary *tires = @{@"front-left" : t1, @"front-right", t2, @"back-left" : t3, @"back-right" : t4};   使用objectForKey:方法并传递前面用来存储的关键字,就可以访问字典中的数值了: -(id) objectForKey: (id) aKey; 或者是 tires[key];   找到右后方的轮胎:   Tire *tire = [tires objectForKey:@"back-right"];   或者是: Tire *tire = tires[@"back-right"]; 如果字典里没有右后方的轮胎,字典将会返回nil值。   向NSMutableDictionary类发送dictionary消息,便可以创建新的NSMutableDictionary对象,也可以使用dictionaryWithCapacity:方法来创建新的可变字典并告诉Cocoa该字典的最终大小。   +(id) dictionaryWithCapacity: (NSUInteger) numItems;   可以使用setObject:forKey:方法为字典添加元素。   -(void) setObject:(id)anObject forKey: (id)aKey;   存储轮胎的字典还有一种创建方法: NSMutableDictionary *tires = [NSMutableDictionary dictionary]; [tires setObject:t1 forKey:@"front-left"]; [tires setObject:t2 forKey:@"front-right"]; [tires setObject:t3 forKey:@"back-left"]; [tires setObject:t4 forKey:@"back-right"];   对字典中已有的关键字使用setObject:forKey:方法,会用新值替换掉原有的数值。如果想删除可变字典的一些关键字,可使用removeObjectForKey:方法: -(void) removeObjectForKey: (id)aKey; 如: [tires removeObjectForKey:@"back-left"]; 与NSArray一样,没有适用于NSMutableDictionary的字面量初始化语法。   在 Cocoa中,许多类实际上是以类簇(class clusters)的方式实现的,即它们是一群隐藏在通用接口之下的与实现相关的类。创建NSString对象时,实际上获得的可能是 NSLiteralString、NSCFString、NSSimpleCString、NSBallOfString或者其他未写入文档的与实现相关 的对象。   其他数值   NSArray和NSDictionary只能存储对象,没法直接存储任何基本类型的数据,如int、float、struct。不过可以用对象封装基本数值,比如将int型数据封装到一个对象中,就可以将这个对象放入NSArray或NSDictionary中了。   如果你想使用对象来处理基本类型,就可以使用NSInteger和NSUInteger。这些类型也要针对32位和64位处理器对数值进行统一。   NSNumber Cocoa提供了NSNumber类来封装(wrap, 即以对象形式来实现)基本数据类型。可以使用以下类方法来创建新的NSNumber对象:
+ (NSNumber *) numberWithChar: (char) value; + (NSNumber *) numberWithInt: (int) value; + (NSNumber *) numberWithFloat: (float) value; + (NSNumber *) numberWithBool: (BOOL) value; 以上是最常用的方法,其他的还有许多这样的创建的方法,包括无符号整数和long型整数以及long long整型数据的版本。 也可以使用字面量语法来创建这些对象:   NSNumber *number; number = @ 'X'; // 字符型 number = @12345; // 整型 number = @12345ul; // 无符号长整数 number = @12345ll; // long long number = @123.45f; // 浮点型 number = @123.45; // 双浮点型 number = @YES; // 布尔值   把NSNumber放入一个字典或数组中: NSNumber *number = @42; [array addObject number]; [dictionary setObject: number forKey: @"Bork"];   将一个基本类型数据封装到NSNumber中后,可以通过下面的实例方法来重新获得它: -(char) charValue; -(int) intValue; -(float) floatValue; -(BOOL) boolValue; -(NSString *) stringValue;   将创建方法和提取方法搭配在一起使用是完全可以的。例如:用numberWithFloat:创建的NSNumber对象可以用intValue方法来提取数值。NSNumber会对数据进行适当的转换。   NSValue NSNumber实际上是NSValue的子类,NSValue可以封装任意值。 类方法: +(NSValue *) valueWithBytes: (const void *) value objCType: (const char *) type;   传递的参数是你想要封装的数值的地址。通常得到的是想要存储的变量的地址(在C语言中用&操作符才可以得到)。 用@encode编译器指令可以接受数据类型的名称并合成一个用来描述这个数据类型的字符串,通常用来说明struct中实体的类型和大小。   NSRect rect = NSMakeRect (1, 2, 30, 40); NSValue *value = [NSValue valueWithBytes:&rect objCType:@encode(NSRect)]; [array addObject:value]; 用getValue:来提取数值: -(void) getValue: (void *)buffer; 调用getValue:时,需要传递存储这个数值的变量地址: NSRect rect = NSMakeRect(1, 3, 21, 100);
        NSValue *value = [NSValue valueWithBytes:&rect objCType:@encode(Rect)];
        
        NSRect r1;
        [value getValue:&r1];
        
        NSLog(@"rect value is %f", r1.origin.x); Cocoa提供了将常用的struct型数据转换成NSValue的便捷方法,如下: + (NSValue *) valueWithPoint:(NSPoint)aPoint; + (NSValue *) valueWithSize:(NSSize)size; + (NSValue *) valueWithRect:(NSRect)rect; - (NSPoint) pointValue; - (NSSize) sizeValue; - (NSRect) rectValue;   可以按照以下方式在NSArray中存储和提取NSRect值: value = [NSValue valueWithRect:rect]; [array addObject: value]; ... NSRect anotherRect = [value rectValue];   NSNull大概是Cocoa里最简单的类了,它只有一个方法 +(NSNull *) null; 添加到集合中: [contact setObject: [NSNull null] forKey: @"home fax machine"]; 表示了在key@"home fax machine"下没有值。   访问它的形式: id homefax = [contact objectForKey: @"home fax machine"]; if (homefax == [NSNull null]) { ... } [NSNull null]总是返回一样的数值,所以可以使用运算符==将该值与其他值进行比较。   示例:查找文件 int main (int argc, const char *argv[]) {      @autoreleasepool      {           NSFileManager *manager;           manager = [NSFileManager defaultManager];                      NSString *home;           home = [@"~" stringByExpandingTildeInPath];                      NSDirectoryEnumerator *direnum;           direnum = [manager enumeratorAtPath:home];             NSMutableArray *files;           files = [NSMutableArray arrayWithCapacity: 42];             NSString *filename;           while (filename = [direnum nextObject])           {                if ([[filename pathExtension] isEqualTo: @"jpg"]) {                     [files addObject: filename];                }           }             NSEnumerator *fileenum;           fileenum = [files objectEnumerator];             while (filename = [fileenum nextObject]) {                NSLog(@"%@", filename);           }      } return 0; } // main   @autoreleasepool是自动释放池的样本代码。   NSFileManager的defaultManager的类方法可以创建一个属于我们自己的NSFileManager对象。   在Cocoa中这种情况很常见,很多类都支持 单例架构(singleton architecture),也就是说只需要一个实例就够了,文件管理器、字体管理器或图形上下文确实只需要一个就够了。这些类都提供了一个类方法让你访问唯一的共享对象。   指定目录,可以使用绝对路径,如:“/Users/wmalik/”(wmalik是人名),但是该路径只有在主目录是vmalik时才有效。 在Unix系统(包括OS X系统)有一个代表主目录的速记符号~(也被称为波浪号)。   ~/Documents 代表文稿(Documents)目录,而~/junk/oopack.txt在Waqar(人名)的计算机上就是/Users/wmalik/junk /oopack.txt.NSString中有一个方法可以接受~字符并将其展开成主目录路径,代码如下: NSString *home = [@"~" stringByExpandingTildeInPath];   stringByExpandingTildeInPath方法将~替换成了当前用户的主目录。在Waqar的计算机上,主目录是/Users/wmailk。   将路径字符串传递给文件管理器: NSDirectoryEnumerator *direnum = [manager enumeratorAtPath: home];   enumeratorAtPath:返回一个NSDictonaryEnumerator对象,它是NSEnumerator的子类。每次这个枚举器对象中调用nextObject方法时,都会返回该目录中下一个文件的路径。这个方法也能搜索子目录中的文件。   while迭代结束后,将会得到主目录中每个文件的路径。   NSString提供了许多处理路径名称和文件名称的便捷工具,比如pathExtension方法能输出文件的扩展名(不包括前面那个点);   通过快速枚举方法(不支持Leopard之前的系统) int main (int argc, const char *argv[]) {      @autoreleasepool      {           NSFileManager *manager;           manager = [NSFileManager defaultManager];                      NSString *home;           home = [@"~" stringByExpandingTildeInPath];                      NSMutableArray *files;           files = [NSMutableArray arrayWithCapacity: 42];             for (NSString *filename in [manager enumeratorAtPath:home])           {                if ([[filename pathExtension] isEqualTo: @"jpg"]) {                     [files addObject: filename];                }           }             for (NSString *filename in files)           {                NSLog(@"%@", filename);           }                 }      return 0; } // main   Objective-C的三个语言特性: 类方法:即由类本身而不是某个示例来处理的方法。 @encode()指令:它用于需要描述C语言基础类型的方法。 快速枚举。   内存管理   内存管理是程序设计中常见的资源管理(resource management)的一部分。   在c语言中,我们必须确保在需要的时候分配内存,在程序运行结束时释放占用的内存。如果只分配而不释放内存,则会发生 内存泄露(leak memory):程序的内存占用量不断增加,最终会被耗尽并导致程序崩溃。同样需要注意的是,不要使用任何刚释放的内存,否则可能误用陈旧的数据,从而引发各种各样的错误,而且如果该内存已经加载了其他数据,将会破坏这些新数据。   对象生命周期 对象的生命周期包括诞生(通过alloc或new方法实现)、生存(接受消息并执行操作)、交友(通过复合以及向方法传递参数)以及最终死去(被释放掉)。当生命周期结束时,它们的原材料(内存)将被回收以供新的对象使用。   ios使用ARC之后,不允许直接调用retain, release, autorelease, dealloc, retainCount这些方法了,编译器会在合适的地方将这些代码添加进去,会报错误的信息: 如果真想使用retain等方法,解决方法:打开"Build Settings",找到Objective-C Automatic Reference Counting项,将它的值设置为NO。   引用计数 Cocoa采用了一种叫做 引用计数(reference counting)的技术, 有时也叫做保留计数(retain counting)。每个对象都有一个与之相关联的整数,被称作它的引用计数器或保留计数器。   当某段代码访问一个对象时,改代码就将该对象的保留计数器值加1, 当这段代码结束对象访问时,将对象的保留计数器减1.当保留计数器的值为0时,表示不再有代码访问该对象了,对象将被销毁,其占用的内存被系统回收。   当使用alloc、new方法或者通过copy消息(接收到消息的对象会创建一个自身的副本)创建一个对象时,对象的保留计数器值被设置为1,。要增加对象的保留计数器的值,可以给对象发送一条retain消息。要减少的话,可以给对象发送一条release消息。   当一个对象即将被销毁时,Objective-C会自动向对象发送一条dealloc消息。一定不要直接调用dealloc方法,Objective-C会在需要销毁对象时自动调用它。可以在自己的对象中重写dealloc方法,这样就能释放掉已经分配的全部相关资源。   要获得保留计数器的当前值,可以发送retainCount消息。下面是retain、release和retainCount的方法声明。 - (id) retain; - (oneway void) release; - (NSUInteger) retainCount;   retain方法返回一个id类型的值。通过这种方式,可以在接受其他消息的同时进行retain调用,增加对象的保留计数器的值并要求对象完成某种操作。 例如:[[car retain] setTire: tire atIndex: 2];表示要求car对象将其保留计数器的值加1并执行setTire操作。   Code: @interface RetainTracker : NSObject @end // RetainTracker   @implementation RetainTracker -(id) init {      if (self = [super init])      {           NSLog(@"init: Retain count of %lu.", [self retainCount]);      }      return (self); } // init   -(void) dealloc {      NSLog(@"dealloc called. Bye Bye.");      [super dealloc]; } // dealloc   @end // RetainTracker   init方法遵循标准的Cocoa对象的初始化方式。   int main (int argc, const char *argv[]) {      RetainTracker *tracker = [RetainTracker new];      // count: 1        [tracker retain]; // count: 2;      NSLog(@"%d", [tracker retainCount]);        [tracker retain]; // count: 3;      NSLog(@"%d", [tracker retainCount]);        [tracker release]; // count: 2;      NSLog(@"%d", [tracker retainCount]);        [tracker release]; // count: 1;      NSLog(@"%d", [tracker retainCount]);            [tracker retain]; // count: 2;      NSLog(@"%d", [tracker retainCount]);            [tracker release]; // count: 1;      NSLog(@"%d", [tracker retainCount]);        [tracker release]; // count: 0, dealloc it        return (0);       } // main   对象所有权(object ownership) 当我们说某个实体"拥有一个对象"时,就意味着该实体要负责确保对其拥有的对象进行清理。如果一个对象内有指向其他对象的实例变量,则称该对象拥有这些对象。如car对象拥有其指向的engine和tire对象。 同样的,如果一个函数创建了一个对象,则称该函数拥有这个对象。如在main函数中创建了一个新的car对象,因此称main()函数拥有car对象。   当多个实体拥有某个特定对象时,对象的所有权关系就更加复杂了,这也是保留计数器的值大于1的原因。     访问方法中的保留和释放 编写setEngine方法的第一个内存管理版本,可能如下: - (void) setEngine: (Engine *) newEngine {      engine = [newEngine retain];      // BAD CODE: do not steal. See fixed version below. } // setEngine   这样做是远远不够的。 Engine *engine1 = [Engine new]; // count: 1 [car setEngine: engine1]; // count: 2 [engine1 release]; // count 1   Engine *engine2 = [Engine new]; // count: 1 [car setEngine: engine2]; // count:2   这个时候engine1对象出问题了:它的保留计数器的值仍然为1.main()函数已经释放了对engine1对象的引用,但是Car类一直没有释放engine1对象。现在engine1对象已经发生了泄露。   下面对编写setEngine: 方法的另一个尝试: - (void) setEngine: (Engine *) newEngine {      [engine release];      engine = [new Engine retain];      // More BAD CODE: do not steal. Fixed version below. } // setEngine   该例子修复了前一个例子中的engine1对象泄露的错误,但是当newEngine对象和原来的engine对象是同一个对象时,这段代码也会出问题。如: Engine *engine = [Engine new]; // count 1 Car *car1 = [Car new]; Car *car2 = [Car new];   [car1 setEngine: engine]; // count: 2 [engine release]; // count 1   [car2 setEngine: [car1 engine]]; // oops!   更好的方法: -(void) setEngine: (Engine *) newEngine {      [newEngine retain];      [engine release];      engine = newEngine; } // setEngine   如果首先保留engine对象,即使newEngine与engine是同一个对象,保留计数器的值也将先增加,然后立即减少。由于没有归0,engine对象意外地未被销毁,这样就不会引发错误了。在访问方法中,如果先保留新对象,然后再释放对象就不会出现问题了。    ARC是iOS 5推出的新功能,全称叫 ARC(Automatic Reference Counting)。简单地说,就是代码中自动加入了retain/release,原先需要手动添加的用来处理内存管理的引用计数的代码可以自动地由编译器完成了。   所有对象放入池中   Cocoa中有一个自动释放池(autorelease pool)的概念。   NSObject类提供了一个叫做autorelease的方法: - (id) autorelease;   该 方法预先设定了一条会在未来某个时间发送release消息,其返回值是接受这条消息的对象。这一特性与retain消息采用了相同的技术,使嵌套调用更 加容易。当给一个对象发送autorelease消息时,实际上是将该对象添加到了自动释放池中。当自动释放池被销毁时,会向该池中的所有对象发送 release消息。     可以编写一个能够很好地管理内存的description方法。 -(NSString *) description {      NSString *description;      description = [[NSString alloc] initWithFormat: @"I am %d year old.", 25];        return ([description autorelease]); } // description   现在只需要编写一行代码就够啦: NSLog(@"%@", [someObject description]);   由于description方法中的字符串对象是自动释放的,该对象暂时被放入了当前活动的自动释放池中,等到调用NSLog()函数的代码运行结束以后,自动释放池会被自动销毁。   没有加autorelease的代码是:   自动释放池的销毁时间: 有两种方法可以创建一个自动释放池 1、通过@autoreleasepool 2、通过NSAutoreleasePool对象。   在Foundation库工具集中,创建和销毁自动释放池已经由@autorelease关键字完成。当使用@autorelease{}时,所有在花括号里的代码都会被放入这个新池子里。 注意:任何在花括号里定义的变量在括号外就无法使用了。   第二种方法是NSAutoreleasePool对象。创建和释放NSAutoreleasePool对象之间的代码就会自动使用这个新的池子。   NSAutoreleasePool *pool; pool = [NSAutoreleasePool new]; ...   [pool release]; 创建了一个自动释放池后,该池就会自动成为活动的池子。释放该池后,其保留计数器的值归0,然后该池被销毁,在销毁过程中,该池将释放其包含的所有对象。   推荐使用关键字方法。     #import <Foundation/Foundation.h>

@interface RetainTracker : NSObject
@end // RetainTracker

@implementation RetainTracker

- (id) init
{
     if (self = [super init])
    {
          NSLog (@"init: Retain count of %lu.", [self retainCount]);
     }
    
     return (self);
} // init


- (void) dealloc
{
     NSLog (@"dealloc called. Bye Bye.");
     [super dealloc];
} // dealloc

@end // RetainTracker

int main(int argc, const char * argv[])
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    
    RetainTracker *tracker = [RetainTracker new]; // count: 1
    
    [tracker retain]; // count: 2
    [tracker autorelease]; // count: still 2
    [tracker release]; // count: 1
    
    NSLog (@"releasing pool");
    [pool release];
    // gets nuked, sends release to tracker
    
    @autoreleasepool
    {
        RetainTracker *tracker2 = [RetainTracker new]; // count: 1
       
        [tracker2 retain]; // count: 2
        [tracker2 autorelease]; // count: still 2
        [tracker2 release]; // count: 1
       
        NSLog (@"auto releasing pool");
    }
   
    return (0);
}   Cocoa的内存管理规则: 如果我使用了new、alloc、或copy方法获得了一个对象,就释放或自动释放该对象。只要记住了这条规则,就不用担心内存释放的问题。     临时对象:   NSMutableArray *array; array = [[NSMutableArray alloc] init]; // count 1 // use the array   [array release]; // count 0   NSMutableArray *array array = [NSMutableArray arrayWithCapocity: 17]; // count: 1, autoreleased // use the array   arrayWithCapacity: 方法与alloc、new、copy这三个方法不同,因此可以假设该对象被返回时保留计数器的值为1且已经被设置为自动释放。当自动释放池被销毁时,向 array对象发送release消息,该对象的保留计数器的值归0,其占用的内存被回收。   使用NSColor类对象的部分代码如下: NSColor *color; color = [NSColor blueColor]; // use the color   blueColor方法也不属于alloc、new、copy这三个方法,因此可以假设该对象的保留计数器的值为1并且已经被设置为自动释放。 blueColor方法返回一个全局单例(singleton)对象--每个需要访问它的程序都可以共享的单一对象,这个对象永远不会被销毁,不过不需要关心其实现细节,只需知道不需要自己手动来释放color。   拥有对象: 想要在多段代码中一直拥有某个对象。典型的做法是:把对象加入到诸如NSArray或NSDictionary等集合中,作为其他对象的实例变量来使用,或作为全局变量来使用(比较罕见)。   如果你使用了new、alloc或copy方法获得了一个对象,则不需要执行任何其他操作。该对象的保留计数的值为1, 因此它将一直存在着,只需要确保在拥有对象的dealloc方法中释放它即可。 Code:   - (void) doStuff {      // flonkArray is an instance variable      flonkArray = [NSMutableArray new]; // count 1   } // doStuff   -(void) dealloc {      [flonkArray release]; // count 0      [super dealloc]; } // dealloc   使用除alloc、new、或copy以外的方法获得了一个对象,需要记得保留该对象。如果编写的是GUI应用程序,要考虑到事件循环。需要保留自动释放的对象,以便这些对象在当前的事件循环结束以后仍能继续存在。   使用自动释放对象时, - (void) doStuff {      // flonkArray is an instance variable      flonArray = [NSMutableArray arrayWithCapacity: 17];      // count 1, autoreleased      [flonkArray retain]; // count: 2, 1 autorelease } // doStuff   - (void) dealloc {      [flonkArray release]; // count : 0      [super dealloc]; } // dealloc   在 当前事件循环结束(如果这是一个GUI程序)或自动释放池被销毁时,flonkArray对象会接收到一条release消息,因而其保留计数器的值从2 变为1.其保留计数器的值大于0,故该对象继续存在。并且需要在自己的dealloc方法中release这个对象。如果没有在doStuff方法中向 flonkArray对象发送retain消息,则flonkArray对象将会被意外地销毁。       垃圾回收 Objective-C 2.0引入了自动内存管理机制,也称垃圾回收。它是一个可以选择是否启动的功能,只需要转到项目信息窗口的Build Setting选项卡,并选择Required[-fobjc-gc-only]选项即可。     启动垃圾回收以后,平常的内存管理命令全都变成了空操作指令,也就是说它们不执行任何操作。   与自动释放池一样,垃圾回收器也是在事件循环结束时触发的。如果编写的不是GUI程序,也可以自己触发垃圾回收器。垃圾回收器定期检查变量和对象并且跟踪它们之间的指针,当发现没有任何变量指向某个对象时,就将该对象视为应该丢弃的垃圾。   垃 圾回收功能只支持OS X应用开发,无法用在iOS应用程序上。实际上在iOS开发中,苹果公司建议你不要在自己的代码中使用autorelease方法,也不要使用会返回自动 释放对象的一些便利方法。一般这些便利方法都会返回一个新对象的类方法。比如说NSString,所有以stringWith开头的方法都是便利方法。   自动引用计数:   在iOS中无法使用垃圾回收的主要原因是无法知道垃圾回收器什么时候会起作用。 苹 果公司的解决方案被称为自动引用计数(automatic reference counting, ARC).顾名思义,ARC会追踪你的对象并决定哪一个仍会使用而哪一个不会再用到。如果你启用了ARC,只管像平常那样按需分配并使用对象,编译器会帮 你插入retain和release语句,无需自己动手。   ARC不是垃圾回收器。垃圾回收器在运行时工作,通过返回的代码来定期检查对象。与此相反,ARC是在编译时进行工作的。它在代码中插入了合适的retain和release语句,就好像你自己动手写好了所有的内存管理代码。不过,是编译器替你完成了内存管理的工作。   ARC是一个可选的功能,也就是说你必须明确地启用或禁止它。   编写ARC代码所需的条件: Xcode4.2以上版本; Apple LLVM 3.0以上版本的编译器。 OS X 10.7以上版本的系统。   可以运行ARC代码的设备必须满足的条件: iOS 4.0以上的移动设备或OS X 10.6以上版本的64位系统的电脑; 归零弱引用(zereoing weak reference)需要iOS 5.0或OS X 10.7以上版本的系统。   想要在代码中使用ARC,必须满足一下三个条件:   1、能够确定哪些对象需要进行内存管理; 2、能够表明如何去管理对象; 3、有可行的办法传递对象的所有权。   第一个条件是对象的最上层集合知道如何去管理它的子对象。 例子: 通过malloc:方法创建的字符串数组: NSString **myString; myString = malloc(10 * sizeof(NSString *)); 这段代码创建了一个指向10个字符串的C型数组。因为C型数组不是可保留的对象,所以无法在这个结构体里使用ARC特性。   第二个条件是必须能够对某个对象的保留计数器的值进行加1或减1的操作。也就是说所有NSObject类的子类都能进行内存管理。   第三个条件是在传递对象的时候,你的程序需要能够在调用者和接收者之间传递所有权。   当 用指针指向某个对象时,可以管理它的内存(通过retain和release),拥有这个对象的强引用(strong reference)。也可以不管理,那么就拥有该对象的弱引用(weak reference)。比如:对属性使用了assign特性,便创建了一个弱引用。   弱引用的作用:有助于处理保留循环(retain cycle)。   你拥有一个其他对象创建并且保留计数器的值为1的对象A 对象A创建了保留计数器的值为1的对象B并将其作为子对象。 对象B需要能够访问它的父对象。 在这个示例中,因为对象A创建了对象B,所以对象A拥有了一个指向对象B的强引用。现在对象B有一个指向对象A强引用,那么对象A的保留计数器的值会增加到2.   当对象A的拥有者不再需要它的时候,就会向对象A发送release消息,这样对象A的保留计数器的值就减少到1.不过由对象A所创建的对象B的保留计数器的值仍为1,所以它们都没有被释放掉。这就是一个经典的内存泄露:程序无法访问到这些对象,但它们仍占用着内存容量。 为了解决这个问题,可以使用弱引用。使用assign来获取对象B指向对象A的引用。由于是弱引用,保留计数器的值不会增加1,所以当对象A的拥有者释放它的时候,它的保留计数器就会变成0;它也会释放对象B。 但还算不上完美。在你拥有3个对象的时候,假设对象A通过强引用指向对象B,而对象C通过弱引用指向了对象B。 如果对象A释放了对象B,那么对象C仍将拥有指向对象的弱引用,但这个引用已经失效了,直接使用它会导致问题,因为指向的地方已经没有有效值了。 解决方法就是让对象自己去清空弱引用的对象。这种特殊的弱引用被称为 归零弱引用(zeroing weak reference),因为在指向的对象释放之后,这些弱引用就会被设置为零(即nil),就可以像平常的指向nil值的指针一样被处理。归零弱引用只在iOS 5和OS X 10.7以上的版本中有效。   如果想要使用归零弱引用,必须明确地声明它们。 有两种方式可以声明归零弱引用: 声明变量时使用_weak关键字或对属性使用weak特性。   _weak NSString *myString; @property(weak) NSString *myString;

想在不支持弱引用的旧系统上使用ARC应该怎么办,苹果公司提供了_unsafe_unretained关键字和unsafe_unretainded特性,它们会告诉ARC这个特殊的引用时弱引用。   使用ARC的时候有两种命名规则需要注意: 1、属性名称不能以new开头,比如说@property NSString *newString是不被允许的。 2、属性不能只有一个read-only而没有内存管理特性。如果你没有启动ARC,可以使用@property (readonly) NSString *title语句, 如果启用了ARC功能,就必须指定由谁来管理内存。因为默认的特性是assign。   同样的强引用也有自己的_strong关键字和strong特性。内存管理的关键字和特性是不能一起使用的,两者相互排斥。   垃圾回收和ARC是无法一同使用的。   禁止垃圾回收: 点击项目,在显示的Build Setting里找到Object-C Garbage Collenction 转换过程一共需要两步,首先必须确保你的代码能符合ARC的需求,然后执行转换操作。   ARC转换是一个单程的操作。一旦你转换成ARC版本,就不可以恢复了。     拥有者权限: 指针支持ARC的一个条件是必须是 可保留对象指针(ROP).这意味着,你不能简单地将一个ROP表示成不可保留对象指针(non-ROP),因为指针的所有权会移交。   NSString *theString = @"Learn Objective-C"; CFStringRef cfString = (CFStringRef)theString;   theString指针是一个ROP,而另一个CFStringRef则不是。为了让ARC便于工作,需要告诉编译器哪个对象是指针的拥有者。为此可以使用一种被称为 桥接转换(bridged cast)的C语言技术。这是一个标准的C语言类型转换,不过使用的是其他关键字:_bridge、_bridge_retained和 _bridge_transfer。属于bridge指的是使用不同的数据类型达到同一目的的能力,而不是当你陷入ARC转换困境时帮你脱离困境的工具。   另一个限制是结构体(struct)和集合体(union)不能使用ROP作为成员,因此下面这样的代码是不被允许的。   // Bad code. Do not steal or sell. struct {      int32_t foo;      char *bar;      NSString *baz; } MyStruct; 可以通过使用void *和桥接转换来解决这个问题。如果想要分配并获取字符串,代码如下: struct{      int32_t foo;      char *bar;      void *baz; } MyStruct; MyStruct.baz = (_bridge_retained void *)theString; NSString *myString = (_bridge_transfer NSString *)MyStruct.baz;   不能对ARC管理的对象调用的管理方法: retain、retainCount、release、autorelease、dealloc。   有时需要释放不支持ARC的对象或执行其他清理操作,所以仍要实现dealloc方法,但是不能直接调用[super dealloc]。 以下是不能对ARC对象进行重写的方法: retain、retainCount、release、autorelease。   异常   异常就是意外事件,比如数组溢出,因为程序不知道怎么处理就会扰乱程序流程。   Cocoa中使用NSException类来表示异常。Cocoa要求所有的异常必须是NSException类型的异常,虽然可以通过其他对象来抛出异常,但Cocoa并不会处理它们。也可以创建NSException的子类来作为你自己的异常。     如果想支持异常特性,请确保-fobj-exceptions项被打开。可以在Xcode中启动Enable Objective-C Exceptions。 在运行时系统中创建并处理异常的行为被称为抛出(throwing)异常,或者说是提出(raising)异常。需要注意NSException拥有一些以raise开头的方法名。有些是类方法,而raise本身是实例方法。 处理被抛出的异常的行为被称为捕捉(catching)异常。     与异常有关的关键字。 异常的所有关键字都是以@开头的。 @try:定义用来测试的代码块以决定是否要抛出异常。 @catch(): 定义用来处理已抛出异常的代码块。接受一个参数,通常是NSException类型,但也可能是其他类型。 @finally:定义无论是否有抛出异常都会执行代码块,这段代码总是会执行的。 @throw: 抛出异常。     代码: @try {      // code you want to execute that might throw an exception } @catch (NSException *exception) {      // code to execute that handles exception } @finally {      // code that will always be executed. Typically for cleanup. }   捕捉不同类型的异常。 可以使用多个@catch代码块来捕捉不同的异常类型。处理代码应该按照从具体到抽象的顺序排序,并在最后使用一个通用的处理代码: @try{ }@catch (MyCustomException *custom) { }@catch (NSException *exception) { }@catch (id value) { }@finally { } 抛出异常 程序会创建一个NSException实例来抛出异常,并会使用以下两种技术之一: 1、使用"@throw异常名;"语句来抛出异常; 2、向某个NSException对象发送raise消息。   创建异常: NSException *theException = [NSException exceptionWithName: ...]; 抛出这个异常: @throw theException; 或者: [theException raise];   两种方法都可以使用,但不要两种都使用。两种方法的区别是raise只对NSException对象有效,@throw也可以用在其他对象上。   @try {      NSException *e = ...;      @throw e; } @catch (NSException *e) {      @throw; // rethrow e. } 在@catch异常处理代码中,你可以重复抛出异常而无需指定异常对象。   与当前@catch异常处理代码相关的@finally代码块会在@throw引发下一个异常处理调用之前执行代码,因为@finally是在@throw发生之前调用的。 Objective-C的异常机制与C++的异常机制兼容。   异常也需要内存管理:   -(void)mySimpleMethod {      NSDictionary *dictionary = [[NSDictionary alloc] initWith...];      [self processDictionary:dictionary];      [dictionary release]; } 假设processDictionary抛出了一个异常,程序从这个方法跳出并寻找异常处理代码,而dictionary release没法执行,字典对象就没有被释放,出现内存溢出。 简单的处理: -(void)mySimpleMethod {      NSDictionary *dictionary = [[NSDictionary alloc] initWith...];      @try {           [self processDictionary:dictionary];      }      @finally {           [dictionary release];      } } 异常和自动释放池 -(void)myMethod {      NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];      NSDictionary *myDictionary = [[NSDictionary alloc] initWithObjectsAndKeys:@"lalala", nil];      @try {           [selt processDictionary:myDictionary];      } @catch (NSException *e) {           @throw;      } @finally {           [pool release];      } } 上面代码在@catch代码块中再次抛出异常,而@finally代码块会在异常重新抛出之前执行代码。这样会导致本地pool的释放早于异常通知,因此它会变成可怕的僵尸异常(zombie exception)。有个简单的方法解决: -(void)myMethod {       id savedException = nil;      NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];      NSDictionary *myDictionary = [[NSDictionary alloc] initWithObjectsAndKeys:@"lalala", nil];      @try {           [selt processDictionary:myDictionary];      } @catch (NSException *e) {           savedException = [e retain];           @throw;      } @finally {           [pool release];           [savedException autorelease];      } } 通过使用retain方法,在当前池中保留了异常。当池被释放时,我们早已保存了一个异常指针,它会同当前池一同释放。   当对象接受到一条autorelease消息时,其保留计数器的值并不会发生改变。该对象只是被放入了NSAutoreleasePool当中。

转载于:https://wwwblogs/hengshu/p/5007562.html

更多推荐

Objective-C 03 Foundation Kit

本文发布于:2023-06-13 07:04:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1353846.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:Objective   Kit   Foundation

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!