(二)记忆体管理
Objective-C 基本上的记忆体管理,跟 C 的 malloc() 及 free(),或是跟 C++ 的 new 跟 delete 一样,你动态配置的记忆体,你要负责把它释放掉,不过多了下面几项新功能:
- reference count 机制
- autorelease pool 机制
- garbage collection 机制
(1) reference count 机制,这个机制是用来管控动态配置的记忆体的生命周期,每个继承 NSObject 的类别,内部都会记录有多少对象,参考并引用到这个物件,这个数字就叫 reference count,当 reference count 等于零时,物件就会被释放。如果你以前接触过 COM interface 其实原理一模一样。当物件 reference count > 0 时,物件所占用的记忆体,会被系统保留,当 reference count 为零时,物件就会被删除。
(2) autorelease pool 机制,这个机制是用来简化动态配置的物件的管理,举个例子,你有一个操作,假设需要用到 100 个暂时用到的动态物件,那你在操作结束的地方就要写 100 行程式码,来释放这些物件。万一在程式执行到一半的地方,有配置了另外 50 个,那不就天下大乱。 autorelease pool 让你使用这个系统物件,来追踪你所使用的动态物件,对于每个物件,透过 [obj autorelease]讯息,来加到 pool 里,而你可以在该释放物件的地方,用 [pool drain] 的讯息,把 pool 里所有的物件释放。最后你看到范例,会比较清楚。
(3) garbage collection 则是把动态配置记忆体的管理,完全交给 Objective-C,你可只管配置,等到物件都没地方需要了,自动就被 Objective-C 给释放了,就跟 Java 或是 C# 一样,不过为了对系统记忆体使用的最佳化,iPhone 是不支援 garbage collection 的记忆体管理功能。
(4) 来看这个例子,宣告了一个 Foo 类别继承 NSObject,里面什么都不做。接着动态配置了一个 Foo 物件,然后看它的 reference count 应该是 1,在还没把 Foo 物件加到 autorelease pool 前,autorelease pool 释放所内含的物件时,不会影响 Foo 物件的 reference count。
我们再重新产生一个 autorelease pool 物件,接着我们把 Foo 物件用 [myFoo autorelease] 讯息加到 autorelease pool 内,然后刻意把 Foo 物件的 reference 加 1,因为等一下 [pool drain] 时,也会去释放 Foo 物件,为了能够之后还能够看到 Foo 物件的 reference count,刻意加 1。然后你可以看到,加到 autorelease pool 的物件,在 [pool drain] 之后,reference count 确实会减 1。
1: #import2:3: @interface Foo : NSObject4: {5: int x;
6: }7:8: @end9:10: @implementation Foo11: @end12:13:14: int main (int argc, const char * argv[])15: {16: NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];17:18: Foo *myFoo = [[Foo alloc] init];19: NSLog(@"myFoo retain count = %x", [myFoo retainCount]);
20:21: [pool drain];22: NSLog(@"after pool drain = %x", [myFoo retainCount]);
23:24: pool = [[NSAutoreleasePool alloc] init];25: [myFoo autorelease];26: NSLog(@"after autorelease = %x", [myFoo retainCount]);
27:28: [myFoo retain];29: NSLog(@"after retain = %x", [myFoo retainCount]);
30:31: [pool drain];32: NSLog(@"after second pool retain = %x", [myFoo retainCount]);
33:34: [myFoo release];35:36: return 0;
37: }38:
(5) 执行结果如下图
有关于 Objective-C 的记忆体管理,其实没有很复杂,只是要用文字说明,很啰唆。这个例子,如果你明白是在做什么,你就懂了。而 gargabe collection 你就这么想,当这个功能启动了(这是一个编译器选项)那些控制 reference count 的讯息全部会被忽略,改由 Objective-C 接管。而 iPhone 是不支援 garbage collection 的,所以要让你的程式在两种模式下都能正常编译,你就当成没有 garbage collection,当你的程式码在有 garbage collection 的环境下编译,所有控制 reference count 的讯息都会被忽略。
(三)档案的存取
Objective-C 的档案存取其实就是 Linux 上 C Runtime 上的 File I/O 函式的包装,因为 Mac OS X 的 Kernel 其实就是 BSD,而在 Linux 作业系统上,每一个资源,档案、目录、装置、驱动程式、网路 … 通通可以视为是一个档案,这就是 Linux 美妙之处,系统的所有物件,都可以当成档案来处理。
Foundation Framework 提供的档案存取类别,包括了 Linux 上这些档案处理的函式:
- Standard File I/O - fopen(), fread(), fwrite(), fseek(), fclose() 等
- Low Level File Access – open(), close(), read(), write(), seek() 等
- Directory Maintenance – mkdir(), rmdir(), chdir(), getcwd() 等
如果你孰悉 Linux 上对于档案处理的这些函式,那么你会觉得如鱼得水。如果你之前没有接触过 Linux 也没问题,后面举许多的例子来说明。
(1) Standard File I/O,都是透过 NSFileManager 这个物件类别来进行操作,下面这个例子,把档案 testfile 拷贝到 newfile,然后把 newfile 改名为 newfile2,再把 testfile 删除,最后用存在记忆体内的资料新创 newfile3,你也可以透过 NSFileManager 来查询档案的属性,例如档案大小。
1: #import2:3: int main (int argc, const char * argv[])4: {5: NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];6:7: NSString *fName = @"testfile";
8: NSFileManager *fm;9: NSDictionary *attr;10: NSData *fileData;11:12: // Need to create an instance of the file manager
13:14: fm = [NSFileManager defaultManager];15:16: // Let's make sure our test file exist first
17:18: if ([fm fileExistsAtPath: fName] == NO)
19: {20: NSLog(@"File doesn't exist!");
21: return 1;
22: }23:24: // Now let's make a copy
25:26: if ([fm copyItemAtPath: fName toPath: @"newfile" error: nil] == NO)27: {28: NSLog(@"File copy failed!");
29: return 2;
30: }31:32: // Let's test to see if the two files are identical
33:34: if ([fm contentsEqualAtPath: fName andPath: @"newfile"] == NO)35: {36: NSLog(@"files are not equal!");
37: return 3;
38: }39:40: // Let's rename the copy
41:42: if ([fm moveItemAtPath: @"newfile" toPath: @"newfile2" error: nil] == NO)43: {44: NSLog(@"File rename failed!");
45: return 4;
46: }47:48: // Get the size of newfile2
49:50: if ((attr = [fm attributesOfItemAtPath: @"newfile2" error: nil]) == nil)51: {52: NSLog(@"Couldn't get file attributes!");
53: return 5;
54: }55:56: NSLog(@"File size is %i bytes",
57: [[attr objectForKey: NSFileSize] unsignedLongValue]);58:59: // And finally, let's delete the original file
60:61: if ([fm removeItemAtPath: fName error: nil] == NO)
62: {63: NSLog(@"File removal failed!");
64: return 6;
65: }66:67: NSLog(@"All operations were successful!");
68:69: // Display the contents of he newly-created file
70:71: NSLog(@"%@", [NSString stringWithContentsOfFile: @"newfile2" encoding: NSUTF8StringEncoding error: nil]);72:73: // Read the file newfile2
74:75: fileData = [fm contentsAtPath: @"newfile2"];
76: if (fileData == nil)
77: {78: NSLog(@"File read failed!");
79: return 1;
80: }81:82: // Write the data to newfile3
83:84: if ([fm createFileAtPath: @"newfile3" contents: fileData attributes: nil] == NO)85: {86: NSLog(@"Couldn't create the copy!");
87: return 2;
88: }89:90: NSLog(@"File copy was successful!");
91:92: [pool drain];93: return 0;
94: }95:
(2) Directory Maintenance - 还是老朋友 NSFileManager 帮你,下面这个例子,查询目前的工作目录,接着新增一个目录 testdir,然后把目前所在的工作目录切换到我们新增的目录。
1: #import2:3: int main (int argc, const char * argv[])4: {5: NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];6: NSString *dirName = @"testdir";
7: NSString *path;8: NSFileManager *fm;9:10: // Need to create an instance of the file manager
11:12: fm = [NSFileManager defaultManager];13:14: // Get current directory
15:16: path = [fm currentDirectoryPath];17: NSLog(@"Current directory path is %@", path);
18:19: // Create a new directory
20:21: if ([fm createDirectoryAtPath: dirName
22: withIntermediateDirectories:YES attributes: nil error: nil ] == NO)23: {24: NSLog(@"Couldn't create directory!");
25: return 1;
26: }27:28: // Rename the new directory
29:30: if ([fm moveItemAtPath: dirName toPath: @"newdir" error: nil] == NO)31: {32: NSLog(@"Move directory failed!");
33: return 1;
34: }35:36: // Get and display current working directory
37:38: if ([fm changeCurrentDirectoryPath: @"newDir"] == NO)39: {40: NSLog(@"Change directory failed!");
41: return 3;
42: }43:44: // Now get and display current working directory
45:46: path = [fm currentDirectoryPath];47: NSLog(@"Current directory path is %@", path);
48:49: NSLog(@"All operation were successful!");
50:51: [pool drain];52: return 0;
53: }54:
(3) 接下来这个例子,用两种方法列出目录里的内容,先用 NSDirectoryEnumerator 一个一个列,另外就直接用 contentsOfDirectoryAtPath 这个操作,一次把所有档案名称读到阵列中。第一种方法,在目录中不确定有多少档案的时候用,不过机会非常的少。
1: #import2:3: int main (int argc, const char * argv[])4: {5: NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];6: NSString *path;7: NSFileManager *fm;8: NSDirectoryEnumerator *dirEnum;9: NSArray *dirArray;10:11: // Need to create an instance of the file manager
12:13: fm = [NSFileManager defaultManager];14:15: // Get current working directory path
16:17: path = [fm currentDirectoryPath];18:19: // Enumerate the directory
20:21: dirEnum = [fm enumeratorAtPath: path];22:23: NSLog(@"Contents of %@:", path);
24:25: while ((path = [dirEnum nextObject]) != nil)
26: {27: NSLog(@"%@", path);
28: }29:30: // Another way to enumberate a directory
31:32: dirArray = [fm contentsOfDirectoryAtPath: [fm currentDirectoryPath] error: nil];33:34: NSLog(@"Contents using contentsOfDirectoryAtPath:");
35:36: for (path in dirArray)
37: NSLog(@"%@", path);
38:39: [pool drain];40: return 0;
41: }42:
(4) NSProcessInfo 用来查询有关正在执行的程式(也就是自己啦)的一些很有用的资讯,这个在 Linux 上用 C 的系统呼叫来查询,算是进阶喔。Objective-C 帮你都打包到 NSProcessInfo 这个类别里。process 在 Linux 上也是被当成一个档案喔。
1: #import2:3: int main (int argc, const char * argv[])4: {5: NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];6:7: NSFileManager *fm;8: NSString *source, *dest;9: BOOL isDir;10: NSProcessInfo *proc = [NSProcessInfo processInfo];11: NSArray *args = [proc arguments];12:13: fm = [NSFileManager defaultManager];14:15: // Check for two arguments on the command line
16:17: if ([args count] != 3)
18: {19: NSLog(@"Usage: %@ src dest", [proc processName]);
20: return 1;
21: }22:23: source = [args objectAtIndex: 1];24: dest = [args objectAtIndex: 2];25:26: // Make sure the source file can be read
27:28: if ([fm isReadableFileAtPath: source] == NO)
29: {30: NSLog(@"Can't read %@", source);
31: return 2;
32: }33:34: // See if the destination file is a directory
35: // if it is, add the source to the end of the destination
36:37: [fm fileExistsAtPath: dest isDirectory: &isDir];38:39: if (isDir == YES)
40: {41: dest = [dest stringByAppendingPathComponent: [source lastPathComponent]];42: }43:44: // Remove the destination file if it is already exists
45:46: [fm removeItemAtPath: dest error: nil];47:48: // Okay, time to perform the copy
49:50: if ([fm copyItemAtPath: source toPath: dest error: nil] == NO)
51: {52: NSLog(@"Copy failed!");
53: return 3;
54: }55:56: NSLog(@"Copy of %@ to %@ succeeded!", source, dest);
57:58: [pool drain];59: return 0;
60: }61:
(5) Low Level File Access - 最后一个例子,展示如何用 Low Level File I/O 来存取档案。何时会用到?有些特殊的系统档案,例如网路 socket,必须用 Low Level 的档案纯取,因为在真正网路通讯,其实很多资料的传输都是非同步在发生。这个部分,参考 Linux 上的 socket network prorgramming,这个部分已经超出我们要讨论的范围。
1: #import2:3: int main (int argc, const char * argv[])4: {5: NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];6: NSFileHandle *inFile;7: NSFileHandle *outFile;8: NSData *buffer;9:10: // Open tthe file fileA for reading
11:12: inFile = [NSFileHandle fileHandleForReadingAtPath: @"fileA"];
13:14: if (inFile == nil)
15: {16: NSLog(@"Open of fileA for read failed");
17: return 1;
18: }19:20: // Open the file fileB for updating
21:22: outFile = [NSFileHandle fileHandleForWritingAtPath: @"fileB"];
23:24: if (outFile == nil)
25: {26: NSLog(@"Open of fileB for writing failed");
27: return 2;
28: }29:30: // Seek to the end of outFile
31:32: [outFile seekToEndOfFile];33:34: // Read inFile and write its contents to outFile
35:36: buffer = [inFile readDataToEndOfFile];37: [outFile writeData: buffer];38:39: // Close the two files
40:41: [inFile closeFile];42: [outFile closeFile];43:44: [pool drain];45: return 0;
46: }47:
下次,我们来研究 UIKit Framework,开始来写 iPhone 上跑的程式了。