常见语法说明
2.1 头文件引用使用 #import “文件名”或者 #import <文件名>的形式以确保每个
头文件仅被包含一次。
2.2 类声明以 @interface 类名:继承类 开头,以 @end 结尾,类实现以
@implementation 类名 开头,以 @end 结尾。
2.3 实例方法,即成员方法,在方法名前面添加一个减号(-);类方法,在方法名前面
添加一个加号(+)。
2.4 类方法的调用格式为 [类名 类方法],成员方法调用格式为 [实例名 实例方法],这种模式在ObjC中被称为消息机制,[对象 消息]即给对象发送了一个消息,产生的效果就是该对象调用了该类中定义的对应的实例方法。
2.5 下面以一个简单的例子来说明上述语法:2.5.1 Photo 类声明
#import <Cocoa/Cocoa.h> // 头文件引用
@interface Photo : NSObject {
// 成员属性
NSString* caption;NSString* photographer;
}
// ObjC的所有类都继承于NSObject
// 成员方法
- (NSString*)caption;
- (NSString*)photographer;
- (void) setCaption: (NSString*)input;
- (void) setPhotographer: (NSString*)input;
// 类方法
+ (NSString*)className;
@end
2.5.2 Photo 类实现
#import "Photo.h"
@implementation Photo
- (NSString*) caption {
return caption; }
- (NSString*) photographer {
return photographer;
}
- (void) setCaption: (NSString*)input {
[caption autorelease];
caption = [input retain]; }
- (void) setPhotographer: (NSString*)input {
[photographer autorelease];
photographer = [input retain]; }
+ (NSString*) className {
return (@"Photo"); }
@end
2.5.3 Photo 类使用
#import "Photo.h"
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];// 初始化
Photo* p = [[Photo alloc] init];
// 设置器调用
[p setCaption:@"MyCaption"];
[p setPhotographer:@"MyPhotographer"];
// 类方法调用
NSLog(@"ClassName = \"%@\"", [p className]);
// 获取器调用
NSLog(@"Caption = \"%@\"", [p caption]);NSLog(@"Photographer = \"%@\"", [p photographer]);
[pool drain]; return 0;
}
首先,我们来看初始化方法(这里叫法有点区别)。
其中,if (self = [super init]) 沿用了C语言的代码风格,要求父类作初始化操作,成功之后方能对该类成员进行其它自定义操作。这里如果我们使用 self = [superinit]; if (nil != self)来代替,最终效果是一样的,请根据代码标准、项目要求和个人风格来选择具体的写法。值得注意的是,该方法的返回值是一个可以指向任意对象的指针,为id类型。
然后我们再来看“析构”方法:
对于以细心出名的程序员来说,这样的写法肯定不会让大家满意,因为仅仅只是把数据成员获得的内存空间释放掉,而指针地址依然存在,极有可能造成野指针Bug,因此请大家在给数据成员发送了release消息后将其指针赋空,如下所示:
不过,联想一下ObjC简洁的特点(我把下面引入的一种写法归结到简洁这个特点中,希望没错),我们介绍一下ObjC与标准C++的又一重要区别——点操作符。
在ObjC中使用点操作符表示通过设置器或者获取器使用成员属性。因此,我们将dealloc方法改进成如下形式:
结合我们前面设置器的实现可以发现,成员属性会先接受一个autorelease方法,然后会用传入参数给其赋值,而我们传入参数为nil,因此刚好完成了我们的析构过程。关于autorelease的使用,我会在内存管理中进行描述。
3. 内存管理
3.1 引用计数Cocoa采用了一种称为内存计数的技术,每个对象都有一个与之关联的整数,称其为引用
计数器。当某段代码要访问一个对象时,该代码将该对象的引用计数器加1,当这段代码结束对象的访问时,将该对象的引用计数器减1,当引用计数器的值为0时,表示不再有代码访问该对象了,因此对象将被销毁,其占用的内存被系统回收以便重用。
3.2 内存管理的相关消息当使用alloc、new方法或者通过copy消息创建一个对象时,对象的引用计数器就被设
置为1。要增加对象的引用计数器值可以给对象发送一条retain消息。要减少引用计数器的值可以给对象发送一条release消息。
当一个对象的引用计数器的值变为0时,ObjC自动向对象发送一条dealloc消息,对象的dealloc方法可以被重写,但最后一定要记得给该对象的super发送一条dealloc消息。该方式可以释放已经分配的全部相关资源,一定不要直接调用dealloc方法。如果在释放对象时需要知道当前引用计数器的值,可以给对象发送一条retainCount消息,该消息返回类型为unsigned。
3.3 release和autorelease上面讲过,release消息可以让对象的引用计数器立即减1,而autorelease呢,你也
许会发现在你创建的ObjC项目中,main方法的开头和结尾都会有这样的代码:
这就是Cocoa中引入的自动释放池,它是一个存放实体的池,这些实体可能是对象,能够被自动释放。于是,NSObject类提供了一个autorelease的方法,该方法预先设定了一条在某个时间发送的release消息,其返回值是接收到消息的对象。当给一个对象发送autorelease消息的时候实际上是将该对象添加到NSAutoreleasePool中。当自动释放池被销毁时,会向该池中的所有对象发送release消息。
自动释放池是一个很让人喜欢的机制,但是,系统资源是有限的,垃圾箱再大也不可能比
放垃圾箱的房子要大,当垃圾塞满了整个房间的时候,大家也就没有心情再用这个房间干其它
的事情了。
批注 [DF1]: 该方法只是清空自动释放池而不是销毁它。-drain 方法只适用于Mac OS X 10.4(Tiger)及更高版本。在我们自己编写的代码中,我们使用-release方法,因为该方法适用于Mac OS 的所有版本。
批注 [DF2]: 在开发iPhone 应用程序的时候,苹果公司建议你不要在自己的代码中适用 autorelease 方法,同时还要避免适用创建自动释放对象的便利函数。
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; // insert your code...
[pool drain];
所以,对于需要频繁创建临时变量或者自动释放对象的代码,请自己创建一个自动
释放池,并在这段代码执行结束后释放它,也就是在每个卧室中都放上一个小垃圾桶,不要让
所有的垃圾都丢到客厅里面来。
4. 我们经常要用的
4.1 ObjC字符串在ObjC中提供了一个NSString类用于支持字符串的相关操作。我们用一段代码来说明
我们经常使用的功能,注意,仅仅只是经常使用的功能,NSString的功能也许要比你我想像的要大的多。
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// 字符串的创建和初始化
NSString* sFormat = [[NSString alloc] initWithFormat:@"Year:%d", 2009];
NSString* sString = [[NSString alloc] initWithString:sFormat]; NSString* sTmp = nil;
// 获取长度
NSUInteger uLen = [sFormat length];NSLog(@"uLen = %u", uLen);
// 获取指定索引处的字符
NSUInteger index = 0;
unichar c = [sFormat characterAtIndex:index];
NSLog(@"The character at index %u of \"%@\" is \'%c\'", index,
sFormat, c);
// 字符串大小写转换/*
Case transformations aren’t guaranteed to be symmetrical or to
produce
strings of the same lengths as the originals. The result of this
statement:
lcString = [myString lowercaseString];
might not be equal to this statement:
lcString = [[myString uppercaseString] lowercaseString];
For example, the uppercase form of “ß” in German is “SS”, so
converting
“Straße” to uppercase, then lowercase, produces this sequence of
strings:
“Straße”
“STRASSE”
“strasse”
*/
sTmp = [sFormat lowercaseString]; NSLog(@"Lowercase String of %@ is %@", sFormat, sTmp); sTmp = [sString uppercaseString]; NSLog(@"Uppercase String of %@ is %@", sString, sTmp);
// 字符串比较
// 地址比较
if (@"Year:2009" == sFormat){
NSLog(@"The address of sFormat is equal to constant string \"Year:2009\"");
}
else
{ NSLog(@"The address of sFormat isn't equal to constant string
\"Year:2009\""); }
// 内容比较
if ([sFormat isEqualToString:@"Year:2009"]){
NSLog(@"The content of sFormat is equal to constant string \"Year:2009\"");
}
else
{ NSLog(@"The content of sFormat isn't equal to constant string
\"Year:2009\""); }
// 忽略大小比较
if (![sFormat isEqualToString:sTmp]
&& NSOrderedSame == [sFormat compare:sTmp options:NSCaseInsensitiveSearch])
{ NSLog(@"The 2 strings are the same without comparing their
case");}
// 比较起始和结尾
sTmp = @"Year";
if ([sFormat hasPrefix:sTmp]){
NSLog(@"sFormat has prefix %@", sTmp); }
sTmp = @"2009"; if ([sFormat hasSuffix:sTmp]) {
NSLog(@"sFormat has suffix %@", sTmp); }
// 字符串的拼接
sTmp = [sFormat stringByAppendingFormat:sString];NSLog(@"%@ + %@ = %@", sFormat, sString, sTmp);
// 字符串的查找
NSRange range = [sTmp rangeOfString:@":2009Y"];if (NSNotFound != rang.location)
{ NSLog(@"The substring is start from %d to %d in %@",
range.location, range.location + range.length - 1, sTmp);
}
// 字符串的替换
NSLog(@"sTmp is %@ before replacing", sTmp);
sTmp = [sTmp stringByReplacingOccurrencesOfString:@"2009"
withString:@"1949"]; NSLog(@"sTmp is %@ after replacing", sTmp);
[pool drain]; return 0;
}
4.2 集合
Cocoa提供了许多集合类如NSArray和NSDictionary,它们的实例是为了存储其它对
象而存在的,上面列举出来的两种集合都是不可变的,它们所存储的对象都是在初始化的时候设定的。别着急,Cocoa是强大的,它还提供了NSMutableArray和NSMutableDictionary来满足我们的需要,同时,这种命名方式同样适用于其它的一些类似的类,例如NSString,也有NSMutableString与之对应。
使用集合的时候要注意的是,首先,它只能存储ObjC对象,而不能存储C语言中的基本数据类型,如float、int、enum等,或者NSArray中的随机指针。同时你也不能在其中存储nil(对象的零值或NULL值),因为nil在集合中表示结束的标志,下面我们主要针对这两个问题来介绍避开它们的方法。
4.2.1 装箱和拆箱学习过Java的人对这个概念肯定不陌生,Cocoa提供了NSNumber用来给我们包装基本
数据类型,使之成为一个对象,然后来使用集合。你可以使用下列类方法来创建新的NSNumber对象:
+ (NSNumber *)numberWithChar:(char)value; + (NSNumber *)numberWithUnsignedChar:(unsigned char)value; + (NSNumber *)numberWithShort:(short)value; + (NSNumber *)numberWithUnsignedShort:(unsigned short)value;
+ (NSNumber *)numberWithInt:(int)value; + (NSNumber *)numberWithUnsignedInt:(unsigned int)value; + (NSNumber *)numberWithLong:(long)value; + (NSNumber *)numberWithUnsignedLong:(unsigned long)value; + (NSNumber *)numberWithLongLong:(long long)value; + (NSNumber *)numberWithUnsignedLongLong:(unsigned long long)value; + (NSNumber *)numberWithFloat:(float)value; + (NSNumber *)numberWithDouble:(double)value; + (NSNumber *)numberWithBool:(BOOL)value;
只要将一个基本类型数据封装在到NSNumber中后,就可以通过下面的实例方法重新获得它:
- (char)charValue; - (unsigned char)unsignedCharValue; - (short)shortValue; - (unsigned short)unsignedShortValue; - (int)intValue; - (unsigned int)unsignedIntValue; - (long)longValue; - (unsigned long)unsignedLongValue; - (long long)longLongValue; - (unsigned long long)unsignedLongLongValue; - (float)floatValue; - (double)doubleValue; - (BOOL)boolValue;
4.2.2 NSNull
对于前面我们提到的集合中不能存储nil这个问题,试想,在面对实际需求的时候,肯定存在需要表示“这里什么都没有”的情况,例如,某人的电话号码。此时,你就可以使用
NSNull这个类来帮助你完成这个任务,该类仅提供一个方法:+ (NSNull *)null;
该方法总是返回一样的数值,因此尽管它使一个对象,但是你依然可以使用==运算符来与之作比较。
4.3 iPhone界面相关4.3.1 隐藏状态栏
4.3.2 应用程序中关闭自动屏保
4.3.3 设置应用程序图标数字
@property(nonatomic) NSInteger applicationIconBadgeNumber4.3.4 设置状态栏上网络连接标志是否为活动状态
4.3.5 退出应用程序
- (void)setStatusBarHidden:(BOOL)hidden /* YES为隐藏 NO为显示 */animated:(BOOL)animatedParameters
/* YES关闭 NO打开 */
@property(nonatomic, getter=isIdleTimerDisabled) BOOL idleTimerDisabled
/* YES可见 NO不可见 */
@property(nonatomic, getter=isNetworkActivityIndicatorVisible) BOOLnetworkActivityIndicatorVisible
/* 退出应用程序之前一定要释放资源,保存数据 */if ([[UIApplication sharedApplication]
respondsToSelector:@selector(terminate)]) {
[[UIApplication sharedApplication] performSelector:@selector(terminate)];
}
4.3.6 捕获主界面按钮事件(即退出应用程序)在每个工程的ApplicationDelegate文件中实现如下方法:
- (void)applicationWillTerminate:(UIApplication *)application在该文件的头文件声明中可以找到<UIApplicationDelegate>字样,此声明即表示了
对该应用程序的托管。
4.3.7 制作视图动画
4.4 调试相关我们写好的代码在模拟器上调试时,可能会经常看到IDE下面出现一些异常信息,这里分
享经验如下:
4.4.1 “EXC_BAD_ACCESS”异常该异常属于内存泄露问题。如果是标准C中空指针的问题,则IDE生成的调试信息会自动
帮你指向该位置,如果IDE没有指向,则检查自上一次正确运行以来添加或修改的代码中release、alloc、赋值、数组的初始化等容易引起内存访问错误的位置。
这里值得注意的是,在有断点的情况下,如果出现异常,则IDE中指向的位置并不一定是程序挂死的位置。
4.4.2 “EXC_BAD_INSTRUCTION”异常
出现这种情况的话问题比较难定位,因为导致这个问题的情况一般都很隐蔽。首先,资源文件(*.nib)中控件与类中输出口的关联问题,例如view有没有关联,控件是否关联了不存在的输出口,代码生成的按钮是否添加了一个不存在的触发方法等。 然后,在开发中我还发现,当使用TabBar作为视图控制器的时候,View所对应的TabBar上的按钮的File Owner也要与控制器类相对应。此外,在使用NSArray方法的indexOfObject的方法时,如果该Array中存在两个对象引用了同一块内存,那此时也会出现该异常,使用该方法时一定要小心。
4.4.3 模拟器无法装载应用程序造成这个异常信息的原因还不太清楚,但是,通过模拟器提供的”还原设置”选项可以解
决该问题。
4.4.4 模拟器正在运行,无法装载造成该问题是因为你的另外一个工程正在使用模拟器运行应用程序,请先退出那个应用程
序再进行装载即可。
4.4.5 模拟器装载应用程序很慢该问题多半是因为模拟器沙盒目录下的大文件比较多,例如下载的媒体文件等,在装载过
程中模拟器会读取该目录下的文件,因此会很慢,此时只需要将那些文件删除或者移动到其他位置就可以了。至于模拟器沙盒的位置,在~/Library/Application Support/iPhoneSimulator/User/Applications中,里面会发现很多随机数命名的文件夹,里面的某个就是你的应用程序目录,记住,那个随机名字每次都不一样。
[UIView beginAnimations:nil context:NULL];[UIView setAnimationDuration:0.3]; // 单位:秒// 设置视图要变化成的样子,缩放、移动等
// view.transform = ...;
[UIView commitAnimations];
5. 高级应用5.1 获取网络状态
#import <SystemConfiguration/SystemConfiguration.h> #import <sys/socket.h> #include <sys/types.h> #include <netinet/in.h>
#include <arpa/inet.h>/**********************************************************************
函数名称 : getNetworkState
函数描述 : 获取网络状态,可根据实际情况进行扩充,此处仅判断网络是否可用
: N/A: N/A
输入参数输出参数返回值
: -1:错误(网络状态无法判断) 0:网络中断 1:网络正常**********************************************************************/+ (int)getNetworkState
{
struct sockaddr_in zeroAddress; bzero(&zeroAddress, sizeof(zeroAddress)); zeroAddress.sin_len = sizeof(zeroAddress); zeroAddress.sin_family = AF_INET;
// 以下objc相关函数、类型需要添加System Configuration 框架// 用0.0.0.0来判断本机网络状态
SCNetworkReachabilityRef defaultRouteReachability =
SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr*)&zeroAddress);
SCNetworkReachabilityFlags flags; BOOL didRetrieveFlags
= SCNetworkReachabilityGetFlags(defaultRouteReachability,&flags); CFRelease(defaultRouteReachability);
if (!didRetrieveFlags) {
return -1;}
BOOL isReachable = flags & kSCNetworkFlagsReachable; BOOL needsConnection = flags & kSCNetworkFlagsConnectionRequired;
return (isReachable && !needsConnection) ? 1 : 0; }
5.2 获取当前设备可用内存(单位:MB)
#include <sys/sysctl.h> #include <mach/mach.h>
- (double)availableMemory { vm_statistics_data_t vmStats; mach_msg_type_number_t infoCount = HOST_VM_INFO_COUNT; kern_return_t kernReturn
= host_statistics(mach_host_self(), HOST_VM_INFO,
(host_info_t)&vmStats, &infoCount);
if(kernReturn != KERN_SUCCESS) { return NSNotFound;
}
return ((vm_page_size * vmStats.free_count) / 1024.0) / 1024.0; }
5.3 获取当前任务所占用的内存(单位:MB)
#include <sys/sysctl.h> #include <mach/mach.h>
- (double)usedMemory {
task_basic_info_data_t taskInfo; mach_msg_type_number_t infoCount = TASK_BASIC_INFO_COUNT; kern_return_t kernReturn = task_info(mach_task_self(),
TASK_BASIC_INFO, (task_info_t)&taskInfo, &infoCount); if(kernReturn != KERN_SUCCESS) {
return NSNotFound; }
return taskInfo.resident_size / 1024.0 / 1024.0; }