主要內(nèi)容有三:
- copy VS strong
- newName
- __attribute__
Tip1:對(duì)于 NSString 而言,@property 中的 strong 和 copy 有什么區(qū)別 舰攒?
在項(xiàng)目中發(fā)現(xiàn)有人用 strong熬苍,有人用 copy葛假,還有混著用的蝗罗。
問之躁锁,為什么要用strong, 為什么這么寫 ?
答曰:一樣的都是拄显。
so 真的一樣嗎裁蚁?我們細(xì)細(xì)來看矢渊,這里先說有什么區(qū)別:
首先,聲明一個(gè) MLPerson:
//MLPerson.h
#import <Foundation/Foundation.h>
@interface MLPerson : NSObject
@property (nonatomic, strong) NSString *strongName;
@property (nonatomic, copy) NSString *lcopyName;
@end
//MLPerson.m
#import "MLPerson.h"
@implementation MLPerson
- (NSString *)description
{
return [NSString stringWithFormat:@"strong Name :%@ -- copy Name: %@", _strongName,_lcopyName];
}
@end
如上所示厘擂,在 MLPerson 類中添加了兩個(gè)屬性: strongName 和 lcopyName昆淡, 這兩個(gè)屬性分別用 strong 和 copy 修飾, 為了查看方便刽严,重寫了 description 方法昂灵。
看官卻道,咦舞萄?這廝為何 copy 修飾的不起一個(gè) copyName 的名字呢眨补?
欲知詳細(xì),且看 Tip2
在 main.m 中測(cè)試代碼如下:
#import <Foundation/Foundation.h>
#import "MLPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
MLPerson *jack = [MLPerson new];
NSMutableString *name = [NSMutableString stringWithString:@"Hello world"];
jack.strongName = name;
jack.copyName = name;
NSLog(@"%@",jack);
// 修改name
[name appendString:@" one"];
NSLog(@"%@",jack);
}
return 0;
}
控制輸出結(jié)果:
strong Name :Hello world -- copy Name: Hello world
strong Name :Hello world one -- copy Name: Hello world
Program ended with exit code: 0
在 jack 這個(gè)對(duì)象生成之后倒脓,分別為其兩個(gè)屬性賦值撑螺,這個(gè)值是一個(gè)可變字符串,因 NSMutableString 是 NSString 的子類崎弃,所以這么做是沒有問題的甘晤。賦值結(jié)束之后,將 name 后面追加了 " one" 這一個(gè)字符串饲做,現(xiàn)在 name 的值是: "Hello world one"线婚。 按照面向?qū)ο蠓庋b思想而言,此時(shí)此刻 jack 的這兩個(gè)屬性都不應(yīng)該改變盆均,因?yàn)閷?duì)對(duì)象的修改最好是通過 setter 方法或者公開的方法進(jìn)行塞弊。通過控制臺(tái)打印的結(jié)果來看,strong 修飾的屬性是不能滿足要求的泪姨,而copy則是可以滿足要求的游沿。
所以,在開發(fā)的過程中肮砾,為了讓類的封裝性不被破壞诀黍,針對(duì) NSString 最好使用 copy 來進(jìn)行修飾,這樣的代碼會(huì)更清晰一些唇敞,也不容出錯(cuò)蔗草。
原因解釋:
如圖 1-1 所示咒彤,當(dāng)執(zhí)行 jack.strongName = name 和 jack.lcopyName = name 的時(shí)候疆柔,實(shí)際上咒精,_strongName 指向的是 ① 的 "Hello world" 而 _lcopyName 指向的確實(shí)另一份內(nèi)容與 name 指向的位置一樣的 "Hello world", 后面代碼對(duì) name 的追加是修改的 ① 處的 "Hello world" ,結(jié)果一目了然旷档。
而對(duì)于不可變字符串而言模叙,則沒有什么區(qū)別。
Tip2: 聲明了一個(gè)屬性名字為 copyName 為什么編譯不通過鞋屈,如果就想使用這個(gè)名字該如何去做范咨?
在聲明屬性的時(shí)候,尤其是為了區(qū)分兩個(gè)屬性厂庇,經(jīng)常用寫成 newName渠啊、copyName 或者其他,但是往往 Xcode 編譯不通過权旷,并且報(bào)錯(cuò)替蛉,如圖 1-2 所示:
? Property follows Cocoa naming convention for returning 'owned' objects
在開發(fā)者文檔《Memory Management Policy》中有這么一條內(nèi)存管理策略:
You own any object you create
You create an object using a method whose name begins with "alloc", "new", "copy", or "mutableCopy".
(for example, alloc, newObject, or mutableCopy).
上面這條規(guī)則說的是在 MRR(memory retain release)內(nèi)存管理下的一條規(guī)則。
聲明屬性為 copyName 拄氯,也就是會(huì)默認(rèn)產(chǎn)生 setCopyName 和 copyName 這兩個(gè)方法 setter 和 getter 方法, 然而根據(jù)內(nèi)存規(guī)則來說躲查,通過 newXXX 方法就是持有 newXXX 方法返回的對(duì)象,getter 方法并不是用來持有對(duì)象的译柏,這樣就造成了奇異镣煮,so 編譯器直接報(bào)錯(cuò)。
解決這個(gè)問題鄙麦,最簡(jiǎn)單的方法就是改名典唇,比如:
@property (nonatomic, copy) NSString *theCopyName;
當(dāng)然有句話就:就不信邪!
如果執(zhí)意要用這個(gè)名字的話胯府,可以修改編譯器默認(rèn)為創(chuàng)建的 getter 方法的名字:
@property (nonatomic, copy, getter = theCopyName) NSString *copyName;
這樣編譯也是通過的介衔。
還有一種方式就是使用 Function attribute 來修飾 getter 方法
@property (nonatomic, copy) NSString *copyName ;
- (NSString *)copyName __attribute__((objc_method_family(none)));
上面這種寫法也是可以通過驗(yàn)證的。
當(dāng)然這里又有: __attribute__ 是什么的疑問了盟劫?且看 Tip3
Tip3: __attribute__ 是什么夜牡?
從上面可以看到,當(dāng)為 copyName 的 getter 方法添加了 attribute 后面這一段之后侣签,編譯器便不再報(bào)錯(cuò)塘装。盡管你不知道 attribute 是什么,卻仍然可以推斷出它讓編譯器忽略了對(duì)這個(gè)方法的內(nèi)存規(guī)則檢查即其為編譯器提供了上下文影所。
首先蹦肴,來看下 NSFoundation 框架中 NS_REQUIRES_SUPER 的使用:
NS_REQUIRES_SUPER 是在 NSObjCRuntime.h 中定義的預(yù)編譯指令,定義如下
#ifndef NS_REQUIRES_SUPER
#if __has_attribute(objc_requires_super)
#define NS_REQUIRES_SUPER __attribute__((objc_requires_super))
#else
#define NS_REQUIRES_SUPER
#endif
#endif
可以看到第三句中:
#define NS_REQUIRES_SUPER __attribute__((objc_requires_super))
和上面針對(duì)屬性的使用非常相似猴娩,這句話定義了之后阴幌,當(dāng)使用 NS_REQUIRES_SUPER 地方在預(yù)編譯時(shí)期就會(huì)被替換為 __attribute__((objc_requires_super)) (這一點(diǎn)在后面來進(jìn)行驗(yàn)證)
在 MLPerson 類中添加方法 - work :
//MLPerson.h
@interface MLPerson : NSObject
@property (nonatomic, strong) NSString *strongName;
@property (nonatomic, copy) NSString *copyName ;
//@property (nonatomic, copy, getter=theCopyName) NSString *copyName;
- (NSString *)copyName __attribute__((objc_method_family(none)));
- (void)work NS_REQUIRES_SUPER;
@end
//MLPerson.m
#import "MLPerson.h"
@implementation MLPerson
- (NSString *)description
{
return [NSString stringWithFormat:@"strong Name :%@ -- copy Name: %@", _strongName,_copyName];
}
- (void)work {
NSLog(@"MLPerson - work method");
}
@end
創(chuàng)建 MLStudent 類繼承自 MLPerson 類:
//MStudent.h
#import "MLPerson.h"
@interface MLStudent : MLPerson
@end
//MStudent.m
#import "MLStudent.h"
@implementation MLStudent
- (void)work {
NSLog(@"MLStudent - work method");
}
@end
在為 MLStudent 類添加 work 方法的時(shí)候勺阐,可以看到如下警告信息,如圖 1-3:
當(dāng)然如果不調(diào)用的話也是可以編譯通過的矛双,但是這里會(huì)彈出警告??讓寫此類的人知道此處應(yīng)該通過 [super work] 調(diào)用父類的方法渊抽。
- 通過觀察 * NS_REQUIRES_SUPER* 的使用,對(duì)先前 MLPerson 中 copyName 的 getter 方法修飾做出修改议忽,如下:
#if __has_attribute(objc_method_family)
#define ML_OBJC_METHOD_FAMILY_NONE __attribute__((objc_method_family(none)))
#else
#define ML_OBJC_METHOD_FAMILY_NONE
#endif
#import <Foundation/Foundation.h>
@interface MLPerson : NSObject
@property (nonatomic, strong) NSString *strongName;
@property (nonatomic, copy) NSString *copyName ;
//@property (nonatomic, copy, getter=theCopyName) NSString *copyName;
//- (NSString *)copyName __attribute__((objc_method_family(none)));
- (NSString *)copyName ML_OBJC_METHOD_FAMILY_NONE;
- (void)work NS_REQUIRES_SUPER;
@end
這里使用 ML_OBJC_METHOD_FAMILY_NONE 對(duì) __attribute__ 做了預(yù)定義處理
- 驗(yàn)證預(yù)處理階段的替換
//main.m
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import "MLPerson.h"
#import "MLStudent.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
MLPerson *jack = [MLPerson new];
NSMutableString *name = [NSMutableString stringWithString:@"Hello world"];
jack.strongName = name;
jack.copyName = name;
NSLog(@"%@",jack);
[name appendString:@" one"];
NSLog(@"%@",jack);
NSString *newName = @"one world";
jack.strongName = newName;
jack.copyName = newName;
NSLog(@"%@",jack);
newName = @"xxx";
NSLog(@"%@",jack);
NSString *str = @"Hello world";
MLStudent *liLei = [MLStudent new];
[liLei work];
}
return 0;
}
打開終端懒闷,進(jìn)入到項(xiàng)目目錄,與 main.m 在同一層級(jí):
$tree
.
├── MLPerson.h
├── MLPerson.m
├── MLStudent.h
├── MLStudent.m
└── main.m
0 directories, 5 files
$clang -E -fmodules main.m -o main # 對(duì) main.m 執(zhí)行預(yù)處理操作栈幸,輸出文件名為main
$tree
.
├── MLPerson.h
├── MLPerson.m
├── MLStudent.h
├── MLStudent.m
├── main # 目標(biāo)文件已經(jīng)生成
└── main.m
$cat main
# 1 "main.m"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 343 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "main.m" 2
@import Foundation; /* clang -E: implicit import for "/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h" */
@import AppKit; /* clang -E: implicit import for "/System/Library/Frameworks/AppKit.framework/Headers/AppKit.h" */
# 1 "./MLPerson.h" 1
# 18 "./MLPerson.h"
@import Foundation; /* clang -E: implicit import for "/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h" */
@interface MLPerson : NSObject
@property (nonatomic, strong) NSString *strongName;
@property (nonatomic, copy) NSString *copyName ;
- (NSString *)copyName __attribute__((objc_method_family(none)));
- (void)work __attribute__((objc_requires_super));
@end
# 12 "main.m" 2
# 1 "./MLStudent.h" 1
# 11 "./MLStudent.h"
@interface MLStudent : MLPerson
@end
# 13 "main.m" 2
int main(int argc, const char * argv[]) {
@autoreleasepool {
MLPerson *jack = [MLPerson new];
NSMutableString *name = [NSMutableString stringWithString:@"Hello world"];
jack.strongName = name;
jack.copyName = name;
NSLog(@"%@",jack);
[name appendString:@" one"];
NSLog(@"%@",jack);
NSString *newName = @"one world";
jack.strongName = newName;
jack.copyName = newName;
NSLog(@"%@",jack);
newName = @"xxx";
NSLog(@"%@",jack);
NSString *str = @"Hello world";
MLStudent *liLei = [MLStudent new];
[liLei work];
}
return 0;
}
從 #18 的地方截取看到
@import Foundation; /* clang -E: implicit import for "/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h" */
@interface MLPerson : NSObject
@property (nonatomic, strong) NSString *strongName;
@property (nonatomic, copy) NSString *copyName ;
- (NSString *)copyName __attribute__((objc_method_family(none)));
- (void)work __attribute__((objc_requires_super));
@end
這就是預(yù)處理之后 MLPerson 中的內(nèi)容愤估,我們可以看到注釋已經(jīng)去除,并且宏也已經(jīng)被替換速址。
上面通過 clang -E -fmodules main.m -o main 生成了預(yù)處理之后的 main 文件
當(dāng)然通過:gcc -E -Foundation main.m -o main 也是可以做到這一步的玩焰,只不過目標(biāo)文件中導(dǎo)入的 Foudation 會(huì)被展開。
至此芍锚,我們可以看到 __attribute__ 是為編譯器提供上下問的一個(gè)工具或者方式昔园,在 Cocoa 中早有使用,目前先了解到此處闹炉,后面做專門分析蒿赢。
end