iOS-Objc基础

一. NSObjcet对象在内存中占多少个字节?

  1. 我觉得吧,对象是指针,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
  1. 看下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后就是结构体对齐过的.
  1. 看下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} */
  1. 搞个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个字节.

  1. 搞个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

  1. 所以,NSObjcet对象在内存中最少占用16个字节。

二. 那NSObjcet到底是个啥东西

    NSObject *objc = [[NSObject alloc] init];
  1. 使用xcrun指定编译的sdk, 将.m编译成cpp
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc clangtest.m -o clangtest.cpp
  2. 发现两个东西
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 };
  1. 对比一下, 发现xcode里面@interface NSObject就是cpp里面的struct NSObject_IMPL
@interface NSObject  {
    Class isa  OBJC_ISA_AVAILABILITY;
}
struct NSObject_IMPL {
    Class isa;
};
  1. 可以在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 *` */
  1. 从源码中找到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
}
  1. 所以说,NSObjcet是个类,是一个类对象, 内部存放的是对象需要的信息

三.什么是ISA

在上一节,我们知道struct objc_class : objc_object,objc_class继承自objc_object

  1. 看下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呢?
  1. 使用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")));
}
  1. 第一个NSLog是给self发送class消息.
    因为son没有class()方法,所以会找Father类.Father类也没有所以会找到NSObject的class()方法,所以会返回Son
  2. 第二个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);
  1. 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;
};
  1. 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线程完成某一任务以后再进行自己下面的步骤,这个任务 并不一定是锁定某一资源,还可以是进行一些计算或者数据处理之类。而线程互斥量则是“锁住某一资源”的概念,在锁定期间内,其他线程无法对被保护的数据进行操作。在有些情况下两者可以互换。
    互斥量和信号量的区别
  1. 互斥量用于线程的互斥,信号量用于线程的同步。
    这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。
    互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
    同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源
  2. 互斥量值只能为0/1,信号量值可以为非负整数。
    也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。
  3. 互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。

总结:
1.信号量用于同步,互斥锁用于互斥.信号量不会锁定资源,而互斥锁会锁定资源.
2.信号量的值为非负整数,互斥锁只有0和1.
3.信号量可以在一个线程释放,在另一线程得到.而互斥锁只能在同一线程使用.

十三.iOS开发中静态库和动态库区别

静态库和动态库是相对编译期和运行期的:静态库在程序编译时会被链接到目标代码中,程序运行时将不再需要改静态库;而动态库在程序编译时并不会被链接到目标代码中,只是在程序运行时才被载入,因为在程序运行期间还需要动态库的存在。

动态库形式:.dylib和.framework
静态库形式:.a和.framework

使用静态库的好处

1,模块化,分工合作
2,避免少量改动经常导致大量的重复编译连接
3,也可以重用,注意不是共享使用

使用动态库的好处

1,使用动态库,可以将最终可执行文件体积缩小
2,使用动态库,多个应用程序共享内存中得同一份库文件,节省资源
3,使用动态库,可以不重新编译连接可执行程序的前提下,更新动态库文件达到更新应用程序的目的。

  • 库里面都是函数,是一种可执行代码的二进制格式,可以被载入内存中执行
  • 静态库:
  1. 在程序编译时已经加入到可执行文件中
  2. 不能再多个程序间共享
  3. .a或.framework结尾
  • 动态库:
  1. 在程序运行时才去载入该库
  2. 可以多个程序间共享
  3. .tbd或.framework结尾
  • tips:苹果系统提供的framework(UIKit等)全部属于动态库,可在多个程序间共享.
动态库和静态库的使用时机
  • iOS8以后苹果允许上架自写动态库,但是会对动态库签名,所以不可以在线更新动态库
  • 一般都选择制作静态库
  • 动态库可以使用的两个地方 1.App Extention 2. 企业级应用

十四.数据源同步解决方案

  1. 并发访问,数据拷贝
  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.
  1. 如果字符串赋的值仍然是NSString,那么copy和strong都一样。
  2. 如果字符串赋的值是NSMutableString, 那么copy会走[NSString copy], strong会走[NSString retain].这个时候如果NSMutableString值发生变化,copy能保值字符串不会随着NSMutableString值而变化
  3. 所以对于不可变字符串,还是建议使用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

你可能感兴趣的:(iOS-Objc基础)