《iOS Tips 一》

主要内容有三:

  1. copy VS strong
  2. newName
  3. __attribute__

Tip1:对于 NSString 而言,@property 中的 strong 和 copy 有什么区别 ?

在项目中发现有人用 strong,有人用 copy,还有混着用的。
问之,为什么要用strong, 为什么这么写 ?
答曰:一样的都是。
so 真的一样吗?我们细细来看,这里先说有什么区别:

首先,声明一个 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 类中添加了两个属性: strongNamelcopyName, 这两个属性分别用 strong 和 copy 修饰, 为了查看方便,重写了 description 方法。


看官却道,咦?这厮为何 copy 修饰的不起一个 copyName 的名字呢?
欲知详细,且看 Tip2


在 main.m 中测试代码如下:

#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;
}

控制输出结果:

strong Name :Hello world  -- copy Name: Hello world
strong Name :Hello world one  -- copy Name: Hello world
Program ended with exit code: 0

在 jack 这个对象生成之后,分别为其两个属性赋值,这个值是一个可变字符串,因 NSMutableString 是 NSString 的子类,所以这么做是没有问题的。赋值结束之后,将 name 后面追加了 " one" 这一个字符串,现在 name 的值是: "Hello world one"。 按照面向对象封装思想而言,此时此刻 jack 的这两个属性都不应该改变,因为对对象的修改最好是通过 setter 方法或者公开的方法进行。通过控制台打印的结果来看,strong 修饰的属性是不能满足要求的,而copy则是可以满足要求的。
所以,在开发的过程中,为了让类的封装性不被破坏,针对 NSString 最好使用 copy 来进行修饰,这样的代码会更清晰一些,也不容出错。

原因解释:


图 1-1

如图 1-1 所示,当执行 jack.strongName = name 和 jack.lcopyName = name 的时候,实际上,_strongName 指向的是 ① 的 "Hello world" 而 _lcopyName 指向的确实另一份内容与 name 指向的位置一样的 "Hello world", 后面代码对 name 的追加是修改的 ① 处的 "Hello world" ,结果一目了然。
而对于不可变字符串而言,则没有什么区别。


Tip2: 声明了一个属性名字为 copyName 为什么编译不通过,如果就想使用这个名字该如何去做?

在声明属性的时候,尤其是为了区分两个属性,经常用写成 newName、copyName 或者其他,但是往往 Xcode 编译不通过,并且报错,如图 1-2 所示:

❌ Property follows Cocoa naming convention for returning 'owned' objects
图 1-2

在开发者文档《Memory Management Policy》中有这么一条内存管理策略:

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).

上面这条规则说的是在 MRR(memory retain release)内存管理下的一条规则。
声明属性为 copyName ,也就是会默认产生 setCopyName 和 copyName 这两个方法 setter 和 getter 方法, 然而根据内存规则来说,通过 newXXX 方法就是持有 newXXX 方法返回的对象,getter 方法并不是用来持有对象的,这样就造成了奇异,so 编译器直接报错。
解决这个问题,最简单的方法就是改名,比如:

@property (nonatomic, copy) NSString *theCopyName;

当然有句话就:就不信邪!
如果执意要用这个名字的话,可以修改编译器默认为创建的 getter 方法的名字:

@property (nonatomic, copy, getter = theCopyName) NSString *copyName;

这样编译也是通过的。
还有一种方式就是使用 Function attribute 来修饰 getter 方法

@property (nonatomic, copy)   NSString *copyName ;
- (NSString *)copyName __attribute__((objc_method_family(none)));

上面这种写法也是可以通过验证的。

当然这里又有: __attribute__ 是什么的疑问了?且看 Tip3


Tip3: __attribute__ 是什么?

从上面可以看到,当为 copyName 的 getter 方法添加了 attribute 后面这一段之后,编译器便不再报错。尽管你不知道 attribute 是什么,却仍然可以推断出它让编译器忽略了对这个方法的内存规则检查即其为编译器提供了上下文。
首先,来看下 NSFoundation 框架中 NS_REQUIRES_SUPER 的使用:
NS_REQUIRES_SUPER 是在 NSObjCRuntime.h 中定义的预编译指令,定义如下

#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))

和上面针对属性的使用非常相似,这句话定义了之后,当使用 NS_REQUIRES_SUPER 地方在预编译时期就会被替换为 __attribute__((objc_requires_super)) (这一点在后面来进行验证)
在 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

创建 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 方法的时候,可以看到如下警告信息,如图 1-3:

图 1-3

当然如果不调用的话也是可以编译通过的,但是这里会弹出警告⚠️让写此类的人知道此处应该通过 [super work] 调用父类的方法。

  1. 通过观察 * NS_REQUIRES_SUPER* 的使用,对先前 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 对 __attribute__ 做了预定义处理

  1. 验证预处理阶段的替换
//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;
}

打开终端,进入到项目目录,与 main.m 在同一层级:

 $tree
.
├── MLPerson.h
├── MLPerson.m
├── MLStudent.h
├── MLStudent.m
└── main.m

0 directories, 5 files
$clang -E -fmodules main.m -o main # 对 main.m 执行预处理操作,输出文件名为main
$tree
.
├── MLPerson.h
├── MLPerson.m
├── MLStudent.h
├── MLStudent.m
├── main # 目标文件已经生成
└── 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

这就是预处理之后 MLPerson 中的内容,我们可以看到注释已经去除,并且宏也已经被替换。
上面通过 clang -E -fmodules main.m -o main 生成了预处理之后的 main 文件
当然通过:gcc -E -Foundation main.m -o main 也是可以做到这一步的,只不过目标文件中导入的 Foudation 会被展开。

至此,我们可以看到 __attribute__ 是为编译器提供上下问的一个工具或者方式,在 Cocoa 中早有使用,目前先了解到此处,后面做专门分析。


end


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容

  • 明天我们就要比赛了我心里边非常的激动我的东西们都准备好
    付梦缘阅读 126评论 0 0