//
// @property 、屬性、修飾符
// Created by mac on 2016/10/30.
// Copyright ? 2016年 Rock. All rights reserved.
//
1> @property是什么?
@property 是聲明屬性的語法,它可以快速的為實例變量創建存取器,并允許我們通過點語法使用存取器。
@property 是一個屬性訪問聲明,擴號內支持以下幾個屬性(下面有講解)
@property 它提供成員變量的訪問方法的聲明、也控制成員變量的訪問權限、控制多線程時成員變量的訪問環境。
@property 可以在interface,在協議protocol. 和類別category中使用。
@property (attribute1 , attribute2, ...) 是property的官方表達方式
@property 功能:讓編譯器自動編寫一對與數據成員同名的方法聲明來省去讀寫方法的聲明。
@property 是Objective-C語言關鍵詞,與@synthesize (?s?nθ??sa?z 合成)配對使用。xcode4.5以及以后的版本,@synthesize可以省略。@synthesize 合成訪問器方法,實現@property所聲明的方法的定義。其實說直白就像是:@property聲明了一些成員變量的訪問方法 ,@synthesize則定義了由@property聲明的方法。他們之前的對應關系是 @property 聲明方法,頭文件中申明的方法。 @synthesize定義方法在.m中 Cpp文件中定義的方法
《存取器(accessory): 用于獲取和設置實例變量的方法。 用于獲取實例變量值的存取器是 getter, 用于設置實例變量值的存取器的存取器是 setter 》
聲明property的語法為:@property (參數1,參數2) 類型 名字;
如:@property(nonatomic,retain) UIWindow *window;
@property聲明的是屬性,而類后面加{}聲明的是成員變量,屬性跟成員變量是一回事,
只是 @property將我們的成員變量公開出去讓外部類可以調用,
不允許外部訪問的統統寫在{}內作為成員變量供自己內部使用。
//-----------------------------------------------------
為了跟好的理解@property 來了解一下存取器:
2> 創建存取器 我們先看兩段代碼:
// Car.h
#import <Foundation/Foundation.h>
@interface Car : NSObject
{
// 實例變量
NSString *carName;
NSString *carType;
}
// setter
- (void)setCarName:(NSString *)newCarName;
// getter
- (NSString *)carName;
// setter
- (void)setCarType:(NSString *)newCarType;
// getter
- (NSString *)carType;
@end
//-----------------------------------------------------
上面的代碼中 carName 和 carType 就是 Car 的實例變量,并且可以看到 分別對這兩個實例變量聲明了get/set 法,即存取器。
.m中實現
#import "Car.h"
@implementation Car
// setter
- (void)setCarName:(NSString *)newCarName
{
carName = newCarName;
}
// getter
- (NSString *)carName
{
return carName;
}
// setter
- (void)setCarType:(NSString *)newCarType
{
carType = newCarType;
}
// getter
- (NSString *)carType
{
return carType;
}
@end
、
上面代碼是對實例變量存取器的實現。我們可以看到,存取器就是對實例變量進行賦值和取值。按照約定賦值 法以set開頭,取值方法以實例變量名命名。 我們看看如何使用:
// main.m
#import "Car.h"
int main(int argc, char * argv[])
{
@autoreleasepool {
Car *car = [[Car alloc] init];
[car setCarName:@"Jeep Cherokee"];
[car setCarType:@"SUV"];
NSLog(@"The car name is %@ and the type is %@",[car carName],[car carType]);
}
return 0;
}
上面的代碼中我們注意到,對象 Car 使用了消息語法,也就是使用括號的setter語法給存取器發送消息。
返回結果為:The car name is Jeep Cherokee and the type is SUV
//----------------------------------------------------------------------
2.1 使 @property創建存取器
// Car.h
#import <Foundation/Foundation.h>
@interface Car : NSObject
{
// 實例變量
NSString *carName;
NSString *carType;
}
@property(nonatomic,strong) NSString *carName;
@property(nonatomic,strong) NSString *carType;
@end
上面代碼中,我們使 @property 聲明兩個屬性,名稱與實例變量名稱相同
(讓我們先忽略 nonatomic 和 strong )。
// Car.m
#import "Car.h"
@implementation Car
@synthesize carName;
@synthesize carType;
@end
在.m 文件中我們使 @synthesize 自動生成這兩個實例變量的存取器,
并且隱藏了存取器,雖然我們看不到存取器,但它們確實是存在的。
// main.m
int main(int argc, char * argv[])
{
@autoreleasepool {
Car *car = [[Car alloc] init];
car.carName = @"Jeep Compass";
car.carType = @"SUV";
NSLog(@"The car name is %@ and the type is %@",
car.carName,car.carType);
}
return 0;
}
在上面的代碼中我們可以注意到, Car 對象使用點語法給存取器發送消息,
并且get與set的語法是相同的,并且點語法可以根據語境判斷我們是要賦 值還是取值。
當然我們也依然可以使用消息語法來使用:
// main.m
int main(int argc, char * argv[])
{
@autoreleasepool {
Car *car = [[Car alloc] init];
// 點語法
// car.carName = @"Jeep Compass";
// car.carType = @"SUV";
//NSLog(@"The car name is %@ and the type is %@",car.carName,car.carType);
// 消息語法
[car setCarName:@"Jeep Compass"];
[car setCarType:@"SUV"];
NSLog(@"The car name is %@ and the type is %@",
[car carName],[car carType]);
}
return 0;
}
上面兩段代碼的執 結果都是:
The car name is Jeep Compass and the type is SUV
總結: @property 等同于在.h 件中聲明實例變量的get/set方法,
@synthesize 等同于在.m 件中實現實例變量的get/set方法。
使@property 和 @synthesize 創建存取器 聲明兩個存取器方法
( getter 和 setter )更簡單。 且我們在使 屬性時可以使用點語法賦值或取值,
語法更簡單,更符合面向對象編程。
//---------------------------------------------------------------------
2.2 不必單獨聲明實例變量
如果使用@Property ,就不必單獨聲明實例變量了。
因為在沒有顯式提供實例變量聲明的前提下,
系統會自動幫你完成實例變量。我們通過以下代碼來說明:
// Car.h
#import <Foundation/Foundation.h>
@interface Car : NSObject
@property(nonatomic,strong) NSString *carName;
@property(nonatomic,strong) NSString *carType;
- (NSString *)carInfo;
@end
在.h 件中我們并沒有聲明實例變量,只是聲明了 carName和 carType
兩個屬性,以及 一個 carInfo方法,返回值為 NSString * 。
// Car.m
#import "Car.h"
@implementation Car
- (NSString *)carInfo
{
return [NSString stringWithFormat:@"The car name is %@and the type is%@",_carName,_carType];
}
@end
在.m 件中我們可以注意到,在 carInfo方法中我們使用了 _carName 和 _carType 實例變量,
這就是當我們沒有顯式聲明實例變量時,系統為我們自動生成的。命名規則是以 _ 為前綴,加上屬性名,
即 _propertyName 。
其實在.m 件中實際是存在 @synthesize 聲明語句的,只是系統將其隱藏 了:
@synthesize carName = _carName;
@synthesize carType = _carType;
那么如果我們不喜歡默認的實例變量命名方法,
或者我們希望使用更有語義的名稱,應該怎么做呢。其實很簡單:
// Car.m
#import "Car.h"
@implementation Car
@synthesize carName = i_am_car_name;
@synthesize carType = i_am_car_type;
- (NSString *)carInfo
{
return [NSString stringWithFormat:@"The car name is %@and the type is %@",
i_am_car_name,i_am_car_type];
}
@end
//----------------------------------------------------
2.3 再看一些介紹:關于類Class中的屬性property
在iOS第一版中,我們為輸出口同時聲明了屬性和底層實例變量,
那時屬性是OC語言的一個新的機制,
并且要求你必須聲明與之對應的實例變量。 例如:
@interface MyViewController :UIViewController
{
UIButton *myButton;
}
@property (nonatomic, retain) UIButton *myButton;
@end
蘋果將默認編譯器從GCC轉換為LLVM(low level virtual machine),
從此不再需要為屬性聲明實例變量了。
如果LLVM發現一個沒有匹配實例變量的屬性,
它將自動創建一個以下劃線開頭的實例變量。
因此,在LLVM這個版本中,我們不再為輸出口聲明實例變量。
例如:MyViewController.h文件
@interface MyViewController :UIViewController
@property (nonatomic, retain) UIButton *myButton;
@end
```
在MyViewController.m文件中,編譯器也會自動的生成一個實例變量_myButton。
那么在.m文件中可以直接的使用_myButton實例變量,也可以通過屬性self.myButton.都是一樣的。
注意這里的self.myButton其實是調用的myButton屬性的getter/setter方法。
這與C++中點的使用是有區別的,C++中的點可以直接訪問成員變量(也就是實例變量)。
例如在OC中有如下代碼
.h文件:
@interface MyViewController :UIViewController
{
NSString *name;
}
@end
.m文件中,self.name 這樣的表達式是錯誤的。
xcode會提示你使用->,改成self->name就可以了。因為OC中點表達式是表示調用方法,而上面的代碼中沒有name這個方法。
OC語法關于點表達式的說明: {"點表達式(.)看起來與C語言中的結構體訪問以及java語言匯總的對象訪問有點類似,其實這是OC的設計人員有意為之。如果點表達式出現在等號 = 左邊,該屬性名稱的setter方法將被調用。如果點表達式出現在右邊,該屬性名稱的getter方法將被調用。" } 所以在OC中點表達式其實就是調用對象的setter和getter方法的一種快捷方式, 例如:People.age = 18; 完全等價于 [People.age setAge:18];
```
以前的用法,聲明屬性跟與之對應的實例變量:
@interface MyViewController :UIViewControlle
{
UIButton *myButton;
}
@property (nonatomic, retain) UIButton *myButton;
@end
這種方法基本上使用最多,現在大部分也是在使用,因為很多開源的代碼都是這種方式。
但是ios5更新之后,蘋果是建議以以下的方式來使用:
@interface MyViewController :UIViewController
@property (nonatomic, retain) UIButton *myButton;
@end
因為編譯器會自動為你生成以下劃線開頭的實例變量_myButton,不需要自己手動再去寫實例變量。而且也不需要在.m文件中寫@synthesize myButton;也會自動為你生成setter,getter方法。@synthesize的作用就是讓編譯器為你自動生成setter與getter方法。 @synthesize 還有一個作用,可以指定與屬性對應的實例變量,例如@synthesize myButton = xxx;那么self.myButton其實是操作的實例變量xxx,而不是_myButton了。
在實際的項目中,我們一般這么寫.m文件
@synthesize myButton;
這樣寫了之后,那么編譯器會自動生成myButton的實例變量,以及相應的getter和setter方法。注意:_myButton這個實例變量是不存在的,因為自動生成的實例變量為myButton而不是_myButton,所以現在@synthesize的作用就相當于指定實例變量;
如果.m文件中寫了@synthesize myButton;那么生成的實例變量就是myButton;如果沒寫@synthesize myButton;那么生成的實例變量就是_myButton。所以跟以前的用法還是有點細微的區別。
通過上述代碼可以看到,我們只需要通過 @synthesize 來聲明我們希望的實例變量名。
總結:如果我們希望使用默認的實例變量命名顯式,那么我們在.m 件中 就不需要使 @synthesize 聲明,
系統會幫我們自動完成。如果我們希望自己命名實例變量命,那么我們就使 @synthesize 顯式聲明我們希望的實例變量名。
// @property 屬性描述 只要屬性描述過之后 就可以用對象名打點調用此屬性 例如:
@property NSString *name; //相當于
1 . 聲明了一個屬性 叫做 NSString *_name;
2. 聲明并實現了一個set方法
- (void)setName:(NSString *)name;
3. 聲明并實現了 一個get方法
- (NSString *)name;
*****************************3、@property的特性*********************
@property 還有一些關鍵字,它們都是有特殊作用的,
如上述代碼中的 nonatomic,strong :
@property(nonatomic,strong) NSString *carName;
主要屬性分為3類:
分別是:原子性,存取器控制,內存管理。
原子性 (Atomicity) 包含:nonatomic
讀寫屬性(Writability,有時也可以稱為存取器控制) 包含:readwrite / readonly
內存管理(有時也可以稱為 setter語義,Setter Semantics)包含:assign / retain / copy
// ***********各控制權限的關鍵字意義如下:*****************
1、{
存取器控制/可讀性: readonly 、readwrite
readwrite 、readonly:決定是否生成set訪問器,
readwrite 是默認屬性,是可讀可寫特性、表明該屬性同時擁有 setter 和 getter方法的聲明與實現 。
readonly 是只讀特性 只會生成getter方法 不會生成setter方法 ;不希望屬性在類外改變,注意 :readonly關鍵字代表setter不會被生成, 所以它不可以和 copy/retain/assign組合使用。
@property(readwrite,....) valueType value;這個屬性是變量的默認屬性,就是如果你(readwrite and readonly都沒有使用,那么你的變量就是readwrite屬性),通過加入readwrite屬性你的變量就會有get方法,和set方法。
@property(readonly,...) valueType value;這個屬性變量就是表明變量只有可讀方法,也就是說,你只能使用它的get方法。
有時候為了語意更明確可能需要定義訪問器的名字 修改方法名稱的關鍵字 :
getter=getterName,setter=setterName,設置setter與getter的方法名 eg:
@property (nonatomic, setter = mySetter:,getter = myGetter) NSString *name;
最常用的是BOOL類型,一般情況下,BOOL類型的屬性的getter方法都是以is開頭的。
如標識View是否隱藏的屬性hidden。可以這樣聲明:
@property (nonatomic,getter = isHidden ) BOOL hidden;
setter表示修改生成的setter方法的名稱,不過一般不修改。
從OC 2.0開始,我們能讓系統自動生成設置變量值的方法或獲取變量值的方法,
即系統會自動為我們生成setter/getter方法。
注意:默認的property行為有:atomic,assign,readwrite。
如果這樣使用:@property BOOL _flag;//代表這_flag有著atomic,assign,readwrite三種行為。
所以我們一定要提防這種默認行為潛在的危險。
如:@property NSMutableArray *photoViews;//此時會有警告出現,因為NSMutableArray是一種obj類型,并且是 NSArray類型,根據前面的分析,最好采用retain。所以默認的assign會帶來警告提示。
}
2、{
內存管理:
@property 有顯式的內存管理策略。
這使得我們只需要看一眼 @property 聲明就明白它會怎樣對待傳遞的值。
************************************************
assign (默認): assign 用于值類型,
如 int 、 float 、 double 和 NSInteger , CGFloat 等表的單純的復制。
還包括不存在所有權關系的對象, 如常用的delegate。
@property(nonatomic) int running;
@property(nonatomic,assign) int running;
以上兩段代碼是相同的。
在 setter 方法中,采用直接賦值來實現設值操作:
-(void)setRunning:(int)newRunning{
_running = newRunning;
}
retian :在 setter 方法中,需要對傳的對象進行引用計數加1的操作。 簡單來說,就是對傳的對象擁有所有權,只要對該對象擁有所有權,該對象就不會被釋放。如下代碼所 :
-(void)setName:(NSString*)_name{
//先判斷是否與舊對象一致,如果不一致 進行賦值。
//因為如果是一個對象的話,進行if內的代碼會造成一個極端的情況:
當此name的retain為1時,使用此次的set操作讓實例name提前釋放,而達不到賦值的。
if ( name != _name){
[name release];
name = [_name retain];
}
}
strong : strong 是在IOS引入ARC的時候引用的關鍵字,是retain的一個可選的替代。
表示實例變量對傳入的對象要有所有權關系,即強引用。strong跟retain的意思相同并產生相同的代碼,但是語意上更好更能體現對象的關系。
weak :在 setter 方法中,需要對傳入的對象不進行引用計數加1的操作。 簡單來說,就是對傳入的對象沒有所有權,當該對象引用計數為0時,即該對象被釋放后,weak 聲明的實例變量指向nil ,即實例變量的值為 0。
注: weak 關鍵字是IOS5引進的,IOS5之前是不能使用該關鍵字的。
delegate 和 IBOutlet一般用 weak 來聲明。
copy :與 strong 類似,但區別在于實例變量是對傳對象的副本擁有有所有權,
在setter方法中,首先復制傳入的參數,然后將原來的舊值release,新的數值復制上去。
-(void)setName:(NSString*)newName{
if(newName!=name){
[name release];
name=[newName copy];
}
}
注意:Foundation中可復制的對象,當我們copy的是一個不可變的對象的時候,它的作用相當與retain(cocoa做的內存優化)比如我們對NSString進行copy,copy得到的地址是不變的。
下面四段代碼中,第一段顯示的結果不會copy,而是retain.
NSString *sstr = [NSString stringWithFormat:@"dfa"];
NSLog(@"strassign:%p, count:%ld", sstr , [sstr retainCount]);
NSString *sstr2 = [sstr copy];
NSLog(@"strassign:%p, count:%ld", sstr2 , [sstr2 retainCount]);
NSString *sstr = [NSString stringWithFormat:@"dfa"];
NSLog(@"strassign:%p, count:%ld", sstr , [sstr retainCount]);
NSString *sstr2 = [sstr mutableCopy];
NSLog(@"strassign:%p, count:%ld", sstr2 , [sstr2 retainCount]);
NSMutableString *str = [NSMutableString stringWithFormat:@"dfa"];
NSLog(@"strassign:%p, count:%ld", str , [str retainCount]);
NSMutableString *str2 = [str copy];
NSLog(@"strassign:%p, count:%ld", str2 , [str2 retainCount]);
NSMutableString *str = [NSMutableString stringWithFormat:@"dfa"];
NSLog(@"strassign:%p, count:%ld", str , [str retainCount]);
NSMutableString *str2 = [str mutableCopy];
NSLog(@"strassign:%p, count:%ld", str2 , [str2 retainCount]);
*******************************************************************
//================我是分割線===============新的剖析================
assign、 retain 、copy: 這些屬性用于指定set訪問器的語義,
也就是說,這些屬性決定了以何種方式對數據成員賦予新值。
assign:默認類型,setter方法直接賦值,不進行任何retain操作,
為了解決原類型與環循引用問題
retain:setter方法對參數進行release舊值,再retain新值。所有實現都是這個順序
copy: setter方法進行Copy操作,與retain處理流程一樣,先舊值release,
再Copy出新的對象,retainCount為1。這是為了減少對上下文的依賴而引入的機制。
copy是在你不希望a和b共享一塊內存時會使用到。a和b各自有自己的內存
(通過這些我們可以看出這些屬性和setter方法有關系,
所以面試的或者筆試題會有讓寫出一個assign、retain、copy方法)
//------------------------------------------------------
面試題中會有:試寫出 @property中帶retain和assign關鍵字,
通過@synthesize自動生成的的合成存取方法(set、get方法)的實現代碼:
getter分析:
@property (nonatomic, retain) test*aTest;
@property (nonatomic, copy) test*aTest;
等效代碼:
-(void)aTest {
return aTest;
}
@property (retain) test* aTest;
@property (copy) test* aTest;
等效代碼:
-(void)aTest
{
[aTest retain];
return [aTest autorelease];
}
======== 貌似我是分割線 =====
setter分析:
@property (nonatomic, retain) test*aTest;
@property (retain) test* aTest;
等效于:
-(void)setaTest:(test *)newaTest {
if (aTest !=newaTest) {
[aTest release];
aTest = [newaTest retain];
}
}
======== 貌似我是分割線 =====
@property (nonatomic, copy) test*aTest;
@property (copy) test* aTest;
等效于:
-(void)setaTest:(test *)newaTest {
if (aTest != newaTest){
[aTest release];
aTest = [newaTest copy];
}
}
*******************************************************************
//------------------------第二輪解析---------------------------------
**************************** assign *******************************
1、{
assign 普通、直接賦值,索引計數不改變,一般常用于簡單數據類型,
如NSInteger,double,bool,CGFloat,C數據類型(int, float,double, char, 等等)常見委托設計模式,以此來防止循環引用。assign是賦值特性,僅設置變量時,setter方法將傳入參數賦值給實例變量;
(補充:
assign與weak,它們都是弱引用聲明類型,最大的區別在那呢?
如果用weak聲明的變量在棧中就會自動清空,賦值為nil。
如果用assign聲明的變量在棧中可能不會自動賦值為nil,就會造成野指針錯誤!
二、實例
他們常用在基本類型屬性,比如BOOL,int等,還有就是delegate。
在使用delegate時,需要注意,非ARC時是使用assign,但到了ARC時代,都建議使用weak,這樣更安全。
```
不管是在非ARC還是ARC,使用assign時,都需要注意釋放,
@property (nonatomic , assign) id <MyDelegate> delegate;
如果你寫的library比較早,那時還是非ARC的,你的delegate設成assign property
這樣是為了不造成circular reference
當別人使用你的library的時候,記得在你自己dealloc的時候,把delegate設成nil,以避免crash的事情發生。
這在非ARC模式下是比較自然的,都會這么去做,
當myViewController 的 ratain count 變成0 ,則會dealloc
同時在dealloc 中,也是一并把myClass release,則myClass 也跟著被release.
//MRC
- (void) dealloc {
myClass.delegate = nil ;
[myClass release];
[super dealloc];
}
但在ARC模式下,使用方就不會有dealloc處理的習慣了
- (void) dealloc {
//myClass.delegate = nil ;
[myClass setDelegate:nil];
}
如果在ARC下,沒有做這個邏輯的話,當頁面銷毀的時候,
很可能出現delegate對象無效,導致程序crash.
所以,在我們使用網上下載的開源庫或者時間比較久的代碼時,
記得檢查delegate的修飾符,如果是assign的需要及時修改為weak。
@property (nonatomic , weak) id <MyDelegate> delegate;
###為什么基本數據類型用assgin?
iOS中的垃圾處理機制是根據一個對象的索引數來處理的,
為0的時候表示沒有地方使用該對象,則該對象將被清除,
而基本數據類型不屬于對象,它的創建和使用都是在內存中,
超出對應方法體即被清除,所以不需要使用垃圾處理機制,
無需記錄索引值,所以使用assgin
循環引用: 所有的引用計數系統,都存在循環應用的問題。
循環引用:指針的是兩個對象中,你中有我,我中有你。
跟java中的一對一很相似。
至于產生內存泄露的原因:主要是相互之間強指針指著對方,
感覺跟java里面誰來hibernate設置誰來管理對方。
(在這里我們引入了強指針與弱指針在ARC中會提到,這里不做解釋。)
解決循環引用的方式:讓其中一方設置為assign。
多個對象之間不要封閉環,如果出現封閉的環,那么環中所有的對象將得不到釋放。
解決的方法,讓其中一端為虛線。
例如下面的引用關系:
·對象a創建并引用到了對象b.
·對象b創建并引用到了對象c.
·對象c創建并引用到了對象b. 這時候b和c的引用計數分別是2和1。
當a不再使用b,調用release釋放對b的所有權,因為c還引用了b,
所以b的引用計數為1,b不會被釋放。b不釋放,c的引用計數就是1,
c也不會被釋放。從此,b和c永遠留在內存中。這種情況,
必須打斷循環引用,通過其他規則來維護引用關系。
比如,我們常見的delegate往往是assign方式的屬性而不是retain方式 的屬性,
賦值不會增加引用計數,就是為了防止delegation兩端產生不必要的循環引用。
如果一個UITableViewController 對象a通過retain獲取了UITableView對象b的所有權,
這個UITableView對象b的delegate又是a, 如果這個delegate是retain方式的,
那基本上就沒有機會釋放這兩個對象了。自己在設計使用delegate模式時,
也要注意這點。因為循環引用而產生的內存泄露也是Instrument無法發現的,所以要特別小心。
###assign和weak的區別
在ARC中的assign和weak可以說非常相像,導致有很多人誤以為他們是一摸一樣的,
在任何時候都可以劃等價,但事實卻不是這樣。
有人問,id類型的delegate屬性到底是用assign還是weak?
@property (weak, nonatomic) id<AnyDelegate> delegate;
@property (assign, nonatomic) id<AnyDelegate> delegate;
大家眾說紛紜,說都可以的,說assign的,說weak的都有,下面我們來看一本書中的描述:
“The main difference between weak and assign is that the with weak,
once the object being pointed to is no longer valid,
the pointer is nilled out. Assigning the pointer the value nil avoids
many crashes as messages sent to nil are essentially no-ops”
Excerpt From: Daniel H Steinberg.
“Developing iOS 7 Apps for iPad and iPhone.”
Dim Sum Thinking, Inc, 2014. iBooks. iTunes - Books
大致的意思是說,weak比assign多了一個功能就是當屬性所指向的對象消失的時候
(也就是內存引用計數為0)會自動賦值為nil,
這樣再向weak修飾的屬性發送消息就不會導致野指針操作crash。
可能不太好理解下面我寫了一個演示程序。
OC:
// ViewController.m
// weak與assgin的區別:
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic,weak) id weakPoint;
@property (nonatomic,assign) id assignPoint;
@property (nonatomic,strong) id strongPoint;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.strongPoint = [NSDate date];
NSLog(@"strong屬性:%@",self.strongPoint);
self.weakPoint = self.strongPoint;
self.assignPoint = self.strongPoint;
self.strongPoint = nil;
NSLog(@"weak屬性:%@",self.weakPoint);
// NSLog(@"assign屬性:%@",self.assignPoint);
}
@end
當程序中的注釋被打開時,運行程序有可能會崩潰(有時候不崩潰,你可能需要多運行幾次),
這是因為當assign指針所指向的內存被釋放(釋放并不等于抹除,只是引用計數為0),
不會自動賦值nil,這樣再引用self.assignPoint就會導致野指針操作,
如果這個操作發生時內存還沒有改變內容,依舊可以輸出正確的結果,
而如果發生時內存內容被改變了,就會crash。
結論:在ARC模式下編程時,指針變量一定要用weak修飾,
只有基本數據類型和結構體需要用assgin,例如delegate,
一定要用weak修飾。
)
}
******************************* retain ********************************
#2、{
retain:指針的拷貝,retain表示持有特性,使用的是原來的內存空間。
希望獲得原對象的所有權時,對其他NSObject和其子類引用計數在原有基礎上加1。
setter方法將傳入參數先保留,再賦值,傳入參數的retaincount會+1,
此屬性只能用于Objective-C對象類型,而不能用于Core Foundation對象。
(原因很明顯,retain會增加對象的引用計數,
而基本數據類型或者Core Foundation對象都沒有引用計數)。
copy:也用于對象,對象的拷貝,copy表示賦值特性,希望獲得原對象的副本而不改變原對象內容時,
用來復制對象,新申請一塊內存空間,并把原始內容復制到那片空間。新對象的索引計數為1。
此屬性只對那些實行了NSCopying協議的對象類型有效。
很多Objective-C中的object最好使用用retain,一些特別的object(例如:string)使用copy。
一般字符串(NSString)使用copy,Foundation中的不可變對象使用copy效果相當于retain,
只是引用計數+1。setter方法將傳入對象復制一份;需要完全一份新的變量時。
}
******************************* atomic ********************************
3、{
***#對原子性的理解:#***
1、原子性就是說一個操作不可以被中途CPU暫停然后調度, 即不能被中斷, 要不就執行完, 要不就不執行. 如果一個操作是原子性的,那么在多線程環境下, 就不會出現變量被修改等奇怪的問題。原子操作就是不可再分的操作,在多線程程序中原子操作是一個非常重要的概念,它常常用來實現一些同步機制,同時也是一些常見的多線程Bug的源頭。當然,原子性的變量在執行效率上要低些。
2、關于異步與同步:并非同步就是不好,我們通常需要同時進行多個操作,這時使用異步,而對于程序來說,一般就是使用多線程,然而我們很多時候需要在多個線程間訪問共享的數據,這個時候又需要同步來保證數據的準確性或訪問的先后次序。當有多個線程需要訪問到同一個數據時,OC中,我們可以使用@synchronized(變量)來對該變量進行加鎖(加鎖的目的常常是為了同步或保證原子操作)。
3、**nonatomic:**非原子性,是直接從內存中取數值,因為它是從內存中取得數據,它并沒有一個加鎖的保護來用于CPU中的寄存器計算Value,它只是單純的從內存地址中,當前的內存存儲的數據結果來進行使用。在多線環境下可提高性能,但無法保證數據同步。
**4、atomic:**原子性,如果你沒有對對象進行一個聲明(atomic or nonatomic),那么系統會默認你選擇的是atomic。默認值是atomic,為原子操作(默認): atomic 意為操作是原子性的,意味著只有一個線程 訪問實例變量。atomic是線程安全的, 少在當前的存取器上是安全的。 它是一個默認的特性,但是很少使 ,因為比較影響效率,這跟ARM平臺和內部鎖機制有關。
** nonatomic:**非原子性操作,非原子性訪問,不加同步,多線程并發訪問會提高性能。如果不加此屬性,則默認是兩個訪問方法都為原子型事務訪問。鎖被加到所屬對象實例級(所以不加nonatomic對與多線程是安全的),nonatomic 跟 atomic 剛好相反。非原子性操作,可以被多個線程訪問。它的效率比atomic快。但不能保證在多線程環境下的安全性,在單線程和明確只有一個線程訪問的情況下廣泛使 。
atomic和nonatomic用來決定編譯器生成的getter和setter是否為原子操作。在多線程環境下,原子操作是必要的,否則有可能引起錯誤的結果。atomic表示多線程安全,一般使用nonatomic
**5、原子(atomic) 跟非原子(non-atomic)屬性有什么區別?**
1). atomic 提供多線程安全。atomic是OC使用的一種線程保護技術,基本上來講,是防止在寫未完成的時候被另外一個線程讀取,造成數據錯誤。而這種機制是耗費系統資源的,所以在iPhone這種小型設備上,如果沒有使用多線程間的通訊編程,那么nonatomic是一個非常好的選擇
2). non-atomic 在自己管理內存的環境中,解析的訪問器保留并自動釋放返回的值,如果指定了 nonatomic ,那么訪問器只是簡單地返回這個值。
}
//
********類別中的屬性property*************************
類與類別中添加的屬性要區分開來,因為類別中只能添加方法,不能添加實例變量! ! !
經常會在iOS的代碼中看到在類別中添加屬性,這種情況下,是不會自動生成實例變量的。
比如在:UINavigationController.h文件中會對UIViewController類進行擴展
@interface UIViewController (UINavigationControllerItem)
@property(nonatomic,readonly,retain) UINavigationItem *navigationItem;
@property(nonatomic) BOOL hidesBottomBarWhenPushed;
@property(nonatomic,readonly,retain) UINavigationController *navigationController;
@end
這里添加的屬性,不會自動生成實例變量,這里添加的屬性其實是添加的getter與setter方法。
注意一點,匿名類別(匿名擴展)是可以添加實例變量的,非匿名類別是不能添加實例變量的,
只能添加方法,或者屬性(其實也是方法)。
· 成員變量用于類內部,無需與外界接觸的變量。
· 根據成員變量的私有性,為了方便訪問,所以就有了屬性變量。
屬性變量的好處就是允許讓其他對象訪問到該變量。
當然,你可以設置只讀或者可寫等,設置方法也可自定義。所以,
屬性變量是用于與其他對象交互的變量。
一些建議:
1.如果只是單純的private變量,最好聲明在implementation里.
2.如果是類的public屬性,就用property寫在.h文件里
3.如果自己內部需要setter和getter來實現一些東西,就在.m文件的類目里用property來聲明
//--------------------------------------------------------
.h中的interface的大括號{}之間的實例變量,.m中可以直接使用;
.h中的property變量,.m中需要使用self.propertyVariable的方式
使用propertyVariable變量
// ***第三輪解析 在MRC中的@property ********
#1,assign :簡單賦值,不更改索引計數
假設你用alloc分配了一塊內存,并且把它的地址賦值給了指針a,
后來你希望指針b也共享這塊內存,于是你又把a賦值給(assign)了b。
此時a 和b指向同一塊內存,請問當a不再需要這塊內存,能否直接釋放它?
答案是否定的,因為a并不知道b是否還在使用這塊內存,
如果a釋放了,那么b在使用這塊內存的時候會引起程序crash掉
應用場合:對基礎數據類型 (例如NSInteger,CGFloat)
和C數據類型(int, float, double, char, 等)
適用簡單數據類型、 特例 delegate
//--------------------------------------------------------
#2,retain:與strong相對應,使用了引用計數,retain+1,release -1;
當引用 計數為0時,dealloc會被調用,內存被釋放
//-------------------------------------------------------
#3,copy:用于非共享內存時,每個指針有自己的內存空間
copy 當屬性是NSString數據類型的時候就使用copy
copy 此特質所表達的所屬關系與strong類似。然而設置方法并不保留新值,而是將其“拷貝”
copy與retain的具體區別為:
copy其實是建立了一個相同的對象,而retain只是保存其對象,并且其計數值+1。例如:
一個NSString對象,地址為0×1000,內容為@”string”
copy到另外一個NSString之后,地址為0×2000,內容相同,
新的對象retain為1,舊有對象沒有變化
retain到另外一個NSString之后,地址相同(建立一個指針,指針拷貝),內容當然相同,
但是這個新對象的retain值+1,并釋放舊的對象。
因此,retain是指針拷貝,copy是內容拷貝。
```
#4,atomic 默認屬性
A,當一個變量聲明為atomic時,意味著在多線程中只能有一個線程能對它進行訪問
B,當一個變量聲明為atomic時,該變量為線程安全型,但是會影響訪問速度,
C,當一個變量聲明為atomic時,在非ARC編譯環境下,
需要設置訪問鎖來保證對該變量進行正確的get/set
atomicity的默認值是atomic,讀取函數為原子操作。
atomic是保證讀取變量是線程安全的,即它會保證每次getter和setter的操作都會正確的執行完畢,
而不用擔心其它線程在你get的時候set,可以說保證了某種程度上的線程安全。```
# 5,nonatomic
A,當一個變量聲明為nonatomic時,意味著多個線程可以同時對其進行訪問
B,當一個變量聲明為nonatomic時,它是非線程安全型,訪問速度快;
C,當一個變量聲明為nonatomic時,當兩個不同的線程對其訪問時,容易失控。
nonatomic是不能保證線程安全的。但是nonatomic比atomic速度要快。
這也是為什么property基本上都用nonatomic了。
僅僅靠atomic來保證線程安全是不可能的,要寫出線程安全的代碼,還是需要有同步和互斥機制。
# 總結:atomic和nonatomic用來決定編譯器生成的getter和setter是否為原子操作。
在多線程環境下,原子操作是必要的,否則有可能引起錯誤的結果。
加了atomic,setter函數會變成下面這樣:
if (property != newValue) {
[property release];
property = [newValue retain];
}
# strong與weak是由ARC新引入的對象變量屬性
6,strong: ARC中默認屬性,等于MRC中的retain, 與retain相對應,“擁有關系” 為這種屬性設置新值時,
設置方法先保留新值,并釋放舊值,然后再將新值設置上去,strong是在iOS引入ARC的時候引入的關鍵字,
是retain的一個可選的替代。表示實例變量對傳入的對象要有所有權關系,即強引用。
strong跟retain的意思相同并產生相同的代碼,但是語意上更好更能體現對象的關系。
應用場景:strong屬性用于ARC中
@property (strong,nonatomic) ViewController *viewController;
7,weak: 與assign 相對應,“非擁有關系” 為這種屬性設置新值時,設置方法既不保留新值,也不釋放舊值。
此特質同assign類似,然而屬性所指的對象遭到摧毀時,屬性也會被清空(nil out),在setter方法中,
需要對傳入的對象不進行引用計數加1的操作,和assign相似。簡單來說,就是對傳入的對象沒有所有權,
當該對象引用計數為0時,即該對象被釋放后,用weak聲明的實例變量指向nil,即實例變量的值為0。
應用場景:用于IBOutlets,如,UIViewController的子類,即一般的控件。
@property (weak, nonatomic) IBOutlet UIButton *myButton;
#// 8、strong與weak的區別舉例:
前提:我們把要用strong或者weak的對象比作一只風箏,風箏想掙脫線的束縛,自由飛翔去,
如果此時有一根線,那么這只風箏就掙脫不了
過程分析:
strong屬性的變量:當我們把指向一只風箏的變量聲明為strong時,此時,你就擁有控制這只風箏的線
假如此時有五個人同時控制這只風箏(即這只風箏對象有三個strong類型的變量指向它),
那么只有一種情況,這只風箏才會掙脫掉線的束縛:這三個人都放掉手中的線,(release掉)
#weak屬性的變量:當我們把指向一只風箏的變量聲明為weak時,此時,就像站在旁邊看風箏的觀眾們一樣,
當上面的三個人還握著手中的線時,他們只能看到風箏,并不能控制它,他們能做的只能是用手指指向風箏,
并大喊,“看,那只風箏飛得真高!”,然而,當上面的三個人把手中的線都放掉時,此時,風箏飛走了,
看不見了,不管有再多的觀眾,他們再也看不到風箏了,
這個故事告訴我們一個道理:
當strong類型的指針被釋放掉之后,所有的指向同一個對象的weak指針都會被清零。
ARC引入了新的對象的新生命周期限定,即零弱引用。
如果零弱引用指向的對象被dealloc的話,零弱引用的對象會被自動設置為nil。
#(_strong強指針標識符,默認所有的指針都是強指針)
作用:只要強指針指向的對象,那么這個對象就不會被釋放。
只要沒有強指針指向的對象,那么這個對象將會被立即被釋放。
#(_weak弱指針標識符)
弱指針:不參與內存管理,對內存管理沒有影響。不會影響對象的回收。
#注意:不要使用一個弱指針指向一個剛剛創建出來的對象,
一旦這樣做,剛創建出來的對象馬上銷毀,在OC中也是自動銷毀機制。
當出現循環引用的時候就必須要讓一端使用弱指針。
###強引用與弱引用的廣義區別:
強引用也就是我們通常所講的引用,其存亡直接決定了所指對象的存亡。如果不存在指向一個對象的引用,
并且此對象不再顯示列表中,則此對象會被從內存中釋放。
弱引用除了不決定對象的存亡外,其他與強引用相同。即使一個對象被持有無數個若引用,
只要沒有強引用指向他,那麼其還是會被清除。沒辦法,還是 “強哥” 有面子。
簡單講strong 等同 retain,weak比assign多了一個功能,
當對象消失后自動把指針變成nil,好處不言而喻。
###強引用(strong)和弱引用(weak)的一個笑話(便于理解):
把對象想象成一條狗,它要跑 (be deallocated)。強指針就像一條拴在狗脖子上的狗鏈;
只要攥在手里,狗就跑不了;如果5個人攥著5條狗鏈都拴著狗 (5個強指針指向對象),
除非5條狗鏈都撒開,否則狗就跑不了。
弱指針就像是孩子指著狗喊“看!狗!”;只要狗鏈還拴著狗,孩子就能指著狗喊。
當所有狗鏈都撒開,不管有多少孩子指著狗喊,狗都跑了。當最后一個強指針不再指向對象,
對象就會被釋放,所有弱指針清零。我們什么時候使用弱指針呢?
只有當你想避免保留循環 (retain cycles,) 時,我們才使用它。
@property(strong) MyClass *myObject;
相當于@property(retain) MyClass *myObject;
@property(weak) MyOtherClass *delegate;
相當于@property(assign) MyOtherClass *delegate;
//-----------------------------------------------------------
9、,unsafe_unretauined
用在ARC編譯環境下,在此環境下,與assign相似。它只是告訴ARC如何正確地調用聲明為unsafe_unretauined變量的retain和release
但是它適用于“對象類型”(object type),該特質表達一種“非擁有關系”(“不保留”,unretained),
當目標對象遭到摧毀時,屬性值不會自動清空(“不安全”,unsafe),這一點與weak有區別
//------------------------------------------------------------
在ARC中的@property 中 strong 對應所有的普通 OC對象 copy 對應字符串 、
assign 基本數據類型 、weak 對于循環應用的時候,必須保證一段使用的是weak。
```
#自動釋放池:(自動釋放池是一個棧)
autorelease: 延長對象的釋放生命周期。 作用: 把對象放進離自己最近的那個自動釋放池中。
(它與對象在何地創建沒有關系,只要標記上就放進離自己最近的那個自動釋放池中。)
當自動釋放池銷毀的時候,它會把放在池中的所有對象進行一次release操作。
調用幾次autorelease,在自動釋放池銷毀的時候就調用幾次release操作。
在自動釋放池中,只要是使用getter方法 構造方法返回來的對象都是放在池中。
#注意:
在iOS 5(Xcode 4.2)以后加入了ARC機制,ARC(automatic Reference Counting)自動內存管理
ARC中編譯器的特性:編譯器會在適當的時候,
加入內存管理的代碼!!! 不需要再調用retain/release方法管理內存了,
但這并不是說ARC會自動回收內存,它只是自動加入了retain/release的代碼,
OC的內存管理機制依然是 !!! 計數機制 !!!。
assign生成的set方法中依然不會被自動加入retain/release代碼。
*/