先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Web前端全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip1024c (备注前端)
正文
6. @property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的
@property 的本质是什么?
@property = ivar + getter + setter;
下面解释下:
“属性” (property)有两大概念:ivar(实例变量)、存取方法(access method = getter + setter)。
“属性” (property)作为 Objective-C 的一项特性,主要的作用就在于封装对象中的数据。 Objective-C 对象通常会把其所需要的数据保存为各种实例变量。实例变量一般通过“存取方法”(access method)来访问。其中,“获取方法” (getter)用于读取变量值,而“设置方法” (setter)用于写入变量值。这个概念已经定型,并且经由“属性”这一特性而成为 Objective-C 2.0
的一部分。 而在正规的 Objective-C 编码风格中,存取方法有着严格的命名规范。 正因为有了这种严格的命名规范,所以 Objective-C 这门语言才能根据名称自动创建出存取方法。其实也可以把属性当做一种关键字,其表示:
编译器会自动写出一套存取方法,用以访问给定类型中具有给定名称的变量。 所以你也可以这么说:
@property = getter + setter;
例如下面这个类:
@interface Person : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end
上述代码写出来的类与下面这种写法等效:
@interface Person : NSObject
-
(NSString *)firstName;
-
(void)setFirstName:(NSString *)firstName;
-
(NSString *)lastName;
-
(void)setLastName:(NSString *)lastName;
@end
更新:
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就代表对于的实例变量。
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
-
在 protocol 中使用 property 只会生成 setter 和 getter 方法声明,我们使用属性的目的,是希望遵守我协议的对象能实现该属性
-
category 使用 @property 也是只会生成 setter 和 getter 方法的声明,如果我们真的需要给 category 增加属性的实现,需要借助于运行时的两个函数:
-
objc_setAssociatedObject
-
objc_getAssociatedObject
8. runtime 如何实现 weak 属性
要实现 weak 属性,首先要搞清楚 weak 属性的特点:
weak 此特质表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同 assign 类似, 然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。
那么 runtime 如何实现 weak 变量的自动置nil?
runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。
(注:在下文的《使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?》里给出的“对象的内存销毁时间表”也提到__weak
引用的解除时间。)
先看下 runtime 里源码的实现:
/**
-
The internal structure stored in the weak references table.
-
It maintains and stores
-
a hash set of weak references pointing to an object.
-
If out_of_line==0, the set is instead a small inline array.
*/
#define WEAK_INLINE_COUNT 4
struct weak_entry_t {
DisguisedPtr<objc_object> referent;
union {
struct {
weak_referrer_t *referrers;
uintptr_t out_of_line : 1;
uintptr_t num_refs : PTR_MINUS_1;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
// out_of_line=0 is LSB of one of these (don’t care which)
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
};
/**
-
The global weak references table. Stores object ids as keys,
-
and weak_entry_t structs as their values.
*/
struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
具体完整实现参照 objc/objc-weak.h 。
我们可以设计一个函数(伪代码)来表示上述机制:
objc_storeWeak(&a, b)
函数:
objc_storeWeak
函数把第二个参数–赋值对象(b)的内存地址作为键值key,将第一个参数–weak修饰的属性变量(a)的内存地址(&a)作为value,注册到 weak 表中。如果第二个参数(b)为0(nil),那么把变量(a)的内存地址(&a)从weak表中删除,
你可以把objc_storeWeak(&a, b)
理解为:objc_storeWeak(value, key)
,并且当key变nil,将value置nil。
在b非nil时,a和b指向同一个内存地址,在b变nil时,a变nil。此时向a发送消息不会崩溃:在Objective-C中向nil发送消息是安全的。
而如果a是由 assign 修饰的,则: 在 b 非 nil 时,a 和 b 指向同一个内存地址,在 b 变 nil 时,a 还是指向该内存地址,变野指针。此时向 a 发送消息极易崩溃。
下面我们将基于objc_storeWeak(&a, b)
函数,使用伪代码模拟“runtime如何实现weak属性”:
// 使用伪代码模拟:runtime如何实现weak属性
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
id obj1;
objc_initWeak(&obj1, obj);
/obj引用计数变为0,变量作用域结束/
objc_destroyWeak(&obj1);
下面对用到的两个方法objc_initWeak
和objc_destroyWeak
做下解释:
总体说来,作用是: 通过objc_initWeak
函数初始化“附有weak修饰符的变量(obj1)”,在变量作用域结束时通过objc_destoryWeak
函数释放该变量(obj1)。
下面分别介绍下方法的内部实现:
objc_initWeak
函数的实现是这样的:在将“附有weak修饰符的变量(obj1)”初始化为0(nil)后,会将“赋值对象”(obj)作为参数,调用objc_storeWeak
函数。
obj1 = 0;
obj_storeWeak(&obj1, obj);
也就是说:
weak 修饰的指针默认值是 nil (在Objective-C中向nil发送消息是安全的)
然后obj_destroyWeak
函数将0(nil)作为参数,调用objc_storeWeak
函数。
objc_storeWeak(&obj1, 0);
前面的源代码与下列源代码相同。
// 使用伪代码模拟:runtime如何实现weak属性
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
/* … obj的引用计数变为0,被置nil … */
objc_storeWeak(&obj1, 0);
objc_storeWeak
函数把第二个参数–赋值对象(obj)的内存地址作为键值,将第一个参数–weak修饰的属性变量(obj1)的内存地址注册到 weak 表中。如果第二个参数(obj)为0(nil),那么把变量(obj1)的地址从 weak 表中删除,在后面的相关一题会详解。
使用伪代码是为了方便理解,下面我们“真枪实弹”地实现下:
如何让不使用weak修饰的@property,拥有weak的效果。
我们从setter方法入手:
(注意以下的 cyl_runAtDealloc
方法实现仅仅用于模拟原理,如果想用于项目中,还需要考虑更复杂的场景,想在实际项目使用的话,可以使用我写的一个小库,可以使用 CocoaPods 在项目中使用: CYLDeallocBlockExecutor )
- (void)setObject:(NSObject *)object
{
objc_setAssociatedObject(self, “object”, object, OBJC_ASSOCIATION_ASSIGN);
[object cyl_runAtDealloc:^{
_object = nil;
}];
}
也就是有两个步骤:
- 在setter方法中做如下设置:
objc_setAssociatedObject(self, “object”, object, OBJC_ASSOCIATION_ASSIGN);
- 在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。做到这点,同样要借助 runtime:
//要销毁的目标对象
id objectToBeDeallocated;
//可以理解为一个“事件”:当上面的目标对象销毁时,同时要发生的“事件”。
id objectWeWantToBeReleasedWhenThatHappens;
objc_setAssociatedObject(objectToBeDeallocted,
someUniqueKey,
objectWeWantToBeReleasedWhenThatHappens,
OBJC_ASSOCIATION_RETAIN);
知道了思路,我们就开始实现 cyl_runAtDealloc
方法,实现过程分两部分:
第一部分:创建一个类,可以理解为一个“事件”:当目标对象销毁时,同时要发生的“事件”。借助 block 执行“事件”。
// .h文件
// .h文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// 这个类,可以理解为一个“事件”:当目标对象销毁时,同时要发生的“事件”。借助block执行“事件”。
typedef void (^voidBlock)(void);
@interface CYLBlockExecutor : NSObject
- (id)initWithBlock:(voidBlock)block;
@end
// .m文件
// .m文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// 这个类,可以理解为一个“事件”:当目标对象销毁时,同时要发生的“事件”。借助block执行“事件”。
#import “CYLBlockExecutor.h”
@interface CYLBlockExecutor() {
voidBlock _block;
}
@implementation CYLBlockExecutor
- (id)initWithBlock:(voidBlock)aBlock
{
self = [super init];
if (self) {
_block = [aBlock copy];
}
return self;
}
- (void)dealloc
{
_block ? _block() : nil;
}
@end
第二部分:核心代码:利用runtime实现cyl_runAtDealloc
方法
// CYLNSObject+RunAtDealloc.h文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// 利用runtime实现cyl_runAtDealloc方法
#import “CYLBlockExecutor.h”
const void *runAtDeallocBlockKey = &runAtDeallocBlockKey;
@interface NSObject (CYLRunAtDealloc)
- (void)cyl_runAtDealloc:(voidBlock)block;
@end
// CYLNSObject+RunAtDealloc.m文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// 利用runtime实现cyl_runAtDealloc方法
#import “CYLNSObject+RunAtDealloc.h”
#import “CYLBlockExecutor.h”
@implementation NSObject (CYLRunAtDealloc)
- (void)cyl_runAtDealloc:(voidBlock)block
{
if (block) {
CYLBlockExecutor *executor = [[CYLBlockExecutor alloc] initWithBlock:block];
objc_setAssociatedObject(self,
runAtDeallocBlockKey,
executor,
OBJC_ASSOCIATION_RETAIN);
}
}
@end
使用方法: 导入
#import “CYLNSObject+RunAtDealloc.h”
然后就可以使用了:
NSObject *foo = [[NSObject alloc] init];
[foo cyl_runAtDealloc:^{
NSLog(@“正在释放foo!”);
}];
如果对 cyl_runAtDealloc
的实现原理有兴趣,可以看下我写的一个小库,可以使用 CocoaPods 在项目中使用: CYLDeallocBlockExecutor
参考博文: Fun With the Objective-C Runtime: Run Code at Deallocation of Any Object
9. @property中有哪些属性关键字?/ @property 后面可以有哪些修饰符?
属性可以拥有的特质分为四类:
- 原子性—
nonatomic
特质
在默认情况下,由编译器合成的方法会通过锁定机制确保其原子性(atomicity)。如果属性具备 nonatomic 特质,则不使用自旋锁。请注意,尽管没有名为“atomic”的特质(如果某属性不具备 nonatomic 特质,那它就是“原子的” ( atomic) ),但是仍然可以在属性特质中写明这一点,编译器不会报错。若是自己定义存取方法,那么就应该遵从与属性特质相符的原子性。
-
读/写权限—
readwrite(读写)
、readonly (只读)
-
内存管理语义—
assign
、strong
、weak
、unsafe_unretained
、copy
-
方法名—
getter=<name>
、setter=<name>
getter=<name>
的样式:
@property (nonatomic, getter=isOn) BOOL on;
~( `setter=`这种不常用,也不推荐使用。故不在这里给出写法。)~
setter=<name>
一般用在特殊的情境下,比如:
在数据反序列化、转模型的过程中,服务器返回的字段如果以 init
开头,所以你需要定义一个 init
开头的属性,但默认生成的 setter
与 getter
方法也会以 init
开头,而编译器会把所有以 init
开头的方法当成初始化方法,而初始化方法只能返回 self 类型,因此编译器会报错。
这时你就可以使用下面的方式来避免编译器报错:
@property(nonatomic, strong, getter=p_initBy, setter=setP_initBy:)NSString *initBy;
另外也可以用关键字进行特殊说明,来避免编译器报错:
@property(nonatomic, readwrite, copy, null_resettable) NSString *initBy;
- (NSString *)initBy attribute((objc_method_family(none)));
- 不常用的:
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环境无论是强指针还是弱指针都无需在 dealloc 设置为 nil , ARC 会自动帮我们处理
即便是编译器不帮我们做这些,weak也不需要在 dealloc 中置nil:
正如上文的:runtime 如何实现 weak 属性 中提到的:
我们模拟下 weak 的 setter 方法,应该如下:
- (void)setObject:(NSObject *)object
{
objc_setAssociatedObject(self, “object”, object, OBJC_ASSOCIATION_ASSIGN);
[object cyl_runAtDealloc:^{
_object = nil;
}];
}
如果对 cyl_runAtDealloc
的实现原理有兴趣,可以看下我写的一个小库,可以使用 CocoaPods 在项目中使用: CYLDeallocBlockExecutor
也即:
在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。
11. @synthesize和@dynamic分别有什么作用?
-
@property有两个对应的词,一个是 @synthesize,一个是 @dynamic。如果 @synthesize和 @dynamic都没写,那么默认的就是
@syntheszie var = _var;
-
@synthesize 的语义是如果你没有手动实现 setter 方法和 getter 方法,那么编译器会自动为你加上这两个方法。
-
@dynamic 告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成。(当然对于 readonly 的属性只需提供 getter 即可)。假如一个属性被声明为 @dynamic var,然后你没有提供 @setter方法和 @getter 方法,编译的时候没问题,但是当程序运行到
instance.var = someVar
,由于缺 setter 方法会导致程序崩溃;或者当运行到someVar = var
时,由于缺 getter 方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
12. ARC下,不显式指定任何属性关键字时,默认的关键字都有哪些?
- 对应基本数据类型默认关键字是
atomic,readwrite,assign 2. 对于普通的 Objective-C 对象
atomic,readwrite,strong
参考链接:
13. 用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?
-
因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本.
-
如果我们使用是 strong ,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性.
copy 此特质所表达的所属关系与 strong 类似。然而设置方法并不保留新值,而是将其“拷贝” (copy)。 当属性类型为 NSString 时,经常用此特质来保护其封装性,因为传递给设置方法的新值有可能指向一个 NSMutableString 类的实例。这个类是 NSString 的子类,表示一种可修改其值的字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。所以,这时就要拷贝一份“不可变” (immutable)的字符串,确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是“可变的” (mutable),就应该在设置新属性值时拷贝一份。
举例说明:
定义一个以 strong 修饰的 array:
@property (nonatomic ,readwrite, strong) NSArray *array;
然后进行下面的操作:
NSArray *array = @[ @1, @2, @3, @4 ];
NSMutableArray *mutableArray = [NSMutableArray arrayWithArray:array];
self.array = mutableArray;
[mutableArray removeAllObjects];;
NSLog(@“%@”,self.array);
[mutableArray addObjectsFromArray:array];
self.array = [mutableArray copy];
[mutableArray removeAllObjects];;
NSLog(@“%@”,self.array);
打印结果如下所示:
2015-09-27 19:10:32.523 CYLArrayCopyDmo[10681:713670] (
)
2015-09-27 19:10:32.524 CYLArrayCopyDmo[10681:713670] (
1,
2,
3,
4
)
(详见仓库内附录的 Demo。)
为了理解这种做法,首先要知道,两种情况:
-
对非集合类对象的 copy 与 mutableCopy 操作;
-
对集合类对象的 copy 与 mutableCopy 操作。
1. 对非集合类对象的copy操作:
在非集合类对象中:对 immutable 对象进行 copy 操作,是指针复制,mutableCopy 操作时内容复制;对 mutable 对象进行 copy 和 mutableCopy 都是内容复制。用代码简单表示如下:
-
[immutableObject copy] // 浅复制
-
[immutableObject mutableCopy] //深复制
-
[mutableObject copy] //深复制
-
[mutableObject mutableCopy] //深复制
比如以下代码:
NSMutableString *string = [NSMutableString stringWithString:@“origin”];//copy
NSString *stringCopy = [string copy];
查看内存,会发现 string、stringCopy 内存地址都不一样,说明此时都是做内容拷贝、深拷贝。即使你进行如下操作:
[string appendString:@“origion!”]
stringCopy 的值也不会因此改变,但是如果不使用 copy,stringCopy 的值就会被改变。 集合类对象以此类推。 所以,
用 @property 声明 NSString、NSArray、NSDictionary 经常使用 copy 关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作,为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。
2、集合类对象的copy与mutableCopy
集合类对象是指 NSArray、NSDictionary、NSSet … 之类的对象。下面先看集合类immutable对象使用 copy 和 mutableCopy 的一个例子:
NSArray *array = @[@[@“a”, @“b”], @[@“c”, @“d”]];
NSArray *copyArray = [array copy];
NSMutableArray *mCopyArray = [array mutableCopy];
查看内容,可以看到 copyArray 和 array 的地址是一样的,而 mCopyArray 和 array 的地址是不同的。说明 copy 操作进行了指针拷贝,mutableCopy 进行了内容拷贝。但需要强调的是:此处的内容拷贝,仅仅是拷贝 array 这个对象,array 集合内部的元素仍然是指针拷贝。这和上面的非集合 immutable 对象的拷贝还是挺相似的,那么mutable对象的拷贝会不会类似呢?我们继续往下,看 mutable 对象拷贝的例子:
NSMutableArray *array = [NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@“a”],@“b”,@“c”,nil];
NSArray *copyArray = [array copy];
NSMutableArray *mCopyArray = [array mutableCopy];
查看内存,如我们所料,copyArray、mCopyArray和 array 的内存地址都不一样,说明 copyArray、mCopyArray 都对 array 进行了内容拷贝。同样,我们可以得出结论:
在集合类对象中,对 immutable 对象进行 copy,是指针复制, mutableCopy 是内容复制;对 mutable 对象进行 copy 和 mutableCopy 都是内容复制。但是:集合对象的内容复制仅限于对象本身,对象元素仍然是指针复制。用代码简单表示如下:
[immutableObject copy] // 浅复制
[immutableObject mutableCopy] //单层深复制
[mutableObject copy] //单层深复制
[mutableObject mutableCopy] //单层深复制
这个代码结论和非集合类的非常相似。
参考链接:iOS 集合的深复制与浅复制
14. @synthesize合成实例变量的规则是什么?假如property名为foo,存在一个名为_foo
的实例变量,那么还会自动合成新变量么?
在回答之前先说明下一个概念:
实例变量 = 成员变量 = ivar
这些说法,笔者下文中,可能都会用到,指的是一个东西。
正如 Apple官方文档 You Can Customize Synthesized Instance Variable Names 所说:
如果使用了属性的话,那么编译器就会自动编写访问属性所需的方法,此过程叫做“自动合成”( auto synthesis)。需要强调的是,这个过程由编译器在编译期执行,所以编辑器里看不到这些“合成方法” (synthesized method)的源代码。除了生成方法代码之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字。
@interface CYLPerson : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end
在上例中,会生成两个实例变量,其名称分别为 _firstName
与 _lastName
。也可以在类的实现代码里通过 @synthesize
语法来指定实例变量的名字:
@implementation CYLPerson
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end
上述语法会将生成的实例变量命名为 _myFirstName
与 _myLastName
,而不再使用默认的名字。一般情况下无须修改默认的实例变量名,但是如果你不喜欢以下划线来命名实例变量,那么可以用这个办法将其改为自己想要的名字。笔者还是推荐使用默认的命名方案,因为如果所有人都坚持这套方案,那么写出来的代码大家都能看得懂。
总结下 @synthesize 合成实例变量的规则,有以下几点:
-
如果指定了成员变量的名称,会生成一个指定的名称的成员变量,
-
如果这个成员已经存在了就不再生成了.
-
如果是
@synthesize foo;
还会生成一个名称为foo的成员变量,也就是说:
如果没有指定成员变量的名称会自动生成一个属性同名的成员变量,
- 如果是
@synthesize foo = _foo;
就不会生成成员变量了.
假如 property 名为 foo,存在一个名为 _foo
的实例变量,那么还会自动合成新变量么? 不会。如下图:
15. 在有了自动合成属性实例变量之后,@synthesize还有哪些使用场景?
回答这个问题前,我们要搞清楚一个问题,什么情况下不会autosynthesis(自动合成)?
-
同时重写了 setter 和 getter 时
-
重写了只读属性的 getter 时
-
使用了 @dynamic 时
-
在 @protocol 中定义的所有属性
-
在 category 中定义的所有属性
-
重载的属性
当你在子类中重载了父类中的属性,你必须 使用 @synthesize
来手动合成ivar。
除了后三条,对其他几个我们可以总结出一个规律:当你想手动管理 @property 的所有内容时,你就会尝试通过实现 @property 的所有“存取方法”(the accessor methods)或者使用 @dynamic
来达到这个目的,这时编译器就会认为你打算手动管理 @property,于是编译器就禁用了 autosynthesis(自动合成)。
因为有了 autosynthesis(自动合成),大部分开发者已经习惯不去手动定义ivar,而是依赖于 autosynthesis(自动合成),但是一旦你需要使用ivar,而 autosynthesis(自动合成)又失效了,如果不去手动定义ivar,那么你就得借助 @synthesize
来手动合成 ivar。
其实,@synthesize
语法还有一个应用场景,但是不太建议大家使用:
可以在类的实现代码里通过 @synthesize
语法来指定实例变量的名字:
@implementation CYLPerson
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end
上述语法会将生成的实例变量命名为 _myFirstName
与 _myLastName
,而不再使用默认的名字。一般情况下无须修改默认的实例变量名,但是如果你不喜欢以下划线来命名实例变量,那么可以用这个办法将其改为自己想要的名字。笔者还是推荐使用默认的命名方案,因为如果所有人都坚持这套方案,那么写出来的代码大家都能看得懂。
举例说明:应用场景:
//
// .m文件
// http://weibo.com/luohanchenyilong/ (微博@iOS程序犭袁)
// https://github.com/ChenYilong
// 打开第14行和第17行中任意一行,就可编译成功
@import Foundation;
@interface CYLObject : NSObject
@property (nonatomic, copy) NSString *title;
@end
@implementation CYLObject {
// NSString *_title;
}
//@synthesize title = _title;
- (instancetype)init
{
self = [super init];
if (self) {
_title = @“微博@iOS程序犭袁”;
}
return self;
}
- (NSString *)title {
return _title;
}
- (void)setTitle:(NSString *)title {
_title = [title copy];
}
@end
当你同时重写了 setter 和 getter 时,系统就不会生成 ivar(实例变量/成员变量)。这时候有两种选择:
-
要么如第14行:手动创建 ivar
-
要么如第17行:使用
@synthesize foo = _foo;
,关联 @property 与 ivar。
更多信息,请戳- 》 When should I use @synthesize explicitly?
16. objc中向一个nil对象发送消息将会发生什么?
在 Objective-C 中向 nil 发送消息是完全有效的——只是在运行时不会有任何作用:
- 如果一个方法返回值是一个对象,那么发送给nil的消息将返回0(nil)。例如:
Person * motherInlaw = [[aPerson spouse] mother];
如果 spouse 对象为 nil,那么发送给 nil 的消息 mother 也将返回 nil。 2. 如果方法返回值为指针类型,其指针大小为小于或者等于sizeof(void*),float,double,long double 或者 long long 的整型标量,发送给 nil 的消息将返回0。 2. 如果方法返回值为结构体,发送给 nil 的消息将返回0。结构体中各个字段的值将都是0。 2. 如果方法的返回值不是上述提到的几种情况,那么发送给 nil 的消息的返回值将是未定义的。
具体原因如下:
objc是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector)。
那么,为了方便理解这个内容,还是贴一个objc的源代码:
// runtime.h(类在runtime中的定义)
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; //isa指针指向Meta Class,因为Objc的类的本身也是一个Object,为了处理这个关系,runtime就创造了Meta Class,当给类发送[NSObject alloc]这样消息时,实际上是把这个消息发给了Class Object
#if !OBJC2
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
long info OBJC2_UNAVAILABLE; // 类信息,供运行期使用的一些位标识
long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量链表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定义的链表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法缓存,对象接到一个消息会根据isa指针查找消息对象,这时会在method Lists中遍历,如果cache了,常用的方法调用时就能够提高调用的效率。
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表
#endif
} OBJC2_UNAVAILABLE;
objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,然后在发送消息的时候,objc_msgSend方法不会返回值,所谓的返回内容都是具体调用时执行的。 那么,回到本题,如果向一个nil对象发送消息,首先在寻找对象的isa指针时就是0地址返回了,所以不会出现任何错误。
17. objc中向一个对象发送消息[obj foo]和objc_msgSend()
函数之间有什么关系?
具体原因同上题:该方法编译之后就是objc_msgSend()
函数调用.
我们用 clang 分析下,clang 提供一个命令,可以将Objective-C的源码改写成C++语言,借此可以研究下[obj foo]和objc_msgSend()
函数之间有什么关系。
以下面的代码为例,由于 clang 后的代码达到了10万多行,为了便于区分,添加了一个叫 iOSinit 方法,
//
// main.m
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// Copyright © 2015年 微博@iOS程序犭袁. All rights reserved.
//
#import “CYLTest.h”
int main(int argc, char * argv[]) {
@autoreleasepool {
CYLTest *test = [[CYLTest alloc] init];
[test performSelector:(@selector(iOSinit))];
return 0;
}
}
在终端中输入
clang -rewrite-objc main.m
就可以生成一个main.cpp
的文件,在最低端(10万4千行左右)
我们可以看到大概是这样的:
((void ()(id, SEL))(void )objc_msgSend)((id)obj, sel_registerName(“foo”));
也就是说:
[obj foo];在objc编译时,会被转意为:
objc_msgSend(obj, @selector(foo));
。
18. 什么时候会报unrecognized selector的异常?
简单来说:
当调用该对象上某个方法,而该对象上没有实现这个方法的时候, 可以通过“消息转发”进行解决。
简单的流程如下,在上一题中也提到过:
objc是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector)。
objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,如果,在最顶层的父类中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX 。但是在这之前,objc的运行时会给出三次拯救程序崩溃的机会:
- Method resolution
objc运行时会调用+resolveInstanceMethod:
或者 +resolveClassMethod:
,让你有机会提供一个函数实现。如果你添加了函数,那运行时系统就会重新启动一次消息发送的过程,否则 ,运行时就会移到下一步,消息转发(Message Forwarding)。
- Fast forwarding
如果目标对象实现了-forwardingTargetForSelector:
,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。 只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会继续Normal Fowarding。 这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但下一步转发会创建一个NSInvocation对象,所以相对更快点。 3. Normal forwarding
这一步是Runtime最后一次给你挽救的机会。首先它会发送-methodSignatureForSelector:
消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:
返回nil,Runtime则会发出-doesNotRecognizeSelector:
消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:
消息给目标对象。
为了能更清晰地理解这些方法的作用,git仓库里也给出了一个Demo,名称叫“ _objc_msgForward_demo
”,可运行起来看看。
19. 一个objc对象如何进行内存布局?(考虑有父类的情况)
-
所有父类的成员变量和自己的成员变量都会存放在该对象所对应的存储空间中.
-
每一个对象内部都有一个isa指针,指向他的类对象,类对象中存放着本对象的
-
对象方法列表(对象能够接收的消息列表,保存在它所对应的类对象中)
-
成员变量的列表,
-
属性列表,
它内部也有一个isa指针指向元对象(meta class),元对象内部存放的是类方法列表,类对象内部还有一个superclass的指针,指向他的父类对象。
每个 Objective-C 对象都有相同的结构,如下图所示:
翻译过来就是
| Objective-C 对象的结构图 |
| — |
| ISA指针 |
| 根类的实例变量 |
| 倒数第二层父类的实例变量 |
| … |
| 父类的实例变量 |
| 类的实例变量 |
-
根对象就是NSObject,它的superclass指针指向nil
-
类对象既然称为对象,那它也是一个实例。类对象中也有一个isa指针指向它的元类(meta class),即类对象是元类的实例。元类内部存放的是类方法列表,根元类的isa指针指向自己,superclass指针指向NSObject类。
20. 一个objc对象的isa的指针指向什么?有什么作用?
指向他的类对象,从而可以找到对象上的方法
21. 下面的代码输出什么?
@implementation Son : Father
- (id)init
{
self = [super init];
if (self) {
NSLog(@“%@”, NSStringFromClass([self class]));
NSLog(@“%@”, NSStringFromClass([super class]));
}
return self;
}
@end
答案:
都输出 Son
NSStringFromClass([self class]) = Son
NSStringFromClass([super class]) = Son
这个题目主要是考察关于 Objective-C 中对 self 和 super 的理解。
我们都知道:self 是类的隐藏参数,指向当前调用方法的这个类的实例。那 super 呢?
很多人会想当然的认为“ super 和 self 类似,应该是指向父类的指针吧!”。这是很普遍的一个误区。其实 super 是一个 Magic Keyword, 它本质是一个编译器标示符,和 self 是指向的同一个消息接受者!他们两个的不同点在于:super 会告诉编译器,调用 class 这个方法时,要去父类的方法,而不是本类里的。
上面的例子不管调用[self class]
还是[super class]
,接受消息的对象都是当前 Son *xxx
这个对象。
Vue 面试题
1.Vue 双向绑定原理
2.描述下 vue 从初始化页面–修改数据–刷新页面 UI 的过程?
3.你是如何理解 Vue 的响应式系统的?
4.虚拟 DOM 实现原理
5.既然 Vue 通过数据劫持可以精准探测数据变化,为什么还需要虚拟 DOM 进行 diff 检测差异?
6.Vue 中 key 值的作用?
7.Vue 的生命周期
8.Vue 组件间通信有哪些方式?
9.watch、methods 和 computed 的区别?
10.vue 中怎么重置 data?
11.组件中写 name 选项有什么作用?
12.vue-router 有哪些钩子函数?
13.route 和 router 的区别是什么?
14.说一下 Vue 和 React 的认识,做一个简单的对比
15.Vue 的 nextTick 的原理是什么?
16.Vuex 有哪几种属性?
17.vue 首屏加载优化
18.Vue 3.0 有没有过了解?
19.vue-cli 替我们做了哪些工作?
…
算法
- 冒泡排序
- 选择排序
- 快速排序
- 二叉树查找: 最大值、最小值、固定值
- 二叉树遍历
- 二叉树的最大深度
- 给予链表中的任一节点,把它删除掉
- 链表倒叙
- 如何判断一个单链表有环
- 给定一个有序数组,找出两个数相加为一个目标数
…
由于篇幅限制小编,pdf文档的详解资料太全面,细节内容实在太多啦,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!有需要的程序猿(媛)可以帮忙点赞+评论666
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注前端)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
2f7736747a46787a2e706e67)
20. 一个objc对象的isa的指针指向什么?有什么作用?
指向他的类对象,从而可以找到对象上的方法
21. 下面的代码输出什么?
@implementation Son : Father
- (id)init
{
self = [super init];
if (self) {
NSLog(@“%@”, NSStringFromClass([self class]));
NSLog(@“%@”, NSStringFromClass([super class]));
}
return self;
}
@end
答案:
都输出 Son
NSStringFromClass([self class]) = Son
NSStringFromClass([super class]) = Son
这个题目主要是考察关于 Objective-C 中对 self 和 super 的理解。
我们都知道:self 是类的隐藏参数,指向当前调用方法的这个类的实例。那 super 呢?
很多人会想当然的认为“ super 和 self 类似,应该是指向父类的指针吧!”。这是很普遍的一个误区。其实 super 是一个 Magic Keyword, 它本质是一个编译器标示符,和 self 是指向的同一个消息接受者!他们两个的不同点在于:super 会告诉编译器,调用 class 这个方法时,要去父类的方法,而不是本类里的。
上面的例子不管调用[self class]
还是[super class]
,接受消息的对象都是当前 Son *xxx
这个对象。
Vue 面试题
1.Vue 双向绑定原理
2.描述下 vue 从初始化页面–修改数据–刷新页面 UI 的过程?
3.你是如何理解 Vue 的响应式系统的?
4.虚拟 DOM 实现原理
5.既然 Vue 通过数据劫持可以精准探测数据变化,为什么还需要虚拟 DOM 进行 diff 检测差异?
6.Vue 中 key 值的作用?
7.Vue 的生命周期
8.Vue 组件间通信有哪些方式?
9.watch、methods 和 computed 的区别?
10.vue 中怎么重置 data?
11.组件中写 name 选项有什么作用?
12.vue-router 有哪些钩子函数?
13.route 和 router 的区别是什么?
14.说一下 Vue 和 React 的认识,做一个简单的对比
15.Vue 的 nextTick 的原理是什么?
16.Vuex 有哪几种属性?
17.vue 首屏加载优化
18.Vue 3.0 有没有过了解?
19.vue-cli 替我们做了哪些工作?
…
[外链图片转存中…(img-59hCUS2a-1713030795296)]
算法
- 冒泡排序
- 选择排序
- 快速排序
- 二叉树查找: 最大值、最小值、固定值
- 二叉树遍历
- 二叉树的最大深度
- 给予链表中的任一节点,把它删除掉
- 链表倒叙
- 如何判断一个单链表有环
- 给定一个有序数组,找出两个数相加为一个目标数
…
[外链图片转存中…(img-bZOrU3n5-1713030795297)]
由于篇幅限制小编,pdf文档的详解资料太全面,细节内容实在太多啦,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!有需要的程序猿(媛)可以帮忙点赞+评论666
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-pHGPWsS9-1713030795297)]
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!