先自我先容一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,每每是本身探索成长,但本身不成体系的自学效果低效又漫长,而且极易遇到天花板技能停滞不前!
因此网络整理了一份《2024年最新Web前端全套学习资料》,初志也很简朴,就是希望能够资助到想自学提升又不知道该从何学起的朋侪。
既有适合小白学习的零基础资料,也有适合3年以上经验的小同伴深入学习提升的进阶课程,涵盖了95%以上前端开发知识点,真正体系化!
由于文件比力多,这里只是将部分目次截图出来,全套包含大厂面经、学习笔记、源码课本、实战项目、大纲路线、讲解视频,并且后续会连续更新
如果你必要这些资料,可以添加V获取:vip1024c (备注前端)
正文
老百姓 copy 一下,咋就这么难?
你可能会说:
之以是在这里做if判断 这个操作:是因为一个 if 可能避免一个耗时的copy,还是很划算的。 (在刚刚讲的:《如何让本身的类用 copy 修饰符?》里的那种复杂的copy,我们可以称之为 “耗时的copy”,但是对 NSString 的 copy 还称不上。)
但是你有没有考虑过代价:
你每次调用 setX: 都会做 if 判断,这会让 setX: 变慢,如果你在 setX:写了一串复杂的 if+elseif+elseif+... 判断,将会更慢。
要回答“哪个服从会高一些?”这个题目,不能脱离实际开发,就算 copy 操作非常耗时,if 判断也不见得一定会更快,除非你把一个“ @property他当前的值 ”赋给了他本身,代码看起来就像:
[a setX:x1];
[a setX:x1]; //你确定你要这么干?与其在setter中判断,为什么不把代码写好?
或者
[a setX:[a x]]; //队友咆哮道:你在干嘛?!!
不要在 setter 里进行像 if(_obj != newObj) 这样的判断。(该观点参考链接: How To Write Cocoa Object Setters: Principle 3: Only Optimize After You Measure )
什么情况会在 copy setter 里做 if 判断? 例如,车速可能就有最高速的限定,车速也不可能出现负值,如果车子的最高速为300,则 setter 的方法就要改写成这样:
-(void)setSpeedint)_speed{
if(_speed < 0) speed = 0;
if(_speed > 300) speed = 300;
_speed = speed;
}
回到这个题目,如果单单就上文的代码而言,我们不必要也不能重写 name 的 setter :由于是 name 是只读属性,以是编译器不会为其创建对应的“设置方法”,用初始化方法设置好属性值之后,就不能再改变了。( 在本例中,之以是还要声明属性的“内存管理语义”–copy,是因为:如果不写 copy,该类的调用者就不知道初始化方法里会拷贝这些属性,他们有可能会在调用初始化方法之前自行拷贝属性值。这种操作多余而低效)。
那如何确保 name 被 copy?在初始化方法(initializer)中做:
- (instancetype)initWithNameNSString *)name
ageNSUInteger)age
sexCYLSex)sex {
if(self = [super init]) {
_name = [name copy];
_age = age;
_sex = sex;
_friends = [[NSMutableSet alloc] init];
}
return self;
}
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)setFirstNameNSString *)firstName;
- (NSString *)lastName;
- (void)setLastNameNSString *)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)setObjectNSObject *)object
{
objc_setAssociatedObject(self, “object”, object, OBJC_ASSOCIATION_ASSIGN);
[object cyl_runAtDealloc:^{
_object = nil;
}];
}
也就是有两个步骤:
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)initWithBlockvoidBlock)block;
@end
// .m文件
// .m文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// 这个类,可以明白为一个“变乱”:当目的对象销毁时,同时要发生的“变乱”。借助block执行“变乱”。
#import “CYLBlockExecutor.h”
@interface CYLBlockExecutor() {
voidBlock _block;
}
@implementation CYLBlockExecutor
- (id)initWithBlockvoidBlock)aBlock
{
self = [super init];
if (self) {
_block = [aBlock copy];
}
return self;
}
{
_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_runAtDeallocvoidBlock)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 背面可以有哪些修饰符?
属性可以拥有的特质分为四类:
在默认情况下,由编译器合成的方法会通过锁定机制确保其原子性(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_initByNSString *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
参考链接:
- Objective-C ARC: strong vs retain and weak vs assign
- Variable property attributes or Modifiers in iOS
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;
{
self = [super init];
if (self) {
_title = @“微博@iOS程序犭袁”;
}
return self;
}
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的运行时会给出三次拯救程序瓦解的时机:
objc运行时会调用+resolveInstanceMethod:或者 +resolveClassMethod:,让你偶然机提供一个函数实现。如果你添加了函数,那运行时系统就会重新启动一次消息发送的过程,否则 ,运行时就会移到下一步,消息转发(Message 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 对象都有相同的结构,如下图所示:
翻译过来就是
盘算机网络
- HTTP 缓存
- 你知道 302 状态码是什么嘛?你平时欣赏网页的过程中遇到过哪些 302 的场景?
- HTTP 常用的请求方式,区别和用途?
- HTTPS 是什么?详细流程
- 三次握手和四次挥手
- 你对 TCP 滑动窗口有了解嘛?
- WebSocket与Ajax的区别
- 了解 WebSocket 嘛?
- HTTP 如何实现长连接?在什么时候会超时?
- TCP 如何保证有用传输及拥塞控制原理。
- TCP 协议怎么保证可靠的,UDP 为什么不可靠?
算法
- 链表
- 字符串
- 数组题目
- 二叉树
- 排序算法
- 二分查找
- 动态规划
- BFS
- 栈
- DFS
- 回溯算法
网上学习资料一大堆,但如果学到的知识不成体系,遇到题目时只是浅尝辄止,不再深入研究,那么很难做到真正的技能提升。
必要这份系统化的资料的朋侪,可以添加V获取:vip1024c (备注前端)
一个人可以走的很快,但一群人才能走的更远!岂论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都接待到场我们的的圈子(技能交换、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
tForSelector:`,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 对象都有相同的结构,如下图所示:
[外链图片转存中…(img-XEiT4SAy-1713030826872)]
翻译过来就是
盘算机网络
- HTTP 缓存
- 你知道 302 状态码是什么嘛?你平时欣赏网页的过程中遇到过哪些 302 的场景?
- HTTP 常用的请求方式,区别和用途?
- HTTPS 是什么?详细流程
- 三次握手和四次挥手
- 你对 TCP 滑动窗口有了解嘛?
- WebSocket与Ajax的区别
- 了解 WebSocket 嘛?
- HTTP 如何实现长连接?在什么时候会超时?
- TCP 如何保证有用传输及拥塞控制原理。
- TCP 协议怎么保证可靠的,UDP 为什么不可靠?
算法
- 链表
- 字符串
- 数组题目
- 二叉树
- 排序算法
- 二分查找
- 动态规划
- BFS
- 栈
- DFS
- 回溯算法
网上学习资料一大堆,但如果学到的知识不成体系,遇到题目时只是浅尝辄止,不再深入研究,那么很难做到真正的技能提升。
必要这份系统化的资料的朋侪,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-PqGPTQbm-1713030826873)]
一个人可以走的很快,但一群人才能走的更远!岂论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都接待到场我们的的圈子(技能交换、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |