目录:
(1)alloc/reatain/release/dealloc
(2)autoreleasepool/autorelease
(3)__strong/__weak/__unsafe_unretain/__autoreleasing
(4)assign/retain/copy/weak/strong
(5)assign/retain/strong等
(6)assign/retain/strong等
(7)const
(8)nil/NULL/Nil/NSNull/nan
(9)abs/fabs/fabsf等
(10)NSString格式化输出
(11)集合类
(1)alloc/reatain/release/dealloc
我们通过这个例子可以看出来,对于 MRC来说,需要手动维护对象的计数,管理对象的存在和销毁的问题.如果稍有不注意的地方,就会存在内存泄露和循环引用的问题,那么苹果为了避免这一套繁琐的开发任务,方便开发者去更好的进行开发任务,于是开发出了一套 ARC,这是我们今天重点学习的内容
在此之前,我们需要很好的了解什么引用计数:
一个对象被持有的数量,打个比方来说,有一根绳子,没有人握住的时候,引用计数是0,当1个人握住的时候 引用计数+1 ,往后每叠加一个人,引用计数变依次+1,当有一个人松手的时候引用计数-1,直到没有绳子没有人握住的时候,绳子掉下来 销毁了,这就是引用计数的概念.
所以引用计数的管理方法是
每个对象都有一个与之关联的整数,这个整数被称为引用计数,在Objective-C中,通过不同的方法可以对引用计数进行操作,具体的处理如下表:
对象操作 Objective-C方法 对应的操作结果
生成并持有对象 alloc, new, copy,mutableCopy等方法 生成对象并设置引用计数 =1
持有对象 reatain方法 使引用计数 +1
释放对象 release方法 使引用计数 -1
废弃对象 dealloc方法—系统自动调用 引用计数 =0 时调用
来讲一下 alloc/reatain/release/dealloc方法的实现
alloc
我们看一下 alloc 如何实现的
+(id)alloc
{
return [self allocWithZone:NSDefaultMallocZone()];
}
+(instancetype)allocWithZone:(struct _NSZone *)zone
{
return NSAllocateObject(self, 0, zone);
}
通过allocWithZone类方法调用 NSAllocateObject方法来开辟了一块内存空间,我们来看一下NSAllocateObject方法是如何实现的
struct obj_layout{
NSUInteger retained;
};
inline id
NSAllocateObject(Class _Nonnull aClass, NSUInteger extraBytes, NSZone * _Nullable zone)
{
int size = 计算容纳对象所需要的内存大小
id new = NSZoneMalloc(zone, size);
memset(new,0,size);
new = (id)&((struct obj_layout *)new)[1];
}
NSAllocateObject通过调用NSZoneMalloc函数分配存放对象所需要的内存控件,之后将该内存空间置0,最后返回作为对象而使用的指针
我们可以执行一下如下代码
id obj = [[NSObject alloc]init];
NSLog(@"retainCount = %lu",(unsigned long)[obj retainCount]);
执行结果为:
2017-06-28 18:15:04.009 MRCTest[12244:1459486] retainCount = 1
reatian 和 release 的方法刚刚在 MRC已经看过 我们看一下 dealloc 方法
-(void)dealloc
{
NSDeallocateObject(self);
}
inline void
NSDeallocateObject(id _Nonnull object)
{
struct obj_layout *o = ((struct obj_layout*) anObject)[-1];
free(o);
}
每个对象都具备 dealloc 方法,当一个对象的引用计数为0的时候,也就意味着没有任何地方需要该对象,系统会自动回收对该对象所占用的内存,在系统销毁对象的时候,会自动调用该对象的 dealloc 方法来执行一些回收的操作,如果此时该对象还对其他对象有引用的话,那么就需要重写 dealloc 方法来释放该对象对其他对象的引用 以确保该对象能正常释放销毁
如何重写 dealloc 方法
- (void)dealloc {
// 处理该对象的其他引用(通过release方法)
/** 回调父类的dealloc方法 */
[super dealloc];
}
对于alloc/reatain/release/dealloc可以总结如下:
- 在 Objective-C的对象中存有引用计数这一整数值
- 调用 alloc或者是 retain 方法后,引用计数值加1
- 调用 release 后,引用计数值减1
- 引用计数为0时,调用 dealloc 方法销毁此对象
苹果对这四个方法的使用
- alloc
+alloc
+allocWithZone:
class_createInstance //此方法可以通过objc4中的runtime/objc-runtime-new.mm确认
calloc // 分配内存块
- retainCount
-retainCount
__CFDoExternRefOperation // 此函数根据retain,retainCount,release操作进行分发,调用__CFBasicHashXXX方法
CFBasicHashGetCountOfKey
1
2
3
- retain
-retain
__CFDoExternRefOperation
CFBasicHashAddValue
1
2
3
- release
__CFDoExternRefOperation
CFBasicHashRemoveValue // 当此函数返回0时, -release调用dealloc方法
可以从__CFDoExternRefOperation函数以及由此函数调用的哥哥函数名来看,苹果的实现大概就是采用的引用计数表来管理引用计数的
(2)autoreleasepool/autorelease
NSAutoreleasePool是什么
官方释义:NSAutoreleasePool 是 Cocoa 用来支持引用计数内存管理机制的类, 当一个autorelease pool(自动释放池)被drain(销毁)的时候会对pool里的对象发送一条release的消息.
个人理解:NSAutoreleasePool是一个对象池,它管理着在池内的对象的引用计数以及何时销毁问题。
那么现在有朋友会说,NSAutoreleasePool离我们很远啊,从来没有使用过,是的,NSAutoreleasePool 是在 MRC时代使用的,那么 ARC是使用什么呢
@autoreleasepool {
}
PS:使用以上的代码的时候,系统自动为我们创建了一个 NSAutoreleasePool
那么来说一个离我们最近的@autoreleasepool吧,我们新建一个工程,然后可以看到如下图的 main.m 文件
main.m
打开 main.m 文件,我们可以看到如下代码
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
原来在我们工程创建的时候,系统就为我们创建好了一个@autoreleasepool。
那么来讲一下这个@autoreleasepool吧。
一个项目里面可以有多个@autoreleasepool
每一个 NSRunLoop会隐式创建一个autoreleasepool
新建一个@autoreleasepool会像堆栈一样压入@autoreleasepool组里面,新的@autoreleasepool会代替当前的@autoreleasepool成为新的当前@autoreleasepool。当每一个NSRunLoop结束的时候,会将当前的autoreleasepool进行销毁,如下的一个结构图
这里写图片描述
PS: 可以把autorelease pool理解成一个类似父类与子类的关系,main()创建了父类,每个Runloop自动生成的或者开发者自定义的autorelease pool都会成为该父类的子类。当父类被释放的时候,没有被释放的子类也会被释放,这样所有子类中的对象也会收到release消息。
我们来看看实际的一个例子
有如下的代码:
#import "MStestaaaViewController.h"
@interface MStestaaaViewController ()
@property (nonatomic ,copy) NSString *testStr;
@end
@implementation MStestaaaViewController
__weak id reference = nil;
- (void)viewDidLoad {
[super viewDidLoad];
NSString *str = [NSString stringWithFormat:@"I am a test"];
// str是一个autorelease对象,设置一个weak的引用来观察它
reference = str;
NSLog(@"viewDidLoad with testStr = %@",reference);
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(@"viewWillAppear with testStr = %@",reference);
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(@"viewDidAppear with testStr = %@",reference);
打印结果如下
2017-07-13 20:36:15.541 hi7_client[4185:603299] viewDidLoad with testStr = I am a test
2017-07-13 20:36:15.544 hi7_client[4185:603299] viewWillAppear with testStr = I am a test
2017-07-13 20:36:37.466 hi7_client[4185:603299] viewDidDisappear with testStr = I am a test
2017-07-13 20:36:37.467 hi7_client[4185:603299] dealloc
以上结果说明这三个方法都是在一个 autorelease实现的,我们也可以手动修改作用块
- (void)viewDidLoad {
[super viewDidLoad];
__autoreleasing NSString *str;
@autoreleasepool {
str = [NSString stringWithFormat:@"sunnyxx"];
}
NSLog(@"%@", str); // Console: (null)
}
关于__autoreleaseing 的解释是
__autoreleasing表示在autorelease pool中自动释放对象的引用,和MRC时代autorelease的用法相同。定义property时不能使用这个修饰符,任何一个对象的property都不应该是autorelease型的。
当我们创建一个 autorelease pool 的时候,系统是如何做的呢,系统会生成一个叫做“autorelease pool page”的东西,为我们开辟一页的虚拟内存空间,至于这个类是怎么实现的借助一下这篇文章的一个图片黑幕背后的Autorelease
这里写图片描述
我们知道内存地址的分配都是由低地址分配到高地址,最开始栈顶指针和栈底指针是一致的, 随着我们往当前的autoreleasepool里面增加元素栈顶地址也会增加,每释放一个元素,栈顶地址也会随之下降,如果是直接释放整个 autoreleasepool的话,里面的元素也会随之释放。
嵌套式的 autoleasepool 也是如此。
理解 autorelease
Autorelease实际上只是把对release的调用延迟了,对于每一个Autorelease,系统只是把该Object放入了当前的Autorelease pool中,当该pool被释放时,该pool中的所有Object会被调用Release
举个例子来说,有如下代码
-(void)viewDidLoad
{
[super viewDidLoad];
Person *p = [[Person alloc]init];
[p release];
p.name = @"I am Lili";
}
这个时候,带么执行到”p.name = @”I am Lili”;”这一句的时候就会报错,原因很简单,因为 p 已经被释放了,这个内存地址已经不存在了,而再次调用 p.name = @”I am Lili”;,就会产生野指针
在 ARC的模式下,我们不需要手动调用 release 方法,系统在编译阶段自动为我们加上了释放的代码
例如: 有如下代码
+ (instancetype)createSark {
return [self new];
}
// caller
Sark *sark = [Sark createSark];
系统在编译阶段创建的代码是这样的
+ (instancetype)createSark {
return [[self new]autorelease];
}
// caller
Sark *sark = [[Sark createSark]autorelease];
什么样的场景下用autoreleasepool?
苹果官方是这么说的
If you are writing a program that is not based on a UI framework, such as a command-line tool.
你写的程序不是基于UI framework, 例如命令行项目
If you write a loop that creates many temporary objects.
You may use an autorelease pool block inside the loop to dispose of those objects before the next iteration. Using an autorelease pool block in the loop helps to reduce the maximum memory footprint of the application.
If you spawn a secondary thread.
你写的循环创建了大量临时对象 -> 你需要在循环体内创建一个autorelease pool block并且在每次循环结束之前处理那些autoreleased对象. 在循环中使用autorelease pool block可以降低内存峰值
You must create your own autorelease pool block as soon as the thread begins executing; otherwise, your application will leak objects.
你创建了一个新线程
当线程开始执行的时候你必须立马创建一个autorelease pool block, 否则你的应用会造成内存泄露.
举个例子来说
+ (UIImage*)simpleImageWithImage:(UIImage*)image scaledToSize:(CGSize)newSize
{
// Create a graphics image context
UIGraphicsBeginImageContext(newSize);
// Tell the old image to draw in this new context, with the desired
// new size
[image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
// Get the new image from the context
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
// End the context
UIGraphicsEndImageContext();
// Return the new image.
return newImage;
}
如果循环几百次调用以上的代码,就会收到内存警告,如何优化,代码如下:
+ (UIImage*)simpleImageWithImage:(UIImage*)image scaledToSize:(CGSize)newSize
{
//http://wiresareobsolete.com/2010/08/uiimagepickercontroller/
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Create a graphics image context
UIGraphicsBeginImageContext(newSize);
// Tell the old image to draw in this new context, with the desired
// new size
[image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
// Get the new image from the context
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
// End the context
UIGraphicsEndImageContext();
[newImage retain];
[pool release];
// Return the new image.
return newImage;
}
添加上了 nsautoreleasepool 后就不会收到内存警告了 arc 模式下使用@autoreleasepool
平时使用 for 循环和 for in 循环,苹果官方还给我们提供了一种循环遍历的方法,叫做
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// 这里被一个局部@autoreleasepool包围着
}];
在内存上也进行了优化,有兴趣的同学可以试一试。
在使用的时候需要注意什么
在ARC项目中我们同样可以创建NSAutoreleasePool类对象去帮助我们更精确的管理内存问题。
NSAutoreleasePool的管理范围是在NSAutoreleasePool *pool =
[[NSAutoreleasePool alloc]init];与[pool release];之间的对象
既然ARC项目中设置了ARC,为什么还要使用@autoreleasepool?(注意a的案例解释)ARC 并不是舍弃了
@autoreleasepool,而是在编译阶段帮你插入必要的 retain/release/autorelease
的代码调用。所以,跟你想象的不一样,ARC 之下依然是延时释放的,依然是依赖于 NSAutoreleasePool,跟非 ARC
模式下手动调用那些函数本质上毫无差别,只是编译器来做会保证引用计数的正确性
NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init]; 当执行[pool
autorelease]的时候,系统会进行一次内存释放,把autorelease的对象释放掉,如果没有NSAutoreleasePool
, 那这些内存不会释放
注意,对象并不是自动被加入到当前pool中,而是需要对对象发送autorelease消息,这样,对象就被加到当前pool的管理里了。当当前pool接受到drain消息时,它就简单的对它所管理的所有对象发送release消息。
在ARC项目中.不能直接使用autorelease pools,而是使用@autoreleasepool{},
@autoreleasepool{}比直接使用NSAutoreleasePool效率高。不使用ARC的时候也可以使用(autorelease嵌套)
(3)__strong/__weak/__unsafe_unretain/__autoreleasing
前言:
说到内存管理,避免不了的就是循环应用和某个变量释放的实际,虽然在实际开发种,ARC会为我们自动的加上引用技术和减少引用技术,但是并不是万能的,百密一疏,还是会在实际开发过程中内存管理出现问题,今天我们要来看的就是__strong,__weak,这些关键词的作用和使用方法
__strong关键字与retain关似,用了它,引用计数自动+1
有如下 Demo:
id obj = [[NSObject alloc]init];
/*等价于*/
id __strong obj = [[NSObject alloc]init];
那么在内存里面是怎么执行这段代码的呢
/*编译器模拟的代码*/
id obj = objc_msgSend(NSObject,@selector(alloc));
objc_msgSend(obj,@selector(init));
objc_release(obj);
从上面的代码我们可以看出,系统两次调用objc_msgSend来创建这个对象并为之开辟内存空间,然后在变量作用于失效的时候使用objc_release来释放对象,虽然在 ARC模式下我们不能使用 release,但是系统在运行时为我们自动添加了 release。
那么我们来看看实际的例子吧
有两个变量,是用 strong 修饰的
@property (nonatomic, strong) NSString *str1;
@property (nonatomic, strong) NSString *str2;
做了如下的操作
self.str1 = @"A";
self.str2 = self.str1;
self.str1 = nil;
NSLog(@"self.str2 = %@",self.str2);
大家可以猜猜打印结果是什么
打印结果是 self.str2 = A
由于str2是strong定义的属性,所以引用计数+1,使得它们所指向的值都是@”A”, 虽然 self.str = nil 了,但是并没有使得 str2的引用计数减1导致释放,所以打印的结果为 A
__weak修饰符提供的功能如同魔法一般
若附有__weak修饰符的变量所引用的对象被废弃,则将 nil赋值给该变量
使用附有__weak修饰符的变量,即是使用注册到__autoreleasepool 中的对象
有如下代码:
id __strong obj = [[NSObject alloc]init];
id __weak obj1 = obj;
我们看看系统是如何为我们处理的
/*编译器模拟的代码*/
id obj1;
objc_initWeak(&obj1,obj);
objc_destroyWeak(&obj1);
通过objc_initWeak函数初始化附有__weak修饰符的变量,在变量作用域结束的时候用objc_destroyWeak释放该变量
我们来下一下的具体的例子
@property (nonatomic, strong) NSString *str1;
@property (nonatomic, weak) NSString *str2;
执行如下的代码
self.str1 = @"A";
self.str2 = self.str1;
self.str1 = nil;
NSLog(@"self.str2 = %@",self.str2);
结果是 self.str2 = null 为什么会是这个结果呢 分析一下,由于self.string1与self.str2指向同一地址,且str2没有retain内存地址,而 self.str1=nil释放了内存,所以string1为nil。
所以这是符合第一条规则的,因为str2 指向的地址已经被释放了,那么会将 nil 赋值给 str2
在这个过程中__weak帮我们干了什么呢
(1) 从 weak表中获取废弃对象的地址为键值的记录
(2) 将包含在记录中的所有附有__weak 修饰变量的地址,赋值为 nil
(3) 从 weak 表中删除该记录
(4) 从引用技术表中删除废弃对象的地址为键值的记录
我们来看看 weak 的另外一个特性
id __weak obj1 = obj/*obj 是 strong 的*/
NSLog(%@"obj1 = %@",obj1);
这段代码转换为系统编译器码为
id obj1;
objc_initWeak(&obj1,obj);
id temp = objc_loadWeakRetained(&obj1);
objc_autorelease(temp);
NSLog(%"%@",temp);
objc_destroyWeak(obj1);
我们可以注意到有个objc_autorelease这个函数,所以正说明了第二点:使用附有__weak修饰符的变量,即是使用注册到__autoreleasepool 中的对象,因为附有__weak的变量所使用的对象都被注册到了__autoreleasepool里面,所以在@autoreleasepool块结束之前可以放心使用,所以在实际开发过程中,如果大量使用 weak 来修饰变量的话,是会非常耗费 CPU资源的,所以当只有会可能产生循环引用的地方使用 weak 是合适的。
__unsafe_unretain顾名思义就是不安全的,其功能和 weak 差不多
有如下的代码
id __unsafe_unretain obj = [[NSObject alloc]init];
这时候编译器报出警告,该代码对应的模拟代码为
/*编译器模拟的代码*/
id obj = objc_msgSend(NSObject,@selector(alloc));
objc_msgSend(obj,@selector(init));
objc_release(obj);
objc_release函数立即释放了生成并持有的对象,这样对象的悬垂指针被赋值给了变量obj 当中
我们来看如下的代码:
@property (nonatomic, strong) NSString *str1;
@property (nonatomic,__unsafe_unretain) NSString *str2;
执行如下的代码
self.str1 = @"A";
self.str2 = self.str1;
self.str1 = nil;
NSLog(@"self.str2 = %@",self.str2);
这次根本就不用输出,在没有输出前,程序已经崩溃了,其实就是野指针造成的,为何会造成野指针呢?同于用unsafe_unretained声明的指针,由于 self.str1=nil已将内存释放掉了,但是str2并不知道已被释放了,所以是野指针。然后访问野指针的内存就造成crash. 所以尽量少用unsafe_unretained关键字。
__autoreleasing:将对象赋值给附有__autoreleasing修饰的变量等同于 ARC无效时调用对象的 autorelease 方法
有如下代码:
@autoreleasepool{
id __autoreleasing obj = [[NSObject alloc]init];
}
对应的编译器码:
/*编译器的模拟代码*/
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject,@selector(alloc));
objc_msgSend(obj,@selector(init));
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);
- (void) generateErrorInVariable:(__autoreleasing NSError **)paramError{
NSArray *objects = [[NSArray alloc] initWithObjects:@"A simple error", nil];
NSArray *keys = [[NSArray alloc] initWithObjects:NSLocalizedDescriptionKey, nil];
NSDictionary *errorDictionary = [[NSDictionary alloc] initWithObjects:objects forKeys:keys];
*paramError = [[NSError alloc] initWithDomain:@"MyApp" code:1 userInfo:errorDictionary];
}
-(void)test
{
NSError *error = nil;
[self generateErrorInVariable:&error];
NSLog(@"Error = %@", error);
}
__autoreleasing则可以使对像延迟释放。比如你想传一个未初始 化地对像引用到一个方法当中,在此方法中实始化此对像,那么这种情况将是__autoreleasing表演的时候。看个示例:
@autoreleasepool{
id __autoreleasing obj = [[NSObject alloc]init];
}
对应的编译器码:
- (void) generateErrorInVariable:(__autoreleasing NSError **)paramError{
NSArray *objects = [[NSArray alloc] initWithObjects:@"A simple error", nil];
NSArray *keys = [[NSArray alloc] initWithObjects:NSLocalizedDescriptionKey, nil];
NSDictionary *errorDictionary = [[NSDictionary alloc] initWithObjects:objects forKeys:keys];
*paramError = [[NSError alloc] initWithDomain:@"MyApp" code:1 userInfo:errorDictionary];
}
-(void)test
{
NSError *error = nil;
[self generateErrorInVariable:&error];
NSLog(@"Error = %@", error);
}
符合内存管理规则:谁分配谁释放。
小结:不管是__strong、__weak、__unsafe_unretain、__autoreleasing 这是为了让我们更好的理解内存管理,而且在必要的时候使用这些修饰符来管理我们的内存,而在实际开发种,一般很少使用到后两者,前两者在循环引用的时候可以用来解循环引用,我们在今后的学习中还会学习和使用到它们,别忘了哟~
作者:付佳
来源:CSDN
原文:https://blog.csdn.net/wanna_dance/article/details/76549432?utm_source=copy
(4)assign/retain/copy/weak/strong
我们在声明@property 属性时,总是要在括号中写上assign、retain、copy、weak、strong中的一个,很多时候,我们仅仅只是按照习惯去写经常写的那一个,但有时候看代码时又会发现别人用的不尽相同,那这些之间的区别是什么呢?
首先,上面五个并不是在一个层面上的,可以分为两部分,第一部分是assign、retain、copy,第二部分是weak、strong。
我们先说第一部分的assign、retain、copy。
assign:
assign一般用来修饰基本的数据类型,包括基础数据类型 (NSInteger,CGFloat)和C数据类型(int, float, double, char, 等等),为什么呢?assign声明的属性是不会增加引用计数的,也就是说声明的属性释放后,就没有了,即使其他对象用到了它,也无法留住它,只会crash。但是,即使被释放,指针却还在,成为了野指针,如果新的对象被分配到了这个内存地址上,又会crash,所以一般只用来声明基本的数据类型,因为它们会被分配到栈上,而栈会由系统自动处理,不会造成野指针。
retain:
与assign相对,我们要解决对象被其他对象引用后释放造成的问题,就要用retain来声明。retain声明后的对象会更改引用计数,那么每次被引用,引用计数都会+1,释放后就会-1,即使这个对象本身释放了,只要还有对象在引用它,就会持有,不会造成什么问题,只有当引用计数为0时,就被dealloc析构函数回收内存了。
copy:
最常见到copy声明的应该是NSString。copy与retain的区别在于retain的引用是拷贝指针地址,而copy是拷贝对象本身,也就是说retain是浅复制,copy是深复制,如果是浅复制,当修改对象值时,都会被修改,而深复制不会。之所以在NSString这类有可变类型的对象上使用,是因为它们有可能和对应的可变类型如NSMutableString之间进行赋值操作,为了防止内容被改变,使用copy去深复制一份。copy工作由copy方法执行,此属性只对那些实现了NSCopying协议的对象类型有效 。
以上三个可以在MRC中使用,但是weak和strong就只能在ARC中使用,也就是自动引用计数,这时就不能手动去进行retain、release等操作了,ARC会帮我们完成这些工作。
weak:
weak其实类似于assign,叫弱引用,也是不增加引用计数。一般只有在防止循环引用时使用,比如父类引用了子类,子类又去引用父类。IBOutlet、Delegate一般用的就是weak,这是因为它们会在类外部被调用,防止循环引用。
strong:
相对的,strong就类似与retain了,叫强引用,会增加引用计数,类内部使用的属性一般都是strong修饰的,现在ARC已经基本替代了MRC,所以我们最常见的就是strong了。
nonatomic:
在修饰属性时,我们往往还会加一个nonatomic,这又是什么呢?它的名字叫非原子访问。对应的有atomic,是原子性的访问。我们知道,在使用多线程时为了避免在写操作时同时进行写导致问题,经常会对要写的对象进行加锁,也就是同一时刻只允许一个线程去操作它。如果一个属性是由atomic修饰的,那么系统就会进行线程保护,防止多个写操作同时进行。这有好处,但也有坏处,那就是消耗系统资源,所以对于iPhone这种小型设备,如果不是进行多线程的写操作,就可以使用nonatomic,取消线程保护,提高性能。
(5)assign/retain/strong等
assign
此标记说明设置器直接进行赋值,这也是默认值。在使用垃圾收集的应用程序中,如果你要一个属性使用assign,且这个类符合NSCopying协议,你就要明确指出这个标记,而不是简单地使用默认值,否则的话,你将得到一个编译警告。这再次向编译器说明你确实需要赋值,即使它是可拷贝的。
assign用于非指针变量。用于 基础数据类型 (例如NSInteger)和C数据类型(int, float, double, char, 等),另外还有id
如:
@property (nonatomic, assign) int number;
@property (nonatomic, assign) id className;//id必须用assign
反正记住:前面不需要加 “*” 的就用assign吧
retain
用于指针变量。就是说你定义了一个变量,然后这个变量在程序的运行过程中会被更改,并且影响到其他方法。
一般是用于字符串( NSString,NSMutableString),数组(NSMutableArray,NSArray),字典对象,视图对象(UIView ),控制器对象(UIViewController)等
比如:
@property (nonatomic,retain) NSString * myString;
@property (nonatomic, retain) UIView * myView;
@property (nonatomic, retain) UIViewController * myViewController;
xcode 4.2不支持ARC,所以会频繁使用retain来修饰,用完释放掉,而xcode4.3支持ARC,可以使用retian,不需要手动释放内存,系统会自动为你完成,如果你在xcode4.3上面开发,retian和strong都是一样的,没区别.
指定retain会在赋值时唤醒传入值的retain消息。此属性只能用于Objective-C对象类型,而不能用于Core Foundation对象。(原因很明显,retain会增加对象的引用计数,而基本数据类型或者Core Foundation对象都没有引用计数)。
strong和weak
事实上
@property(nonatomic,strong) MyClass myObject;就是相当于@property(nonatomic,retain)MyClassmyObject;
@property(nonatomic, weak )iddelegate;就是相当于@property(nonatomic,assign )iddelegate;
现在系统自动生成的属性都是用weak来修饰的,我想应该是xcode 4.2不支持ARC,所以大家都是用retain。
现在xcode4.3支持ARC了,于是苹果建议程序员放弃retain,以后都用weak。
weak 就是相当于assign,同样可以在xcode4.3开发环境下放弃使用assign 使用weak 来代替 .
strong与weak是由ARC新引入的对象变量属性
xcode 4.2(ios sdk4.3和以下版本)和之前的版本使用的是retain和assign,是不支持ARC的。xcode 4.3(ios5和以上版本)之后就有了ARC,并且开始使用
unsafe_unretained(现在基本不用)
unsafe_unretained 就是ios5版本以下的 assign ,也就是 unsafe_unretained , weak, assign 三个都是一个样的。 因为 ios5用的是 weak ,那在ios4.3就用不了,如果你将 weak 修改为 unsafe_unretained ,那就可以用了。说到底就是iOS 5之前的系统用该属性代替 weak 来使用。
readonly
此标记说明属性是只读的,默认的标记是读写,如果你指定了只读,在@implementation中只需要一个读取器。
或者如果你使用@synthesize关键字,也是有读取器方法被解析。
而且如果你试图使用点操作符为属性赋值,你将得到一个编译错误。
readwrite
此标记说明属性会被当成读写的,这也是默认属性。设置器和读取器都需要在@implementation中实现。
如果使用@synthesize关键字,读取器和设置器都会被解析。
nonatomic
指出访问器不是原子操作,而默认地,访问器是原子操作。
这也就是说,在多线程环境下,解析的访问器提供一个对属性的安全访问,从获取器得到的返回值或者通过设置器设置的值可以一次完成,即便是别的线程也正在对其进行访问。
如果你不指定nonatomic,在自己管理内存的环境中,解析的访问器保留并自动释放返回的值,如果指定了nonatomic,那么访问器只是简单地返回这个值。
所以property的属性默认是:readwrite,assign, atomic
static
ios中static只能作用于.m文件中,在.h文件中会有警告
在函数体内 static 变量的作用范围为该函数体,不同于 auto 变量。
(1)该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;
(2)在模块内的 static 全局变量可以被模块内所用函数访问,但不能被模块外的其它函数访问;
(3)在模块内的 static 函数只能被这一模块内的其它函数调用,而且这个函数的使用范围也被限制在声明
它的模块内;
(4)在类中的 static 成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
(5)在类中的 static 成员函数属于整个类所拥有,这个函数不接收 this 指针,因而只能访问类的static 成员变量。
import,class,include
@class一般用于头文件中需要声明该类的某个实例变量的时候用到,同时也是避免头文件中互相引用的问题,在m文件中还是需要使用#import。
而#import比起#include的好处就是不会引起重复包含。
const
const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。可以减少bug的出现。
关键词正确写法:
Xcode 错误的写法: xcode
iPhone 错误的写法:IPhone
iOS 错误的写法:IOS
OC 全称: Objective-C 错误的写法:Object-c
文件后缀:
.h 头文件
.c C语言
.m C语言,OC语言
.cpp C++
.mm C++,OC语言
import关键字
'#import 和 #include 的异同
- 相同点:都是预编译指令
将其他文件导入到#import该行的功能
'#include<> , #import<> 都是导入系统文件
"#include" , " #import"" 都是导入用户自定义的文件
- 不同点:#import是#include的升级版,具有防止相同文件多次导入的功能, 以后我们是导入的指令符用#import
Foundation框架介绍:
Foundation框架,OC中最重要的一个框架,Foundation命名空间以NS开头
框架:封装了一些常用的类,函数,方法,结构体,变量等等。
自动释放池:@autoreleasepool。
自动释放池,帮助程序员管理内存空间,代码写下括号之内。
iOS4.0之前的写法
NSAutoreleasepool * pool = [NSAutoreleasepool new];
[pool drain];
NSLog的用法:
NSLog是Foundation框架提供的日志输出函数,可以输出日期,时间,对应的应用程序。
NSLog具有自动换行功能,末尾加\n无效。
@"" 是一个NSString对象的字面量变现。
字面量:@(),@[],@{}
printf的占位符在我们NSLog中一样使用
%i 修饰整形
%g 修饰浮点型,末尾0省略
%@ 修饰对象
面向对象编程:
面向过程:程序员相当于一个执行者,C语言。
面向对象:程序员相当于一个指挥者,OC,Java,C++。
打开电脑,打开浏览器,听歌
电脑 - 开机, 电脑 - 打开浏览器,浏览器 - 听歌
类和对象:
对象是对客观事物的抽象,类是对对象的抽象。类是一种抽象的数据类型,其定义为:
它们的关系是,对象是类的实例,类是对象的模板。
C语言用结构体封装数据的。
OC用的是类。属性就是成员变量,行为就是方法。
类:描述事物的名称,属性和行为。
名称:老师 (类)
属性:男,人 (属性)
行为:讲课 (方法)
Student (类)
属性:姓名,学号 (属性)
行为:学习,翘课 (方法)
OC中是如何实现一个类的
声明 和 实现。
类的声明:以@interface开头,以@end。
类的实现:以@implementation开头,以@end。
NSObject基类
NSObject的作用使我们的Person具有创建对象的能力
在大括号里声明成员变量
常见错误:
unrecognized selector sent to instance 对象没有实现该方法
命名规范
- 成员变量名称,方法名一般是小驼峰:
NSString *endDate; //截止日
//成员方法
- openTheBook {
}
- 定义的宏, 静态变量 等一般大驼峰
#define ZMJRed 0.4
static const CGFloat ZMJRed = 0.4;
成员方法:
-
无参数:方法类型符 (返回值类型) 方法名,被(-)修饰的方法,只能被对象调用
-
' : '的作用:
:是方法名的一部分
一个:对应一个形参
:前面的文字是描述参数的
描述参数可以省略,但是坚决不建议省略
不同类定义相同方法,是互不影响的。
举例:[Person new]对应哪些操作
1.为Person这个对象在堆内存中分配空间,malloc
2.初始化成员变量
3.返回一个指向该对象的指针
static
1.修饰局部变量
让局部变量只初始化一次
局部变量在程序中只有一份内存
并不会改变局部变量的作用域,仅仅是改变了局部变量的生命周期(只到程序结束,这个局部变量才会销毁)
2.修饰全局变量
全局变量的作用域仅限于当前文件
网友评论