系列目录
目录
1.风格纠错
2. 什么情况使用 weak 关键字,相比 assign 有什么不同?
1)、什么情况下使用weak
2)、与assign的不同
3)、参考资料
a)、区别
b)、相似
c)、总结
3. 怎么用 copy 关键字?
4. 这个写法会出什么问题: @property (copy) NSMutableArray *array;
5. 如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter?
1)、让自己的类可以使用copy修饰符的步骤
2)、如何重写带copy关键字的setter:
3)、拓展
a)、Copy & MutableCopy
b)、深拷贝 & 浅拷贝
6. @property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的?
1)、@property 的本质是什么?
2)、拓展
3)、ivar、getter、setter 是如何生成并添加到这个类中的?
7. @protocol 和 category 中如何使用 @property
1)、在 protocol 中使用 property
2)、在category 中使用 @property
8. runtime 如何实现 weak 属性
1)、weak的特点
2)、runtime 如何实现 weak 变量的自动置nil?
3)、具体机制
4)、拓展
5)、小结
9. @property中有哪些属性关键字和修饰符?
10.weak属性需要在dealloc中置nil么?
1.风格纠错

答:
// 1)、使用NS_ENUM而不是C语言类型的枚举enum
typedef NS_ENUM(NSInteget, XBYGender) { //使用gender比sex正式
XBYGenderMan,
XBYGenderWoman
}
// 2)、一般定义一个初始化方法来对属性进行赋值,而把具体属性隐藏起来(放.m文件中),
// 或者设置为只读,不可外部修改(这里倾向于设置为只读)
// 3)、NSString一般使用copy修饰
@property(nonatomic, readonly, copy) NSString *name;
// 4)、和enum一样,尽量避免c语言的内容,将int改成NSInteger或者NSUInteger
@property(nonatomic, readonly, assign) NSUInteger age;
@property(nonatomic, readonly, assign) XBYGender gender;
// 5)、初始化一般是一个实例化方法一个工厂方法
// 6)、初始化应该把属性都有对应的赋值
// 7)、返回值一般用instancetype而不是id
+ (instancetype)initWithUserName:(NSString *)name age:(NSUInteger)age gender:(XBYGender)genger;
- (instancetype)initWithUserName:(NSString *)name age:(NSUInteger)age gender:(XBYGender)genger;
// 8)、login本来就有动作的意思,不需要do
- (void)login;
2. 什么情况使用 weak 关键字,相比 assign 有什么不同?
答:
1)、什么情况下使用weak
- a、在ARC中修饰代理
@property (nonatomic, weak) id <BYDelegate> delegate;
- b、使用@IBOutlet连接控件
@property (nonatomic, weak) IBOutlet UIView *view;
- c、当block会造成循环引用
#import "People.h"
typedef void(^ResultBlock)(void);
@interface People ()
@property (nonatomic, copy) ResultBlock resultBlock;
@property (nonatomic, copy) NSString *name;
@end
@implementation People
- (void)printResult {
self.resultBlock = ^{
// 编译器警告:Capturing 'self' strongly in this block is likely to lead to
// a retain cycle
NSLog(@"%@", self.name);
};
}
- (void)printName {
__weak typeof(self) weakSelf = self;
self.resultBlock = ^{
NSLog(@"%@", weakSelf.name);
};
}
@end
2)、与assign的不同
assign可以用于非对象类型,而weak必须用于对象类型3)、参考资料
a)、区别
-
1.修饰变量类型的区别
weak 只可以修饰对象。如果修饰基本数据类型,编译器会报错-“Property with ‘weak’ attribute must be of object type”。 assign 可修饰对象,和基本数据类型。当需要修饰对象类型时,MRC时代使用unsafe_unretained。当然,unsafe_unretained也可能产生野指针,所以它名字是"unsafe_”。 -
2.是否产生野指针的区别
weak 不会产生野指针问题。因为weak修饰的对象释放后(引用计数器值为0),指针会自动被置nil,之后再向该对象发消息也不会崩溃。 weak是安全的。 assign 如果修饰对象,会产生野指针问题;如果修饰基本数据类型则是安全的。修饰的对象释放后,指针不会自动被置空,此时向对象发消息会崩溃。
b)、相似
- 都可以修饰对象类型,但是assign修饰对象会存在问题。
c)、总结
- assign 适用于基本数据类型如int,float,struct等值类型,不适用于引用类型。因为值类型会被放入栈中,遵循先进后出原则,由系统负责管理栈内存。而引用类型会被放入堆中,需要我们自己手动管理内存或通过ARC管理。
- weak 适用于delegate和block等引用类型,不会导致野指针问题,也不会循环引用,非常安全。
3. 怎么用 copy 关键字?
答:
- 1)、一般对于那些遵循NSCopying协议的类才能使用copy,例如针对那些有不可变类型和可变类型的类,NSString, NSArray 与之对应的是NSMutableString, NSMutableArray等。因为他们之间可能进行赋值操作,为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。
- 2)、block一般也使用copy,这是MRC遗留下来的使用习惯
tips:
在 MRC中,方法内部的block 是在栈区的,使用 copy 可以把它放到堆区。在ARC 中写不写都
行:对于 block 使用copy还是strong效果是一样的,但写上copy也无伤大雅,还能时刻提醒
我们:编译器自动对 block 进行了 copy 操作。如果不写 copy,该类的调用者有可能会忘记
或者根本不知道“编译器会自动对 block 进行了 copy 操作”,他们有可能会在调用之前自行拷
贝属性值。这种操作多余而低效。你也许会对这种做法有些怪异,不需要写却依然写。
示例:
@property (nonatomic, copy) NSString *userId;
- (instancetype)initWithUserId:(NSString *)userId {
self = [super init];
if (!self) {
return nil;
}
_userId = [userId copy];
return self;
}
4. 这个写法会出什么问题: @property (copy) NSMutableArray *array;
答:
- 1)、copy修饰的属性进行赋值的时候会生成一份不可变的副本,此时修改副本array(增删之类的操作)会提示找不到方法而报异常;
- 2)、不写原子性修饰词模式使用atomic,而atomic性能比nonatomic差很多。
5. 如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter?
答:
1)、让自己的类可以使用copy修饰符的步骤:
- a)、让自己的类遵循NSCopying协议
- b)、实现NSCopying协议中必须实现的代理方法:
- (id)copyWithZone:(NSZone *)zone;
示例代码:
- (id)copyWithZone:(NSZone *)zone {
//创建实例的方式比较特殊
Person *p = [[[self class] allocWithZone:zone] init];
p.name = self.name;
p.age = self.age;
return p;
}
2)、如何重写带copy关键字的setter:
- (void)setName:(NSString *)name {
//[_name release];
_name = [name copy];
}
3)、拓展
a)、Copy & MutableCopy
使用copy或mutableCopy方法可以创建一个对象的副本
- copy 需要实现NSCoppying协议 这些创建的是不可变副本(如NSString、NSArray、NSDictionary)
- mutableCopy 需要先实现NSMutableCopying协议 创建的是可变副本(如NSMutableString、NSMutableArray、NSMutableDictionary)
- copy的目的 建立副本,同时修改原始对象和复本不会互相干扰
b)、深拷贝 & 浅拷贝
-
深拷贝 内容拷贝,源对象和副本指向的是不同的两个对象 源对象引用计数器不变,副本计数器设置为1
-
浅拷贝 指针拷贝,源对象和副本指向的是同一个对象 对象的引用计数器+1,其实相当于做了一次retain操作
结论
- 只有不可变对象创建不可变副本(copy)才是浅复制,其他都是深复制
6. @property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的?
答:
1)、@property 的本质是什么?
@property = ivar + getter + setter
翻译出来就是:
属性(property)等于实例变量(ivar)加上存取方法(getter和setter,实例变量用于存储数据,而存取方法用来读取写入该实例变量的数据
有swift开发经验的人对这个感触应该表较深(存储属性和计算属性)
编译器会自动为@property添加实例变量和存取方法,当你重写一个存取方法的时候不用手动写实例变量:@synthesize name = _name;,但是当你重写setter和getter方法时,你需要添加这行代码才能通过编译。
2)、拓展
property在runtime中是objc_property_t定义如下:
typedef struct objc_property *objc_property_t;
而objc_property是一个结构体,包括name和attributes,定义如下:
struct property_t {
const char *name;
const char *attributes;
};
而attributes本质是objc_property_attribute_t,定义了property的一些属性,定义如下:
/// Defines a property attribute
typedef struct {
const char *name; /**< The name of the attribute */
const char *value; /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;
而attributes的具体内容是什么呢?其实,包括:类型,原子性,内存语义和对应的实例变量。
例如:
我们定义一个string的property@property (nonatomic, copy) NSString *string;,通过 property_getAttributes(property)获取到attributes并打印出来之后的结果为T@"NSString",C,N,V_string
其中T就代表类型,可参阅Type Encodings,C就代表Copy,N代表nonatomic,V就代表对于的实例变量。
| Code | Meaning |
|---|---|
| c | A char |
| i | An int |
| s | A short |
| l | A long l is treated as a 32-bit quantity on 64-bit programs. |
| q | A long long |
| C | An unsigned char |
| I | An unsigned int |
| S | An unsigned short |
| L | An unsigned long |
| Q | An unsigned long long |
| f | A float |
| d | A double |
| B | A C++ bool or a C99 _Bool |
| v | A void |
| * | A character string (char *) |
| @ | An object (whether statically typed or typed id) |
| # | A class object (Class) |
| : | A method selector (SEL) |
| [array type] | An array |
| {name=type...} | A structure |
| (name=type...) | A union |
| bnum | A bit field of num bits |
| ^type | A pointer to type |
| ? | An unknown type (among other things, this code is used for function pointers) |
3)、ivar、getter、setter 是如何生成并添加到这个类中的?
“自动合成”( autosynthesis) 完成属性定义后,编译器会自动编写访问这些属性所需的方法,此过程叫做“自动合成”(autosynthesis)。需要强调的是,这个过程由编译 器在编译期执行,所以编辑器里看不到这些“合成方法”(synthesized method)的源代码。除了生成方法代码 getter、setter 之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字。在前例中,会生成两个实例变量,其名称分别为 _firstName 与 _lastName。也可以在类的实现代码里通过 @synthesize 语法来指定实例变量的名字.@implementation Person
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end
反编译相关的代码,他大致生成了五个东西
OBJC_IVAR_$类名$属性名称 :该属性的“偏移量” (offset),这个偏移量是“硬编码” (hardcode),表示该变量距离存放对象的内存区域的起始地址有多远。
setter 与 getter 方法对应的实现函数
ivar_list :成员变量列表
method_list :方法列表
prop_list :属性列表
也就是说我们每次在增加一个属性,系统都会在 ivar_list 中添加一个成员变量的描述,在 method_list 中增加 setter 与 getter 方法的描述,在属性列表中增加一个属性的描述,然后计算该属性在对象中的偏移量,然后给出 setter 与 getter 方法对应的实现,在 setter 方法中从偏移量的位置开始赋值,在 getter 方法中从偏移量开始取值,为了能够读取正确字节数,系统对象偏移量的指针类型进行了类型强转.
7. 在@protocol 和 category 中如何使用 @property?
答:
1)、在 protocol 中使用 property
只会生成 setter 和 getter 方法声明,我们使用属性的目的,是希望遵守我协议的对象能实现该属性2)、在 category 中使用 @property
也是只会生成 setter 和 getter 方法的声明,如果我们真的需要给 category 增加属性的实现,需要借助于运行时的两个函数:objc_setAssociatedObject
objc_getAssociatedObject
Person.h
// 本类头文件及实现文件
#import <Foundation/Foundation.h>
@interface Person : NSObject
// 声明一个属性
@property (nonatomic, copy) NSString *name;
@end
Person.m
#import "Person.h"
@implementation Person
@end
Person+Extension.h
// 分类头文件及实现文件
#import "Person.h"
@interface Person (Extension)
// 分类中声明属性,只会生成setter和getter方法的声明,不会生成带“_”的成员变量
// 和setter和getter方法的实现
@property (nonatomic, assign) NSInteger age;
@end
Person+Extension.m
#import "Person+Extension.h"
// 使用运行时,需要导入头文件
#import <objc/runtime.h>
@implementation Person (Extension)
- (void)setAge:(NSInteger)age{
// 使用运行时关联对象,person对象(self)强引用age对象,并且设置标记为"age"(可
// 以根据该标记来获取引用的对象age,标记可以为任意字符,只要setter和getter中
// 的标记一致就可以)
// 参数1:源对象
// 参数2:关联时用来标记属性的key(因为可能要添加很多属性),很多人习惯使用char *类
// 型的key,如static char *key = "age",那这里需要传入&key,但是使用iOS的
// NSString类型的字符常量也是可以的,目测估计这里需要内存地址一致,有兴趣可以
// 试试给objc_setAssociatedObject的key对象 new一个NSString然后再
// objc_getAssociatedObject的key new一个新的字面量相同的新的key对象
// 参数3:关联的对象
// 参数4:关联策略
objc_setAssociatedObject(self, @"age", @(age), OBJC_ASSOCIATION_RETAIN);
}
- (NSInteger)age{
// 根据“age”标识取person对象(self)强引用的age对象
// 参数1:源对象
// 参数2:关联时用来标记属性的key(因为可能要添加很多属性)
return [objc_getAssociatedObject(self, @"age") integerValue];
}
@end
objc_AssociationPolicy的定义:
enum {
OBJC_ASSOCIATION_ASSIGN = 0, //assign
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, //retain
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, //copy
OBJC_ASSOCIATION_RETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403
};
8. runtime 如何实现 weak 属性
答:
1)、weak的特点
要实现 weak 属性,首先要搞清楚 weak 属性的特点:weak 此特质表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种
属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同 assign 类似,然而在属
性所指的对象遭到摧毁时,属性值也会清空(nil out)。
2)、runtime 如何实现 weak 变量的自动置nil?
runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak
指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如weak指向的
对象内存地址是a,那么就会以a为键,在这个 weak 表中搜索,找到所有以a为键的weak对象,
从而设置为 nil。
3)、具体机制
objc_storeWeak(&weakPo, Model)函数:
objc_storeWeak函数把赋值对象(Model)的内存地址作为键值key,将weak修饰的属性
变量(weakPo)的内存地址(& weakPo)作为value,注册到 weak 表中。
如果Model为0(nil),那么把变量(weakPo)的内存地址(& weakPo)从weak表中删除。
可以把objc_storeWeak(&weakPo, Model)理解为:objc_storeWeak(value, key),并
且当key变nil,将value置nil。
在Model非nil时,weakPo和Model指向同一个内存地址,在Model变nil时,weakPo变nil。
此时向weakPo发送消息不会崩溃:在Objective-C中向nil发送消息是安全的。
4)、拓展
#import <Foundation/Foundation.h>
@interface WeakProperty : NSObject
@property (nonatomic,weak) NSObject *obj;
@end
@implementation WeakProperty
- (void)dealloc {
NSLog(@"%s",__func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
WeakProperty *property = [[WeakProperty alloc] init];
NSObject *obj = [[NSObject alloc] init];
property.obj = obj;
NSLog(@"%@",property.obj);
// 会触发函数 ``id objc_initWeak(id *location, id newObj)``
// NSObject *obj = [[NSObject alloc] init];
// __weak NSObject *obj2 = obj;
// 会触发函数 ``void objc_copyWeak(id *dst, id *src)``
// __weak NSObject *obj3 = obj2;
}
return 0;
}
结果
对象的 weak 属性调用 setter 时,调用
id objc_storeWeak(id *location, id newObj)
static id storeWeak(id *location, objc_object *newObj)
使用 NSLog 输出 property.obj 属性时,调用
id objc_loadWeakRetained(id *location)
当 dealloc 释放对象时,调用
void objc_destroyWeak(id *location)
相关函数 查看 NSObject.mm 源码发现
id objc_storeWeak(id *location, id newObj)
id objc_storeWeakOrNil(id *location, id newObj)
id objc_initWeak(id *location, id newObj)
id objc_initWeakOrNil(id *location, id newObj)
void objc_destroyWeak(id *location)
都调用了 static id storeWeak(id *location, objc_object *newObj), objc_xxxWeakOrNil 多了一点额外的处理。而void objc_destroyWeak(id *location)在调用 static id storeWeak(id *location, objc_object *newObj)时 newObj 参数传递的是 nil 这一点与上面提到的参考答案中关于 dealloc 释放对象时,将哈希表中指定的键对应的值设置为 nil 是符合的。
5)、小结
- storeWeak 函数用于为 weak 属性赋值 (包括销毁)
- objc_loadWeakRetained 函数用于获取 weak 属性
对于函数 storeWeak 的调用
- 赋值,即
id objc_storeWeak(id *location, id newObj) - 销毁,即
void objc_destroyWeak(id *location)
而对于 weak 属性的获取的调用方式
- 函数
id objc_loadWeakRetained(id *location)
9. @property中有哪些属性关键字和修饰符?
答:
1)、原子性---(atomic/nonatomic ,默认或不写是atomic)
在默认情况下,由编译器合成的方法会通过锁定机制确保其原子性(atomicity)。如果属性具
备 nonatomic 特质,则不使用自旋锁。
2)、读写权限---(readwrite/readonly)
3)、内存管理---(strong/weak/assign/unsafe_unretain/copy)
4)、读写方法名---(get=< name >/set=< name >)
@property (nonatomic, getter=isOn) BOOL on;
5)、不常用---(nonnull/null_resettable/nullable)
注意
很多人会认为如果属性具备 nonatomic 特质,则不使用 “同步锁”。其实在属性设置方法中使用的是自旋锁。
自旋锁相关代码如下:
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy)
{
bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
bool mutableCopy = (shouldCopy == MUTABLE_COPY);
reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
10.weak属性需要在dealloc中置nil么?
在ARC中,无论是strong修饰的对象还是weak修饰的对象,都不再需要在dealloc中将对象置为nil,编译器会自动帮我们处理,即使编译器不帮助我们处理,根据 《8. runtime 如何实现 weak 属性》,我们可以知道,在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。