一. NSObjcet对象在内存中占多少个字节?
- 我觉得吧,对象是指针,64位系统一个指针就是8个字节,就是8个字节
验证下:我们用系统的函数获取一下,然后就震惊了,发现两个函数返回的大小竟然不一样,看来得继续得就探究了。
NSObject *objc = [[NSObject alloc] init];
//传入类,经过结构体对齐后,应该占多少
size_t size1 = class_getInstanceSize(NSObject.class);
NSLog(@"%zu", size1); //8
//传入对象,实际开辟的空间大小(在堆上开辟)
size_t size2 = malloc_size((__bridge const void *)(objc));
NSLog(@"%zu", size2); //16
- 看下
class_getInstanceSize
的实现源码
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
// Class's ivar size rounded up to a pointer-size boundary. 结构体对齐
//传入尚未结构体对齐的对象,使用word_align函数将其对齐
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
// May be unaligned depending on class's ivars. 可能尚未经过结构体对齐
// 返回对象的实际大小
uint32_t unalignedInstanceSize() const {
ASSERT(isRealized());
return data()->ro()->instanceSize;
}
//**结构体内存对齐**:结构体的大小必须为最大成员大小的倍数
//用空间换时间,合理利用内存的地址.
//alignedInstanceSize()的实现,也就是结构体对齐的实现.
//unalignedInstanceSize是对象的实际大小,不经过结构体对齐的.经过word_align后就是结构体对齐过的.
- 看下
alloc
的实现源码
apple规定:如果一个对象分配的内存<16,那么就等于16.而且对象分配的内存为16的倍数必须为16的倍数,是因为为了兼容以前某些模块读取是按照16位读取的,参考深入理解计算机系统的第3章
NSObject *objc = [[NSObject alloc] init];
// Replaced by ObjectAlloc
+ (id)allocWithZone:(struct _NSZone *)zone {
return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
}
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone)
{
id obj;
if (fastpath(!zone)) {
obj = class_createInstance(cls, 0);
} else {
obj = class_createInstanceFromZone(cls, 0, zone);
}
if (slowpath(!obj)) obj = _objc_callBadAllocHandler(cls);
return obj;
}
class_createInstance(Class cls, size_t extraBytes)
{
if (!cls) return nil;
return _class_createInstanceFromZone(cls, extraBytes, nil);
}
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone)
{
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
if (zone) {
bytes = malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
bytes = calloc(1, size);
}
}
//calloc的实现
void *
calloc(size_t num_items, size_t size)
{
return _malloc_zone_calloc(default_zone, num_items, size, MZ_POSIX);
}
static void *
_malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size,
malloc_zone_options_t mzo)
{
MALLOC_TRACE(TRACE_calloc | DBG_FUNC_START, (uintptr_t)zone, num_items, size, 0);
void *ptr;
if (malloc_check_start) {
internal_check();
}
ptr = zone->calloc(zone, num_items, size);
if (os_unlikely(malloc_logger)) {
malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE | MALLOC_LOG_TYPE_CLEARED, (uintptr_t)zone,
(uintptr_t)(num_items * size), 0, (uintptr_t)ptr, 0);
}
MALLOC_TRACE(TRACE_calloc | DBG_FUNC_END, (uintptr_t)zone, num_items, size, (uintptr_t)ptr);
if (os_unlikely(ptr == NULL)) {
malloc_set_errno_fast(mzo, ENOMEM);
}
return ptr;
}
//操作系统决定的,分配的内存就是16 32 48,一定是16的倍数。
#define NANO_MAX_SIZE 256 /* Buckets sized {16, 32, 48, ..., 256} */
- 搞个demo1测试下
@interface Father: NSObject
{
int age;
int weight;
int height;
}
@end
@implementation Father
@end
打印:NSLog(@"%zd", class_getInstanceSize([Father class]));
输出:2019-01-10 10:14:24.498 iOSWorld[2913:385535] 24
Father *f = [[Father alloc]init];
NSLog(@"%zd",malloc_size((__bridge const void *)(f)));
输出32.
注意:因为Father继承自NSObject,所以它内部有NSObject的实现,那么它占的内存为8(NSObject的isa指针)+4x(Father的3个int类型) = 8 + 12 =20个字节
但是:因为结构体计算内存的时候,要考虑结构体对齐的问题.
不满8个字节也要算8个字节,也就是必须是8的倍数,所以实际占用24个字节.
但但但又因为:苹果规定了每个对象在内存中至少16个字节,也就是16的倍数.所以f这个实例对象实际上占用了32个字节.(objc4源码的calloc方法里面有苹果的规定
)
结论:Father对象其实只需要24个字节的空间,但实际上给分配了32个字节.
- 搞个demo2测试下
@interface Father : NSObject
{
int age;
}
@end
@implementation Father
@end
@interface Son : Father
{
int weight;
}
@end
@implementation Son
@end
打印:NSLog(@"%zd", class_getInstanceSize([Son class]));
输出:2019-01-10 10:48:00.284 iOSWorld[2940:390486] 16
father占了8(NSObject 指针) + 4(int类型) = 12
son 继承自 father,所以son的c++结构体应该张这样
struct Son_IMPL{
struct Father_IMPL s; //16个字节
int weight;//4个字节
}
乍一看16+4= 20, 但是答案是16.
原因还在于内存对齐,毕竟Father_IMPL还有4个字节是空的,刚好容得下weight这个int
- 所以,NSObjcet对象在内存中最少占用16个字节。
二. 那NSObjcet到底是个啥东西
NSObject *objc = [[NSObject alloc] init];
- 使用xcrun指定编译的sdk, 将.m编译成cpp
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc clangtest.m -o clangtest.cpp
- 发现两个东西
struct NSObject_IMPL {
Class isa;
};
NSObject *lykObj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
- 对比一下, 发现xcode里面
@interface NSObject
就是cpp里面的struct NSObject_IMPL
@interface NSObject {
Class isa OBJC_ISA_AVAILABILITY;
}
struct NSObject_IMPL {
Class isa;
};
- 可以在
usr/include objc objc.h
找到Class
等的声明
它是一个指向struct objc_class
结构体的指针
!__OBJC2__
代表:不是objc2这个版本才可以使用下面的定义;
Use Class instead of struct objc_class
:让我们使用Class这个结构体.
struct objc_class {
// 新的数据结构
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
//下面是老的数据结构
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
- 从源码中找到Class结构体(从下往上看)
struct method_t {
SEL name; //const char * 类型的字符串
const char *types; //typeEncode,入参和出参类型
IMP imp; //函数指针,指向函数
};
#:类最初始的信息,不包含catagory等的信息---ro的意思:只读
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
}
#:类的具体信息----rw的意思:可读可写
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;//包含了上面的结构体
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
}
//因为是继承关系,所以objc_class里面也有一个isa指针
struct objc_class : objc_object {
// Class ISA;
Class superclass;
#方法缓存
cache_t cache; // formerly cache pointer and vtable
#:bits用来获取具体的类信息,bits&FAST_DATA_MASK就可以拿到struct class_rw_t的信息.
#:为什么要&呢,这是因为&的话,可以拿到64位信息的后30多位的数据,前30多位可以存储其他信息,不浪费空间
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
}
- 所以说,NSObjcet是个类,是一个类对象, 内部存放的是对象需要的信息
三.什么是ISA
在上一节,我们知道struct objc_class : objc_object
,objc_class
继承自objc_object
- 看下
objc_object
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA(bool authenticated = false);
// rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
Class rawISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
}
//isa_t是个联合体/共用体
union isa_t {
isa_t() { }
uintptr_t bits;
struct {
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
};
}
四.类的信息放在哪里?
*OC对象共3种:
1.实例对象(instance):存放isa指针(指向类)和变量
2.类对象(class):存放isa指针(指向元类)、superClass、属性、对象方法、协议、成员变量
3.元类对象(meta-Class):存放isa指针(指向根元类)、superClass、类方法
所有元类的isa指针都指向根元类(rootMetaClass), 根元类的isa指向自己
根元类的superClasss指向RootClass,也就是NSObject类, NSObject类的superClass为nil
答案:类的信息放在元类里面
- 获取元类的几种方法以及他们的区别
NSObject *objc = [[NSObject alloc] init];
//获取objc的类
Class cls1 = objc.class;
Class cls2 = objc_getClass("NSObject");
//获取元类
Class cls3 = [cls1 class];
Class cls4 = objc_getClass([NSStringFromClass(cls1) UTF8String]);
Class cls5 = object_getClass(cls1);
Class cls6 = object_getClass(cls2);
NSLog(@"%p", cls1); //0x1ed52f258
NSLog(@"%p", cls2); //0x1ed52f258
NSLog(@"%p", cls3); //0x1ed52f258
NSLog(@"%p", cls4); //0x1ed52f258
NSLog(@"%p", cls5); //0x1ed52f230 真正的元类
NSLog(@"%p", cls6); //0x1ed52f230 真正的元类
- objc_getClass的实现,是通过查找mapPair(字典)的形式获取类(所以不准确)
Class objc_getClass(const char *aClassName)
{
if (!aClassName) return Nil;
// NO unconnected, YES class handler
return look_up_class(aClassName, NO, YES);
}
Class look_up_class(const char *name,
bool includeUnconnected __attribute__((unused)),
bool includeClassHandler __attribute__((unused)))
{
if (!name) return nil;
Class result;
bool unrealized;
{
runtimeLock.lock();
result = getClassExceptSomeSwift(name);
unrealized = result && !result->isRealized();
}
}
static Class getClassExceptSomeSwift(const char *name)
{
runtimeLock.assertLocked();
// Try name as-is
Class result = getClass_impl(name);
if (result) return result;
// Try Swift-mangled equivalent of the given name.
if (char *swName = copySwiftV1MangledName(name)) {
result = getClass_impl(swName);
free(swName);
return result;
}
return nil;
}
static Class getClass_impl(const char *name)
{
// Try runtime-allocated table
Class result = (Class)NXMapGet(gdb_objc_realized_classes, name);
}
- object_getClass的实现,获取的是isa指针指向的类(准确)
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
inline Class
objc_object::getIsa()
{
if (fastpath(!isTaggedPointer())) return ISA();
extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
uintptr_t slot, ptr = (uintptr_t)this;
Class cls;
slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
cls = objc_tag_classes[slot];
if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
cls = objc_tag_ext_classes[slot];
}
return cls;
}
- isa指针,用来找到它所属的类的(本类)
//实例
NSObject *objc = [[NSObject alloc] init];
//类
Class cls = objc.class;
NSLog(@"%@", objc); //objc的内存地址是:0x0000000281950320 存储的isa是0x000001a1ed52f259
NSLog(@"%@", cls); //cls的地址是:0x00000001ed52f258
// 发现objc的isa指针并不是cls
// 发现 p/x 0x000001a1ed52f259 & 0x0000000ffffffff8ULL = 0x00000001ed52f258
// 也就说,isa需要 & 一个值才是真正的类的地址 (ISA_MASK 0x0000000ffffffff8ULL)
// 按位与,可以取出特定位置的值(特定位置都为1,非指定位置都为0)
- superClass指针,用来找到父类(父类)
- 类和元类都属于
Class
结构体, 所以他们的结构一样,但是用途不一样
五. + (void)load方法调用时机?
//1.从objc-os.mm文件的void _objc_init(void)方法进来
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
//2.进入load_images
//判断是否有load方法,没有直接返回
if (!hasLoadMethods((const headerType *)mh)) return;
//3.发现load方法
prepare_load_methods((const headerType *)mh);
//4.添加到loadable_classes表中去,这个表中维护了所有类,及其对应的load方法的IMP
schedule_class_load(remapClass(classlist[i]));
//内部实现:先递归添加父类的load方法
schedule_class_load(cls->superclass);
//5.获取非懒加载Category的列表,添加到loadable_categories表中去
add_category_to_loadable_list(cat);
//6.调用load方法
call_load_methods();
//内部实现
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
//调用class的load方法
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
//调用Category的load方法
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
-
+ (load)(在main函数开始执行之前,调用顺序和编译顺序无关,并且只调用一次)
的顺序是先执行父类中的+load,然后是子类,再然后执行category中的+load如果有多个category,那么就按照编译的顺序来调用+load
. - 而且分类的load()方法不会覆盖原来类的load()方法,也就是说所有的load方法都会执行.
六.+ (void)initialize方法调用时机?
- 在类第一次接收到消息的时候调用,比如第一次alloc的时候.
- 调用子类的
initialize()
时候,会先调用父类的initialize()
.毕竟你爸爸都不存在,你还怎么玩继承呢?
- catagory的initialize()方法会覆盖原来类的initialize()方法(这是和load()方法的区别)
解释:initialize()方法走的是msg_send(),所以catagory的initialize()方法调用顺序优先级要高于原来类的优先级源码: void callInitialize(Class cls) { ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize); asm(""); }
七. 打印结果是什么?
创建了一个son类和father类,他们都有eat()
@interface Father : NSObject
- (void)eat;
@end
@implementation Father
- (void)eat
{
}
@end
@interface Son : Father
- (void)eat;
@end
@implementation Son
- (void)eat
{
NSLog(@"self的类为%@",[self class]);
NSLog(@"super的类为%@",[super class]);
}
@end
打印结果:
2019-01-17 16:39:36.548350+0800 demo[23333:6167589] self的类为Son
2019-01-17 16:39:36.548401+0800 demo[23333:6167589] super的类为Son
- 为什么
[super class]
的值也是Son呢?
- 使用
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
命令得到c++代码,搜索_Son_eat
做删减后得到源码
static void _I_Son_eat(Son * self, SEL _cmd) {
NSLog( (objc_msgSend) (self, sel_registerName("class")) );
NSLog((objc_msgSendSuper)({self, Father.class}, sel_registerName("class")));
}
- 第一个NSLog是给self发送class消息.
因为son没有class()
方法,所以会找Father类.Father类也没有所以会找到NSObject的class()
方法,所以会返回Son - 第二个NSLog里面调用了
objc_msgSendSuper()
方法,查看objc_msgSendSuper()
的源码
里面需要传入一个结构体和SEL,和我们简化过的c++代码吻合
#objc_msgSendSuper
#方法说明:给一个实例对象的父类发送一条消息
#参数1:是一个objc_super结构体,包含了接受消息的实例对象和开始搜索方法实现的超类
#参数2:@SEL 发送的消息
OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
-
struct objc_super
结构体的源码为
这里的receiver为刚才我们打印出来的self, super_class为Father.class.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
__unsafe_unretained _Nonnull Class super_class;
};
- 从
objc_msgSendSuper()
方法我们可以得出:
第二个NSLog的内容其实是给self发送class消息,而且查找class()
这个方法的起始地为Father这个类的cache,然后是Father这个类的msthodList,然后是Father的父类NSObject.
所以:既然还是给self发送消息,那么打印的值仍然是self的类Son
拓展
我们平时写得
self = [super init]
,其实是告诉系统去父类的methodList中查找方法而已,并不是初始化父类....
八. weak和unsafe_unretain
相同点:都不会retain对象
不同点:weak会将对象置为nil,而unsafe_unretain不会,unsafe_unretain容易引起野指针的问题。
Fianl. 拓展
- objc源码, 包含了
Class class_getInstanceSize
等的实现 - libmalloc源码, 包含了
calloc
等的实现
九.为什么UIKit不是线程安全的
- 成本较大,性价比低.
十.property里面的atomic和nonatomic是怎样实现的?
- 其实是使用了自旋锁(自旋锁适合消耗时间短的操作,而set方法刚好满足,所以使用自旋锁比使用synchronized更合适),看下面的代码
- atomic保证了同一时刻只有一个set方法能执行,但不保证get方法.(多读单写)
- atomic并不安全,直接调用 _age=10就跳过了set方法...
if (!atomic)
{
oldValue = *slot;
*slot = newValue;
}
else
{
spin_lock_t *slotlock = &PropertyLocks[GOODHASH(slot)];
_spin_lock(slotlock);
oldValue = *slot;
*slot = newValue;
_spin_unlock(slotlock);
}
十一.优化
1.CALayer替代UIView2
2.提前计算好布局
3.不适用autolayout
4.把耗时操作放到子线程
5.减少图层
6.减少透明视图的存在.
7.避免离屏渲染
8.减少、合并动态库
9.减少类、分类
10.不要把所有的东西方法didfinishLaunch
十二.信号量和互斥锁的区别
- 信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作(大家都在semtake的时候,就阻塞在 哪里)
- 而互斥锁是用在多线程多任务互斥的,一个线程占用了某一个资源,那么别的线程就无法访问,直到这个线程unlock,其他的线程才开始可以利用这个资源。比如对全局变量的访问,有时要加锁,操作完了,在解锁。有的时候锁和信号量会同时使用的
- 也就是说,信号量不一定是锁定某一个资源,而是流程上的概念,比如:有A,B两个线程,B线程要等A线程完成某一任务以后再进行自己下面的步骤,这个任务 并不一定是锁定某一资源,还可以是进行一些计算或者数据处理之类。而线程互斥量则是“锁住某一资源”的概念,在锁定期间内,其他线程无法对被保护的数据进行操作。在有些情况下两者可以互换。
互斥量和信号量的区别
- 互斥量用于线程的互斥,信号量用于线程的同步。
这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。
互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源 - 互斥量值只能为0/1,信号量值可以为非负整数。
也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。 - 互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。
总结:
1.信号量用于同步,互斥锁用于互斥.信号量不会锁定资源,而互斥锁会锁定资源.
2.信号量的值为非负整数,互斥锁只有0和1.
3.信号量可以在一个线程释放,在另一线程得到.而互斥锁只能在同一线程使用.
十三.iOS开发中静态库和动态库区别
静态库和动态库是相对编译期和运行期的:静态库在程序编译时会被链接到目标代码中,程序运行时将不再需要改静态库;而动态库在程序编译时并不会被链接到目标代码中,只是在程序运行时才被载入,因为在程序运行期间还需要动态库的存在。
动态库形式:.dylib和.framework
静态库形式:.a和.framework
使用静态库的好处
1,模块化,分工合作
2,避免少量改动经常导致大量的重复编译连接
3,也可以重用,注意不是共享使用
使用动态库的好处
1,使用动态库,可以将最终可执行文件体积缩小
2,使用动态库,多个应用程序共享内存中得同一份库文件,节省资源
3,使用动态库,可以不重新编译连接可执行程序的前提下,更新动态库文件达到更新应用程序的目的。
- 库里面都是函数,是一种可执行代码的二进制格式,可以被载入内存中执行
- 静态库:
- 在程序编译时已经加入到可执行文件中
- 不能再多个程序间共享
- .a或.framework结尾
- 动态库:
- 在程序运行时才去载入该库
- 可以多个程序间共享
- .tbd或.framework结尾
- tips:苹果系统提供的framework(UIKit等)全部属于动态库,可在多个程序间共享.
动态库和静态库的使用时机
- iOS8以后苹果允许上架自写动态库,但是会对动态库签名,所以不可以在线更新动态库
- 一般都选择制作静态库
- 动态库可以使用的两个地方 1.App Extention 2. 企业级应用
十四.数据源同步解决方案
- 并发访问,数据拷贝
- 串行访问
十五. 通知的实现原理
大概为:
在内存中维护了一个dic,key为通知的名称,value为所有监听者所在的arr,
当发送通知时,从arr拿出监听者并执行对应的方法
十九. @dynamic和@synthesize区别
- @synthesize age = _age, 会自动生成property的get和set方法
- @dynamic age, 不会生成property的get和set方法, 需要手动生成。如果不手动生成,则会包dosenotRecognizeSelector错误
二十. copy assgin strong weak关键字的区别
- assign,针对普通常量,直接赋值
@property (nonatomic, assign) NSInteger age;
- (void)setAge:(NSInteger)age {
_age = age;
}
- strong,针对对象,不直接赋值,会帮你处理引用计数
@property (nonatomic, strong) NSObject *obj;
- (void)setObj:(NSObject *)obj {
if (_obj != obj) {
[_obj release];
_obj = [obj retain];
}
}
- copy,针对不同的数据类型,有不同的操作
这个会崩溃'Attempt to mutate immutable object with appendString:
,说你给不可变数组使用了appendString
方法。
因为copy将可变字符串改变成了不可变字符串。
数组同理。
@property (nonatomic, copy) NSMutableString *muStr;
self.muStr = [NSMutableString string];
[self.muStr appendString:@"666"];
//底层实现
@property (nonatomic, copy) NSMutableString *muStr;
- (NSMutableString *)muStr {
if (_muStr != muStr) {
[_muStr release];
_muStr = [muStr copy];//这块会将可变数组变成不可变数组
}
return _muStr;
}
@property (nonatomic, strong) NSMutableString *muStr;
- (NSMutableString *)muStr {
if (_muStr != muStr) {
[_muStr release];
_muStr = [muStr retain];//使用retain,只会增加引用计数
}
return _muStr;
}
如果block使用copy,则是把block从栈上copy到堆上,挺好的。
- oc的api里面修饰字符串都是copy.
- 如果字符串赋的值仍然是NSString,那么copy和strong都一样。
- 如果字符串赋的值是NSMutableString, 那么copy会走
[NSString copy]
, strong会走[NSString retain]
.这个时候如果NSMutableString值发生变化,copy能保值字符串不会随着NSMutableString值而变化 - 所以对于不可变字符串,还是建议使用copy
@property (nonatomic, copy) NSString *cpString;
@property (nonatomic, strong) NSString *strongString;
NSMutableString *muString = [NSMutableString stringWithString:@"123"];
self.cpString = muString;
self.strongString = muString;
[muString appendString:@"666"];
NSLog(@"%@", self.cpString); //123
NSLog(@"%@", self.strongString);//123666