大三暑假了,各个公司的招聘都开始了,之前自学iOS都是比较零零散散,没有整体的知识体系,现在暑假在准备面试的时候,借着复习准备面试将之前学习的一些东西都整理出来,做个记录

下面是花了三天的时候阅读某宝典的整理和自己对一些知识点的总结

巧妙回答问题

  1. 谦虚严谨,不能回答那种自负或者自卑的样子
  2. 回答的时候不要什么都说,适当留下悬念
  3. 回答条理要清晰,最好三段式:场景/任务,行动,结果

回答技术问题

即使不会做,也要进行思考,把思路进行表达,而不是完全不回答或者放弃
1. 勇于提问(二义性问题,比如排序的是什么)
2. 高效设计,设计好了基础后,思考扩展性等鲁棒性
3. 征求同意,先写伪代码
4. 控制节奏,算法设计题,快速做完了需要仔细测试
5. 规范
6. 精心测试,对于一些边界测试条件都需要进行考虑

非技术问题

智力题,推理题,作文题??

估算类题目

抽丝剥茧,以常识去从小看到大的去计算。。。。

算法设计类

归纳法,相似法,简化法,递归法,分治法,Hash法,轮询法(每个算法题都以一个数据结构为载体,轮询看看哪个可能是的)

系统设计题

只要不涉及高并发,基本上采用google的 GFS + MapReducce + Bigtable来解决

GFS:可扩展的分布式文件系统,用于大型分布式对大量数据访问的应用,容错性,弥补数据库+sharing的缺点,其本身采用master/slave存在单点故障,元数据信息放在master端端内存,不适合存储小文件。

MapReduce:针对分布式并行计算的一套编程模型。自动备份,自动容错,隐藏跨机器间通信。

Bigtable:大表,存储结构化数据

不会回答的问题

本着实事求是的原则,去向面试官请教。在面试官说之前,先说说自己的想法,回答应该谦逊

不同观点

委婉一些回答

iOS开发基础知识

APP ID 和 Bundle ID

APP ID:苹果分发的,标示一个开发团队
Bundle ID:开发者自定义的,唯一标识一个应用的ID

静态库和动态库

  • 开源库
  • 闭源库:
    • 静态库: .a或者.framework文件,链接时被完整复制到可执行内存中,被使用多次就复制多份
    • 动态库:.dylib或者.framework文件,链接时不复制,程序运行时由dyld动态加载到内存中,供程序调用,共同调用,只加载一次

OC语言基础

self和super区别

super是编译器标识符,区别:self调用方法的时候从当前类中方法列表进行查找,使用super优先从父类的方法列表中寻找方法,但是本质上都指向同一个对象,只不过是调用方法的时候,一个从当前类找,一个从父类中找。

@synthesize和@dynamic表示什么,区别

属性 = 实例变量 + setter + getter方法,@dynamic告诉编译器不用为用户自动生成setter,getter方法
@synthesize:修饰的属性默认自动合成setter和getter方法,系统会自动向类中添加实例变量,实例变量名为属性前加上下划线。
@dynamic:禁止编译器自动合成属性的存取方法和默认的变量名,由程序员手动编写。

属性修饰关键字

原子性:atomic,nonatomic
读写:readwrite,readonly,getter,setter
内存管理语义:assign,weak,unsafe_unretained,retain,strong,copy

原子性:多线程中同一个变量可能被多个线程访问或者更改,造成数据污染,OC默认是atomic,会对setter进行加锁,保证多线程环境下访问时安全的,不过数据的加锁和解锁会造成系统资源的代价,用nonatomic,提高并发时的访问速度。
weak:不增加retaincount,修饰对象消失后自动将指针置为nil
unsafe_unretained:不是由编译器管理内存,会出现悬挂指针的现象

__strong:强引用来持有实例对象,增加retaincount
__weak:防止循环引用
__unsafe_unretained:声明一个弱引用,释放的时候指针不会被置为nil,与__weak相比,不需要遍历weak表来检查对象是否nil

什么时候使用weak assign和weak区别

weak:delegate和block的修饰 IBOutlet
区别:weak修饰的对象被释放后被置为nil,防止指针悬挂,assign修饰的一般都是纯量类型(c语言基础类型和CGFloat,NSInteger等)。weak只能修饰OC对象,assign还能修饰非OC对象

nonatomic和atomic区别,atomic绝对线程安全?

区别:对于属性的存取是否进行加锁。atomic修饰的话,编译器自动生成互斥锁,使得属性的getter和setter方法具有原子性,保证多线程环境下的数据一致性。nonatomic不加锁,造成读写数据不一致。
atomic修饰的数据在读写时原子性,大多情况能够保证读取一致性,但不是一定保证。例如:两个线程同时循环对某一属性进行增加,最终得到的结果并不一定是最终预期的结果。只要给线程中执行的代码块加锁就能够实现多线程访问的安全。

工厂方法

  • 一定是类方法
  • 返回值一定是id或者instancetype类型
  • 规范的方法名说返回什么对象

重载

不支持重载,支持同名方法不同参数个数不同的函数重载,因为添加到类的method_list中,不能存在相同函数签名的方法,因此不支持重载。

NSInteger与int区别

NSInteger是对int和long类型的封装,会识别当前操作系统的位数,自动返回最大的类型。NSUInteger是unsined long和unsined int的别名。不是NSNumber的子类,也不是NSObject的子类。

id声明的变量

id声明的对象指向任意的OC对象,不需要加 * ,在运行时才能够确定对象类型。本质上还是指针,与void*之间需要通过bridge关键字来显示桥接转换。

id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id objc = (__bridge id)p;

instancetype id

都是指向任意OC对象
区别:id可以用作变量,形参,返回值,将对象类型的确定放在程序运行时。
instancetype只能作为返回值,在编译器就确定了返回值类型。
关联返回类型:满足的任一条件:类方法中alloc或者new开头的,实例方法中以autorelease,init,retain或者self开头
非关联返回类型:方法的返回类型是id,然后无法调取类的任何方法。

使用instancetype的目的是让非关联对象返回类型的方法返回所在类的类型。

category优缺点

  • 优点:

    • 可以不改变一个类的情况下对一个已存在的类添加新方法
    • 可以在没有源代码的情况下对框架的类进行扩展
    • 减小单文件的体积
    • 按需加载不同的category
  • 缺点
    • 方法的扩展是硬编码,一个方法只能对应一个功能,不能动态改变
    • category中的方法优先级高于原类中的方法,类别中的方法如果同名可能会造成问题
    • 不能直接添加成员变量,只是添加成员变量比较麻烦
    • 可以添加属性,但是不会生成getter和setter方法,需要手动利用runtime的关联对象进行绑定
    • 同一个类的category中不能有重名方法

category的实现原理

objc-runtime-new.mm

struct category_t {const char *name;  //category的名称classref_t cls;  //扩展的类struct method_list_t *instanceMethods;    //添加的实例方法列表struct method_list_t *classMethods;       //添加的类方法列表struct protocol_list_t *protocols;        //添加的协议列表struct property_list_t *instanceProperties;    //添加的属性列表// Fields below this point are not always present on disk.struct property_list_t *_classProperties;method_list_t *methodsForMeta(bool isMeta) {if (isMeta) return classMethods;else return instanceMethods;}property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};

可以添加实例方法,类方法,协议和属性,但是不能添加实例变量,因为category没有存储实例变量对应的指针变量

category加载的详细步骤和调用过程

void _objc_init(void);       runtime的入口函数,进行初始化操作void map_images(..);        加锁void map_images_nolock(...);    完成所有类的注册和fixup等工作,包括初始化工作以及调用load类方法void _read_images(...);     完成类的加载,协议,类别的加载等工作void static void remethodizeClass(Class cls);   将类别绑定到目标类上attachCategories(Class cls,category_list *cats,bool flush_caches);  将类别中的方法和属性列表绑定到目标类上void attachLists(List* const * addedLists, uint32_t addedCount);    将目标类中的方法和分类中的方法放到一个列表中

category的实现原理中
_read_images:主要做了两个:1.将category和目标类绑定在一起;2.重建目标类的方法列表

_read_images中关于category部分之前的加载有
// Discover classes. Fix up unresolved future classes. Mark bundle classes.
// Fix up remapped classes
// Class list and nonlazy class list remain unremapped.
// Class refs and super refs are remapped for message dispatching.// Fix up @selector references
// Fix up old objc_msgSend_fixup call sites// Discover protocols. Fix up protocol refs.// Fix up @protocol references
// Preoptimized images may have the right
// answer already but we don't know for sure.// Realize non-lazy classes (for +load methods and static instances)
// Realize newly-resolved future classes, in case CF manipulates them// Discover categories. for (EACH_HEADER) {category_t **catlist = _getObjc2CategoryList(hi, &count);bool hasClassProperties = hi->info()->hasCategoryClassProperties();for (i = 0; i < count; i++) {category_t *cat = catlist[i];Class cls = remapClass(cat->cls);if (!cls) {// Category's target class is missing (probably weak-linked).// Disavow any knowledge of this category.catlist[i] = nil;if (PrintConnecting) {_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with ""missing weak-linked target class", cat->name, cat);}continue;}// Process this category. // First, register the category with its target class. // Then, rebuild the class's method lists (etc) if // the class is realized. bool classExists = NO;if (cat->instanceMethods ||  cat->protocols  ||  cat->instanceProperties) {//绑定分类和目标类addUnattachedCategoryForClass(cat, cls, hi);if (cls->isRealized()) {//重新构建实例方法列表remethodizeClass(cls);classExists = YES;}if (PrintConnecting) {_objc_inform("CLASS: found category -%s(%s) %s", cls->nameForLogging(), cat->name, classExists ? "on existing class" : "");}}if (cat->classMethods  ||  cat->protocols  ||  (hasClassProperties && cat->_classProperties)) {addUnattachedCategoryForClass(cat, cls->ISA(), hi);if (cls->ISA()->isRealized()) {//重新构建类方法列表remethodizeClass(cls->ISA());}if (PrintConnecting) {_objc_inform("CLASS: found category +%s(%s)", cls->nameForLogging(), cat->name);}}}}ts.log("IMAGE TIMES: discover categories");// Category discovery MUST BE LAST to avoid potential races // when other threads call the new category code before // this thread finishes its fixups.      <!--进行category和目标类的绑定函数-->
static void addUnattachedCategoryForClass(category_t *cat, Class cls, header_info *catHeader)
{runtimeLock.assertLocked();// DO NOT use cat->cls! cls may be cat->cls->isa insteadNXMapTable *cats = unattachedCategories();category_list *list;list = (category_list *)NXMapGet(cats, cls);if (!list) {list = (category_list *)calloc(sizeof(*list) + sizeof(list->list[0]), 1);} else {list = (category_list *)realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));}list->list[list->count++] = (locstamped_category_t){cat, catHeader};NXMapInsert(cats, cls, list);
}  <!--进行category中的方法进行合并-->
static void remethodizeClass(Class cls)
{category_list *cats;bool isMeta;runtimeLock.assertLocked();isMeta = cls->isMetaClass();// Re-methodizing: check for more categoriesif ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {if (PrintConnecting) {_objc_inform("CLASS: attaching categories to class '%s' %s", cls->nameForLogging(), isMeta ? "(meta)" : "");}attachCategories(cls, cats, true /*flush caches*/);        free(cats);}
}

attachCategories函数主要做分配一个新的方法列表空间,用来存放category中的实例方法,类方法和协议方法,属性列表,然后交给attachLists函数进行处理

// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order,
// oldest categories first.static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{if (!cats) return;if (PrintReplacedMethods) printReplacements(cls, cats);bool isMeta = cls->isMetaClass();// fixme rearrange to remove these intermediate allocationsmethod_list_t **mlists = (method_list_t **)malloc(cats->count * sizeof(*mlists));property_list_t **proplists = (property_list_t **)malloc(cats->count * sizeof(*proplists));protocol_list_t **protolists = (protocol_list_t **)malloc(cats->count * sizeof(*protolists));// Count backwards through cats to get newest categories firstint mcount = 0;int propcount = 0;int protocount = 0;int i = cats->count;bool fromBundle = NO;while (i--) {auto& entry = cats->list[i];method_list_t *mlist = entry.cat->methodsForMeta(isMeta);if (mlist) {mlists[mcount++] = mlist;fromBundle |= entry.hi->isBundle();}property_list_t *proplist = entry.cat->propertiesForMeta(isMeta, entry.hi);if (proplist) {proplists[propcount++] = proplist;}protocol_list_t *protolist = entry.cat->protocols;if (protolist) {protolists[protocount++] = protolist;}}auto rw = cls->data();prepareMethodLists(cls, mlists, mcount, NO, fromBundle);rw->methods.attachLists(mlists, mcount);free(mlists);if (flush_caches  &&  mcount > 0) flushCaches(cls);rw->properties.attachLists(proplists, propcount);free(proplists);rw->protocols.attachLists(protolists, protocount);free(protolists);
}

attachLists函数主要讲attachCategories中创建的方法列表与目标类中的方法列表融合在一起,变成一个方法列表

    void attachLists(List* const * addedLists, uint32_t addedCount) {if (addedCount == 0) return;if (hasArray()) {// many lists -> many listsuint32_t oldCount = array()->count;uint32_t newCount = oldCount + addedCount;setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));array()->count = newCount;memmove(array()->lists + addedCount, array()->lists, oldCount * sizeof(array()->lists[0]));memcpy(array()->lists, addedLists, addedCount * sizeof(array()->lists[0]));}else if (!list  &&  addedCount == 1) {// 0 lists -> 1 listlist = addedLists[0];} else {// 1 list -> many listsList* oldList = list;uint32_t oldCount = oldList ? 1 : 0;uint32_t newCount = oldCount + addedCount;setArray((array_t *)malloc(array_t::byteSize(newCount)));array()->count = newCount;if (oldList) array()->lists[addedCount] = oldList;memcpy(array()->lists, addedLists, addedCount * sizeof(array()->lists[0]));}}

memmove:用于复制字节,如果目标串和源串在空间上有重叠的区域化,其能够保证源串在被覆盖之前已将重叠区域的字节复制到目标区域中,但是复制后源内容会被更改。
memcpy:与memmove功能相同,但memcpy复制的源串和目标串在空间上不能有重叠,否则会产生无法预料的结果。
原码中先使用memmove将目标中的方法向后移动addedCount,然后使用mencpy将category中的方法或属性添加到原来的目标类的前面,这就解释了为什么category的优先级高,方法的查找是在method_list中找到后,立马进行返回,不需要继续往后查找。

category中使用关联对象生成属性的原理

涉及到的方法

id objc_getAssociatedObject(id object, const void *key) {return _object_get_associative_reference(object, (void *)key);
}void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {_object_set_associative_reference(object, (void *)key, value, policy);
}void objc_removeAssociatedObjects(id object)
{if (object && object->hasAssociatedObjects()) {_object_remove_assocations(object);}
}id _object_get_associative_reference(id object, void *key) {id value = nil;uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;{AssociationsManager manager;AssociationsHashMap &associations(manager.associations());disguised_ptr_t disguised_object = DISGUISE(object);AssociationsHashMap::iterator i = associations.find(disguised_object);if (i != associations.end()) {ObjectAssociationMap *refs = i->second;ObjectAssociationMap::iterator j = refs->find(key);if (j != refs->end()) {ObjcAssociation &entry = j->second;value = entry.value();policy = entry.policy();if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {objc_retain(value);}}}}if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {objc_autorelease(value);}return value;
}void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {// This code used to work when nil was passed for object and key. Some code// probably relies on that to not crash. Check and handle it explicitly.// rdar://problem/44094390if (!object && !value) return;assert(object);if (object->getIsa()->forbidsAssociatedObjects())_objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));// retain the new value (if any) outside the lock.ObjcAssociation old_association(0, nil);id new_value = value ? acquireValue(value, policy) : nil;{//初始化一个Associationsmanager对象,它维护了一个单例Hash表AssociationsHashMap对象AssociationsManager manager;//初始化一个AssociationsHashMap对象associations,用来维护对象和ObjectAssociationMap之间的关系,单例对象AssociationsHashMap &associations(manager.associations());//获得关联对象的索引disguised_ptr_t disguised_object = DISGUISE(object);if (new_value) {// break any existing association.//通过迭代器找到对应的ObjectAssociationMapAssociationsHashMap::iterator i = associations.find(disguised_object);if (i != associations.end()) {// secondary table exists// i->first表示对象地址// i->second 表示获取ObjectAssociationMapObjectAssociationMap *refs = i->second;ObjectAssociationMap::iterator j = refs->find(key);if (j != refs->end()) {old_association = j->second;//如果关联对象已存在,则通过ObjectAssociation赋新值j->second = ObjcAssociation(policy, new_value);} else {//不存在,则创建新的关联对象(*refs)[key] = ObjcAssociation(policy, new_value);}} else {// create the new association (first time).// 若没有ObjectAssociationMap表,则第一次创建一个ObjectAssociationMap表ObjectAssociationMap *refs = new ObjectAssociationMap;associations[disguised_object] = refs;(*refs)[key] = ObjcAssociation(policy, new_value);object->setHasAssociatedObjects();}} else {// setting the association to nil breaks the association.// 若new_value为空,删除该关联对象AssociationsHashMap::iterator i = associations.find(disguised_object);if (i !=  associations.end()) {ObjectAssociationMap *refs = i->second;ObjectAssociationMap::iterator j = refs->find(key);if (j != refs->end()) {old_association = j->second;refs->erase(j);}}}}// release the old value (outside of the lock).if (old_association.hasValue()) ReleaseValue()(old_association);
}void _object_remove_assocations(id object) {vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;{AssociationsManager manager;AssociationsHashMap &associations(manager.associations());if (associations.size() == 0) return;disguised_ptr_t disguised_object = DISGUISE(object);AssociationsHashMap::iterator i = associations.find(disguised_object);if (i != associations.end()) {// copy all of the associations that need to be removed.ObjectAssociationMap *refs = i->second;for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {elements.push_back(j->second);}// remove the secondary table.delete refs;associations.erase(i);}}// the calls to releaseValue() happen outside of the lock.for_each(elements.begin(), elements.end(), ReleaseValue());
}AssociationsHashMap是一个map,存储了disguised_ptr_t(object和unsigned操作的结果)和ObjectAssociationMap之间的映射,ObjectAssociationMap中存储了ObjectAssociation和方法名的映射

category中有load方法吗,什么时候调用,load能继承吗

load方法是基类NSObjct的类方法,但是不可被继承,因为load并不是通过消息传递(objc_msgSend)调用,而是通过函数指针直接调用的,因此load方法不存在类的层和遍历。category有load方法,但是与类的load方法不一样子,它并不是简单的继承或者覆盖类中的load方法,而是一个独立的类方法,也是通过函数指针调用的,并且与类中的load方法完全无关
当一个分类或类被添加到runtime中时会调用load方法。顺序如下:先调用父类的load方法,然后调用子类的,最后调用分类中的load方法.

void
load_images(const char *path __unused, const struct mach_header *mh)
{// Return without taking locks if there are no +load methods here.if (!hasLoadMethods((const headerType *)mh)) return;recursive_mutex_locker_t lock(loadMethodLock);// Discover load methods{mutex_locker_t lock2(runtimeLock);prepare_load_methods((const headerType *)mh);}// Call +load methods (without runtimeLock - re-entrant)call_load_methods();
}prepare_load_methods:表示在类和分类中找到所有满足条件的load方法
call_load_methods:调用所有的load方法void prepare_load_methods(const headerType *mhdr)
{size_t count, i;runtimeLock.assertLocked();//获取所有相关类,包括子类和父类classref_t *classlist = _getObjc2NonlazyClassList(mhdr, &count);for (i = 0; i < count; i++) {//递归查找父类,获取子类和父类所有的load方法schedule_class_load(remapClass(classlist[i]));}//获取所有相关分类category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);for (i = 0; i < count; i++) {category_t *cat = categorylist[i];Class cls = remapClass(cat->cls);if (!cls) continue;  // category for ignored weak-linked classif (cls->isSwiftStable()) {_objc_fatal("Swift class extensions and categories on Swift ""classes are not allowed to have +load methods");}realizeClassWithoutSwift(cls);assert(cls->ISA()->isRealized());//获取分类中的load方法add_category_to_loadable_list(cat);}
}void call_load_methods(void)
{static bool loading = NO;bool more_categories;loadMethodLock.assertLocked();// Re-entrant calls do nothing; the outermost call will finish the job.if (loading) return;loading = YES;void *pool = objc_autoreleasePoolPush();do {// 1. Repeatedly call class +loads until there aren't any morewhile (loadable_classes_used > 0) {//先调用类中的load方法call_class_loads();}// 2. Call category +loads ONCE类别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);loading = NO;
}
static void call_class_loads(void)
{int i;// Detach current loadable list.struct loadable_class *classes = loadable_classes;int used = loadable_classes_used;loadable_classes = nil;loadable_classes_allocated = 0;loadable_classes_used = 0;// Call all +loads for the detached list.//从父类到子类,遍历所有类中的load方法for (i = 0; i < used; i++) {Class cls = classes[i].cls;load_method_t load_method = (load_method_t)classes[i].method;if (!cls) continue; if (PrintLoading) {_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());}//通过函数指针调用load方法(*load_method)(cls, SEL_load);}// Destroy the detached list.if (classes) free(classes);
}

block原理 使用注意

https://juejin.im/post/5b0181e15188254270643e88

block中不能修改的自动变量的值

int main(int argc, const char * argv[]) {@autoreleasepool {int a = 10;void(^testBlock)(int i) = ^(int i){NSLog(@"a = %d",a);NSLog(@"i = %d",i);};a = 20;testBlock(a);}return 0;
}通过转换变成了下列的c++代码struct __block_impl {void *isa;int Flags;int Reserved;void *FuncPtr;
};struct __main_block_impl_0 {struct __block_impl impl;           //block的描述体struct __main_block_desc_0* Desc;   //block的描述对象,描述block的大小int a;                              //存放自动变量__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {impl.isa = &_NSConcreteStackBlock;      //block存放在栈上,用isa标记impl.Flags = flags;                     //标示位impl.FuncPtr = fp;                      //block的执行函数Desc = desc;}
};static void __main_block_func_0(struct __main_block_impl_0 *__cself, int i) {int a = __cself->a; // bound by copyNSLog((NSString *)&__NSConstantStringImpl__var_folders_z4_7tfczdsn6dxghdsxrcf6xyq40000gn_T_main_187588_mi_0,a);NSLog((NSString *)&__NSConstantStringImpl__var_folders_z4_7tfczdsn6dxghdsxrcf6xyq40000gn_T_main_187588_mi_1,i);}static struct __main_block_desc_0 {size_t reserved;size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};int main(int argc, const char * argv[]) {/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; int a = 10;void(*testBlock)(int i) = ((void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));a = 20;((void (*)(__block_impl *, int))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock, a);}return 0;
}简化成下面int main(int argc, const char * argv[]) {/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; int a = 10;__main_block_impl_0*testBlock = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a));a = 20;((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock, a);}return 0;
}
testBlock的实现其实是一个__main_block_impl_0结构体指针,__main_block_impl_0中的main表示block所在的函数,0表示第一个block。
testBlock的调用其实是调用了__block_impl对象的FuncPtr函数__main_block_impl_0中有一个成员变量a,用于保存构造函数传入的a,这里的a通过值传递的形式赋给成员变量a,后面block使用成员变量a。__main_block_impl_0是对 __block_impl的封装,__block_impl才是真正的block
struct __block_impl {void *isa;    //_NSConcreteStackBlock  _NSConcreteMallocBlock  _NSConcreteGlobalBlockint Flags;int Reserved;void *FuncPtr;        //指向__main_block_func_0,指向block的执行函数,即大括号内的代码逻辑
};存放在栈上的block,在函数的作用域结束后生命周期结束。
存放在堆上的block和对象一样子,retainCount为0时释放block,ARC环境下,编译器会自动插入release/retain来管理block的生命周期。
存放在全局的block生命周期和函数一样子,存在内存中,block和程序的生命周期相同static void __main_block_func_0(struct __main_block_impl_0 *__cself, int i) {int a = __cself->a; // bound by copy  //获取到的是block之前捕获到的aNSLog((NSString *)&__NSConstantStringImpl__var_folders_z4_7tfczdsn6dxghdsxrcf6xyq40000gn_T_main_187588_mi_0,a);NSLog((NSString *)&__NSConstantStringImpl__var_folders_z4_7tfczdsn6dxghdsxrcf6xyq40000gn_T_main_187588_mi_1,i);
}此时在block中不能修改变量a的值

block中可以修改自动变量的值

int main(int argc, const char * argv[]) {@autoreleasepool {__block int b = 10;void(^testBlock)(int i) = ^(int i){NSLog(@"修改之前b = %d",b);b = 30;};testBlock(b);NSLog(@"修改之后b = %d",b);}return 0;
}通过clang -rewrite-objc main.m进行转换成c++代码struct __Block_byref_b_0 {void *__isa;
__Block_byref_b_0 *__forwarding;int __flags;int __size;int b;
};struct __block_impl {void *isa;int Flags;int Reserved;void *FuncPtr;
};struct __Block_byref_b_0 {void *__isa;
__Block_byref_b_0 *__forwarding;int __flags;int __size;int b;
};struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;__Block_byref_b_0 *b; // by ref       //和之前比较,这里存储变量的方式不一样了//构造函数中不再是传值,而是直接对参数采用指针的方式传递,指针传递使得不会产生副本,直接操作本身。// 构造函数后面的 : b(b->__forwarding)表示在执行构造函数之前先执行__Block_byref_b_0 *b的构造函数,让__forwarding指针指向自身__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_b_0 *_b, int flags=0) : b(_b->__forwarding) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};static void __main_block_func_0(struct __main_block_impl_0 *__cself, int i) {__Block_byref_b_0 *b = __cself->b; // bound by refNSLog((NSString *)&__NSConstantStringImpl__var_folders_z4_7tfczdsn6dxghdsxrcf6xyq40000gn_T_main_ac1d91_mi_0,(b->__forwarding->b));(b->__forwarding->b) = 30;
}static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->b, (void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);}static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);}static struct __main_block_desc_0 {size_t reserved;size_t Block_size;void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};int main(int argc, const char * argv[]) {/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; __attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 10};void(*testBlock)(int i) = ((void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_b_0 *)&b, 570425344));(b.__forwarding->b) = 20;((void (*)(__block_impl *, int))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock, (b.__forwarding->b));NSLog((NSString *)&__NSConstantStringImpl__var_folders_z4_7tfczdsn6dxghdsxrcf6xyq40000gn_T_main_c04732_mi_1,(b.__forwarding->b));}return 0;
}去除类型,大致的样子如下int main(int argc, const char * argv[]) {/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; __Block_byref_b_0 b = {0,&b, 0, sizeof(__Block_byref_b_0), 10};__main_block_impl_0*testBlock = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &b, 570425344));(b.__forwarding->b) = 20;((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock, (b.__forwarding->b));NSLog((NSString *)&__NSConstantStringImpl__var_folders_z4_7tfczdsn6dxghdsxrcf6xyq40000gn_T_main_ac1d91_mi_1,(b.__forwarding->b));}return 0;
}
__block 修饰的变量其实是一个 __Block_byref_b_0 的结构体对象,而变量 b 其实是对属性 _forwarding->b 的修改__Block_byref_b_0结构体的结构
struct __Block_byref_b_0 {void *__isa;          //指向所存储的数据区
__Block_byref_b_0 *__forwarding;    //指向b对象真正存储的地方int __flags;int __size;            //大小int b;                 //保存b的值
};这里的__forwarding指针指向自身(:b(b->__forwarding)),指向自身的原因是因为block存放的地址有可能在栈或者堆上。在block没有发生复制的时候,__forwarding指向栈上的对象,当发生复制后,__forwarding指向堆上的对象。使用__forwarding快速找到存储b的区域。blcok发生复制时候的函数
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->b, (void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);}
使用__main_block_copy_0函数将block从栈复制到堆上,延长了变量b的生命周期,在栈上的变量被提前释放后,不会影响b的使用。ARC下,block从栈复制到堆上的几种情况:
1. 手动调用block的copy方法
2. 将block赋值给__strong修饰的对象,同时block中还要引用外部变量是
3. block作为函数的返回值
4. 想Cococa框架含有usingBlock的方法或者GCD的API传递block参数时候// 从堆中进行变量的移除
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);}当block进行copy操作的时候就会自动调用__main_block_desc_0内部的__main_block_copy_0函数,__main_block_copy_0函数内部会调用_Block_object_assign函数。
_Block_object_assign函数会自动根据__main_block_impl_0结构体内部的变量是什么类型的指针,对变量对象产生强引用或者弱引用。可以理解为_Block_object_assign函数内部会对变量进行引用计数器的操作,如果__main_block_impl_0结构体内变量指针是__strong类型,则为强引用,引用计数+1,如果__main_block_impl_0结构体内变量指针是__weak类型,则为弱引用,引用计数不变。当block从堆中移除时就会自动调用__main_block_desc_0中的__main_block_dispose_0函数,__main_block_dispose_0函数内部会调用_Block_object_dispose函数。_Block_object_dispose会对变量对象做释放操作,类似于release,也就是断开对变量对象的引用,而比纳凉究竟是否被释放还是取决于变量对象自己的引用计数。

对于不同的变量block进行捕获

  • auto 自动变量可能会销毁,block在执行的时候有可能自动变量已经被销毁了,那么此时如果再去访问被销毁的地址肯定会发生坏内存访问,因此对于自动变量一定是值传递而不可能是指针传递了。
  • static 而静态变量不会被销毁,所以完全可以传递地址。而因为传递的是值得地址,所以在block调用之前修改地址中保存的值,block中的地址是不会变得。所以值会随之改变。
  • 全局变量 不需要捕获,直接访问就行,全局静态变量也是不捕获

局部变量因为跨函数访问所以需要捕获,全局变量在哪里都可以访问 ,所以不用捕获。

没有访问auto变量的block是__NSGlobalBlock__类型的,存放在数据段中。
访问了auto变量的block是__NSStackBlock__类型的,存放在栈中。
__NSStackBlock__类型的block调用copy成为__NSMallocBlock__类型并被复制存放在堆中

- (void)test
{void(^block)(void) = ^{NSLog(@"%@",self.name);NSLog(@"%@",_name);};block();
}

  • 一旦block中捕获的变量为对象类型,block结构体中的__main_block_desc_0会出两个参数copy和dispose。因为访问的是个对象,block希望拥有这个对象,就需要对对象进行引用,也就是进行内存管理的操作。比如说对对象进行retarn操作,因此一旦block捕获的变量是对象类型就会会自动生成copy和dispose来对内部引用的对象进行内存管理。
  • 当block内部访问了对象类型的auto变量时,如果block是在栈上,block内部不会对person产生强引用。不论block结构体内部的变量是__strong修饰还是__weak修饰,都不会对变量产生强引用。
  • 如果block被拷贝到堆上。copy函数会调用_Block_object_assign函数,根据auto变量的修饰符(__strong,__weak,unsafe_unretained)做出相应的操作,形成强引用或者弱引用
  • 如果block从堆中移除,dispose函数会调用_Block_object_dispose函数,自动释放引用的auto变量。

如何解决block循环引用

  1. 在block内部使用完变量后,手动将一方置为nil
  2. 使用weak进行属性的修饰引用的变量
  3. 使用weak-strong dance来解决。__weak typeof(self) weakSelf = self ; __strong typeof(self) strongSelf = weakSelf;
  4. 使用 Heap-Stack Dance方式。 self.myblock = ^(Test *test){ NSLog(@"%@",test.name); }. slef.myblock(self); 这样子参数在栈上,由系统进行管理,不用担心相互持有的情况。

__block 和 __weak修饰符的区别

__block修饰可修改和重新赋值的变量
__weak修饰弱引用变量,避免循环引用
区别:

  1. __block 不管ARC还是MRC都能使用,可以修饰对象,也可以修饰基本数据类型
  2. __block 对象可以在block中被重新赋值,__weak不可以
  3. __block对象在ARC下可能会导致循环引用,非ARC下会避免循环引用。__weak只能在ARC下使用,可以避免
  4. __weak只能在ARC下使用,并且只能修饰对象,不能修饰基本数据类型。

OC的load和initialize

都可以做一些类的初始化工作
不同:load不能继承,通过函数指针调用。initialize可以继承,通过消息传递调用。调用时机:load在runtime时调用,initialize在对象第一次接收到消息的时候调用,一般load比initialize早。

copy

既可以作为深复制,也可作为浅复制。浅复制是对象的指针,深复制是复制对象的内容,但是都是产生不可变的对象。

OC语言高级特性

runtime

什么是runtime

运行时,不仅使得OC能够正常运行,还使得OC具有动态的特性。表现为:运行时动态地创建类和对象,进行消息传递和转发。
其实是将代码编译期间的决策推迟到运行时再决定,只有在运行时才去检查对象的类型和方法实现。

isa指针,作用

对象中的isa用来指向它的类,通过isa访问所有它的父类。

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;   //指向一个OC的类/// A pointer to an instance of a class.
typedef struct objc_object *id;     //指向一个实例对象/// Represents an instance of a class.
struct objc_object {Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};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;objc_object和objc_class两个中的isa指针指向的地方不同。objc_object中的isa指向他的父类,object本身不维护实例变量方法和协议等,直接通过isa指针指向父类进行查找,找不到通过super_class去父类中进行查找,直到RootClass。objc_class的isa指向MetaClass。每个类都会有一个单独的元类,元类也是一个类,其内部的isa指针指向父元类。任何NSObject子类的元类都使用NSObject的元类作为自己的所属类。而基类的元类中的isa指针指向自己。isa指针维护对象和类之间的关系,确保对象和类能够通过isa指针找到对应的变量,方法和协议。


non-pointer isa 和 Tagged Pointer

唐巧

https://www.mikeash.com/pyblog/friday-qa-2012-07-27-lets-build-tagged-pointers.html

https://www.jianshu.com/p/58d00e910b1e

Tagged Pointer 标记指针,是一种味了提高iOS和macOS性能而设计的内存优化技术。在c++中为了速度,存在内存对齐。在64位中,一个OC为8字节,内存对齐后,存在许多位都是0,浪费空间。
Apple引入 Tagged Pointer ,将指针一分为二,一部分存储数据,一部分作为特殊标记。Tagged Pointer将0变为1,使得这些位具有特殊含义。

在64位ios或macOS中,若对象指针的最低有效位为1(即奇数),则该指针式Tagged Pointer。这种指针不是通过isa来获取其所属类,而是通过指针最后2-4位的值作为一个类的索引。该索引来查找所属类,剩下的60位留给类来存储其他数据。

  • Tagged Pointer专门用来存储小的对象,例如NSNumber和NSDate
  • Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储在堆中,也不需要 malloc 和 free。
  • 在内存读取上有着 3 倍的效率,创建时比以前快 106 倍。
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}#if OBJC_MSB_TAGGED_POINTERS
#   define _OBJC_TAG_MASK (1UL<<63)
#else
#   define _OBJC_TAG_MASK 1UL
#endif#if (TARGET_OS_OSX || TARGET_OS_IOSMAC) && __x86_64__// 64-bit Mac - tag bit is LSB
#   define OBJC_MSB_TAGGED_POINTERS 0
#else// Everything else - tag bit is MSB
#   define OBJC_MSB_TAGGED_POINTERS 1
#endif// 60-bit payloadsOBJC_TAG_NSAtom            = 0, OBJC_TAG_1                 = 1, OBJC_TAG_NSString          = 2, OBJC_TAG_NSNumber          = 3, OBJC_TAG_NSIndexPath       = 4, OBJC_TAG_NSManagedObjectID = 5, OBJC_TAG_NSDate            = 6,// 60-bit reservedOBJC_TAG_RESERVED_7        = 7, // 52-bit payloadsOBJC_TAG_Photos_1          = 8,OBJC_TAG_Photos_2          = 9,OBJC_TAG_Photos_3          = 10,OBJC_TAG_Photos_4          = 11,OBJC_TAG_XPC_1             = 12,OBJC_TAG_XPC_2             = 13,OBJC_TAG_XPC_3             = 14,OBJC_TAG_XPC_4             = 15,OBJC_TAG_First60BitPayload = 0, OBJC_TAG_Last60BitPayload  = 6, OBJC_TAG_First52BitPayload = 8, OBJC_TAG_Last52BitPayload  = 263, OBJC_TAG_RESERVED_264      = 264

测试Tagged Pointer

    NSNumber *number1 = @1;NSLog(@"number1 %p %@", number1, [number1 class]);NSNumber *number2 = @2;NSLog(@"number2 %p %@", number2, [number2 class]);NSString *a = [[@"a" mutableCopy] copy];NSLog(@"a %p %@", a, [a class]);NSString *ab = [[@"ab" mutableCopy] copy];NSLog(@"ab %p %@", ab, [ab class]);/**mac2020-07-19 11:08:17.872495+0800 Tagged Pointer测试[7440:448114] number1 0xb2cdf2922e0662ab __NSCFNumber2020-07-19 11:08:17.872619+0800 Tagged Pointer测试[7440:448114] number2 0xb2cdf2922e06629b __NSCFNumber2020-07-19 11:08:17.872724+0800 Tagged Pointer测试[7440:448114] a 0xa2cdf2922e0664a8 NSTaggedPointerString2020-07-19 11:08:17.872811+0800 Tagged Pointer测试[7440:448114] ab 0xa2cdf2922e0044ab NSTaggedPointerString2020-07-19 11:15:33.484251+0800 Tagged Pointer测试[2426:258006] number1 0xd211866c8e47ae0f __NSCFNumber2020-07-19 11:15:33.484362+0800 Tagged Pointer测试[2426:258006] number2 0xd211866c8e47ae3f __NSCFNumber2020-07-19 11:15:33.484445+0800 Tagged Pointer测试[2426:258006] a 0xc211866c8e47a80c NSTaggedPointerString2020-07-19 11:15:33.484549+0800 Tagged Pointer测试[2426:258006] ab 0xc211866c8e41880f NSTaggedPointerString*/UTF-8编码兼容ASCII编码,一个ASCII字符为一个字节,4个字节编码其他的Unicode字符,NSString中可以在60位放7个字节,4位表示长度,这样子存储7个ASCII子鼓或者一些非ASCII字符。

NSString *obj = [[NSData alloc] init]; 编译和运行时obj分别是什么类型

编译的时候编译器不会进行类型检查,在编译时为NSString类型
运行时会动态的类型检查,obj为NSData类型,此时如果用NSString的方法会出现找不到方法的错误

runtime如何实现weak自动置为nil

runtime在注册和初始化一个类的时候,会对这个类拥有的实例变量和方法进行内存布局,对于weak修饰的变量,会将此变量指向的地址作为value放入一个hash表中,将weak变量的地址作为key,当引用计数为0的时候,系统自动将所有指向value的weak变量置为nil

/** * Called by dealloc; nils out all weak pointers that point to the * provided object so that they can no longer be used.* * @param weak_table * @param referent The object being deallocated. */
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{//找到弱引用指向的地址objc_object *referent = (objc_object *)referent_id;//找到管理referent的entry容器weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);if (entry == nil) {/// XXX shouldn't happen, but does with mismatched CF/objc//printf("XXX no entry for clear deallocating %p\n", referent);//entry为nil,则没有弱引用需要置为nilreturn;}// zero out referencesweak_referrer_t *referrers;size_t count;//判断弱引用个数是否超过 WEAK_INLINE_COUNTif (entry->out_of_line()) {//referrers是一个数组,用来存储所有指向referent_id的弱引用referrers = entry->referrers;count = TABLE_SIZE(entry); //弱引用数组的长度} else {//没有超过 WEAK_INLINE_COUNT 从inline_referrers数组获取//弱引用数组referrers = entry->inline_referrers;count = WEAK_INLINE_COUNT;}//将所有指向referent_id的弱引用全部置为nilfor (size_t i = 0; i < count; ++i) {objc_object **referrer = referrers[i];  //找到弱引用的指针地址if (referrer) {if (*referrer == referent) {*referrer = nil;}else if (*referrer) {_objc_inform("__weak variable at %p holds %p instead of %p. ""This is probably incorrect use of ""objc_storeWeak() and objc_loadWeak(). ""Break on objc_weak_error to debug.\n", referrer, (void*)*referrer, (void*)referent);objc_weak_error();}}}//从weak_table中移除对应的弱引用的管理容器weak_entry_remove(weak_table, entry);
}* Return the weak reference table entry for the given referent. * If there is no entry for referent, return NULL. * Performs a lookup.static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{assert(referent);weak_entry_t *weak_entries = weak_table->weak_entries;if (!weak_entries) return nil;size_t begin = hash_pointer(referent) & weak_table->mask;size_t index = begin;size_t hash_displacement = 0;while (weak_table->weak_entries[index].referent != referent) {index = (index+1) & weak_table->mask;if (index == begin) bad_weak_table(weak_table->weak_entries);hash_displacement++;if (hash_displacement > weak_table->max_hash_displacement) {return nil;}}return &weak_table->weak_entries[index];
}

weak变量赋值的过程

/** * Initialize a fresh weak pointer to some object location. * It would be used for code like: ** (The nil case) * __weak id weakPtr;* (The non-nil case) * NSObject *o = ...;* __weak id weakPtr = o;* * This function IS NOT thread-safe with respect to concurrent * modifications to the weak variable. (Concurrent weak clear is safe.)** @param location Address of __weak ptr. * @param newObj Object ptr. */
id
objc_initWeak(id *location, id newObj)
{if (!newObj) { //如果newObj为空,那么释放空间,即weak指针为nil*location = nil;return nil;}//newObj不为空,更新weak变量,并返回变量return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>(location, (objc_object*)newObj);
}// Update a weak variable.
// If HaveOld is true, the variable has an existing value
//   that needs to be cleaned up. This value might be nil.
// If HaveNew is true, there is a new value that needs to be
//   assigned into the variable. This value might be nil.
// If CrashIfDeallocating is true, the process is halted if newObj is
//   deallocating or newObj's class does not support weak references.
//   If CrashIfDeallocating is false, nil is stored instead.
enum CrashIfDeallocating {DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};
template <HaveOld haveOld, HaveNew haveNew,CrashIfDeallocating crashIfDeallocating>
static id
storeWeak(id *location, objc_object *newObj)
{assert(haveOld  ||  haveNew);if (!haveNew) assert(newObj == nil);Class previouslyInitializedClass = nil;id oldObj;SideTable *oldTable;    //用来存放已有的weak变量SideTable *newTable;    //用来存放新的weak变量// Acquire locks for old and new values.// Order by lock address to prevent lock ordering problems. // Retry if the old value changes underneath us.retry:if (haveOld) {oldObj = *location;         //取出已有对象(旧)oldTable = &SideTables()[oldObj];   //找到旧表} else {oldTable = nil;}if (haveNew) {  //有新值要更新newTable = &SideTables()[newObj];} else {newTable = nil;}//对oldTable和newTable分别进行加锁SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);//确认oldObj与当前location指向的值是否相同if (haveOld  &&  *location != oldObj) {SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);goto retry;}// Prevent a deadlock between the weak reference machinery// and the +initialize machinery by ensuring that no // weakly-referenced object has an un-+initialized isa.//初始化类if (haveNew  &&  newObj) {Class cls = newObj->getIsa();//如果类没初始化,则初始化if (cls != previouslyInitializedClass  &&  !((objc_class *)cls)->isInitialized()) {SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);//初始化clsclass_initialize(cls, (id)newObj);// If this class is finished with +initialize then we're good.// If this class is still running +initialize on this thread // (i.e. +initialize called storeWeak on an instance of itself)// then we may proceed but it will appear initializing and // not yet initialized to the check above.// Instead set previouslyInitializedClass to recognize it on retry.previouslyInitializedClass = cls;goto retry;}}// Clean up old value, if any.  清除旧值if (haveOld) {weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);}// Assign new value, if any.   赋值新值if (haveNew) {//将素有的weak指针重新指向新的对象newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating);// weak_register_no_lock returns nil if weak store should be rejected// Set is-weakly-referenced bit in refcount table.if (newObj  &&  !newObj->isTaggedPointer()) {newObj->setWeaklyReferenced_nolock();}// Do not set *location anywhere else. That would introduce a race.//location指向新的对象*location = (id)newObj;}else {// No new value. The storage is not changed.}SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);更新成功,返回新对象return (id)newObj;
}过程
1. 判断weak变量是否有新值需要更新,如果没有新,也没有旧的,停止更新
2. 从全局表SideTables中找到管理旧值对象的oldTable,并创建管理新值对象的newTable
3. 检查weak变量所属对象是否初始化,没有初始化就先初始化
4. 给weak变量赋新值钱,将所有的weak变量指向的旧对象一用移除
5. 将weak变量指向新对象

能否向编译后的类添加实例变量,能够向运行时创建的类添加实例变量

不能向编译后的类添加实例属性,但是能够向运行时创建的类添加实例变量。
因为OC时编译性语言,编译完成后,类的内存分布就完成了,类的实例变量列表ivars和方法列表methodLists大小固定了。
运行时创建的类只是通过alloc分配类的内存空间,没有对类的内部进行内存布局,内存布局是在类的初始化过程中完成的,因此可以添加实例变量。

运行时添加实例变量
//  创建新类Class myClass = objc_allocateClassPair([NSObject class], "myClass", 0);
//    添加ivarclass_addIvar(myClass, "_address", sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString *));class_addIvar(myClass, "_age", sizeof(NSUInteger), log2(sizeof(NSUInteger)), @encode(NSUInteger));
//    注册类objc_registerClassPair(myClass);
//    创建实例id object = [[myClass alloc] init];[object setValue:@"china" forKey:@"address"];[object setValue:@20 forKey:@"age"];NSLog(@"address = %@,age = %@",[object valueForKey:@"address"],[object valueForKey:@"age"]);
//    当类或者它的子类实例还在,不能调用objc_disposeClassPair 进行销毁object = nil;
//    销毁objc_disposeClassPair(myClass);objc_allocateClassPair:创建一个新类或者元类,如果想让他成为基类,superClass为nil,extraBytes是分配给类和元类对象尾部索引ivars的字节数,通常为0
objc_registerClassPair:只有注册后才能使用
objc_disposeClassPair:销毁一个类或者元类,如果程序运行中还存在类或者子类的实例,这个方法调用不了,会崩溃

向一个对象发送消息的整个过程

函数调用 == 消息发送/消息传递。
与C不同,函数调用在C语言编译的时候确定。OC中程序运行的时候才绑定。

OC借鉴Smalltalk,引用消息传递。

@interface Demo : NSObject
- (void)test;
@end
@implementation Demo
- (void)test{NSLog(@"%method test");
}
@endint main(int argc, const char * argv[]) {@autoreleasepool {Demo *demo = [[Demo alloc] init];[demo test];}return 0;
}过程1. demo对象通过isa找到它的类对象
2. 在类对象的缓存方法列表中寻找test方法
3. 如果缓存中没有找到,那么就去当前类的方法列表中寻找
4. 如果还是没有找到,通过superclass去父类的缓存列表或者方法列表中去寻找,找到了就放入当前类的方法缓存列表中(hash表)
5. 如果还是没有找到,那么就进行动态解析消息(Method Resolution class_resolveInstanceMethod/class_resolveClassMethod)
6. 如果消息解析后还是没有找到,那么就进行消息转发(Method Forwarding forwardingTargetForSelector methodSignatureForSelector forwardingInvocation)
7. 如果还是没有找到程序崩溃objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
self: object_class
SEL:Selector    @selector(method)用来唯一标示一个函数
参数:argumnets当调用一个方法时,编译器会从objc_msgSend、objc_msgSend_stret、objc_msgSendSuper、objc_msgSendSuper_stret。当给对象的父类发消息的时候使用objc_msgSendSuper,当数据结构作为返回值的时候会使用objc_msgSend_stret和objc_msgSendSuper_stret进行消息传递。大部分情况下,都是通过objc_msgSend进行入口。objc_msgSend采用汇编进行编写。
----------------------------------------------------------------------------------------------------------------ENTRY _objc_msgSendcbz  r0, LNilReceiver_fldr   r9, [r0]        // r9 = self->isaGetClassFromIsa            // r9 = classCacheLookup NORMAL// cache hit, IMP in r12, eq already set for nonstret forwardingbx  r12         // call impCacheLookup2 NORMAL// cache missldr  r9, [r0]        // r9 = self->isaGetClassFromIsa            // r9 = classb __objc_msgSend_uncachedLNilReceiver:// r0 is already zeromov    r1, #0mov   r2, #0mov   r3, #0FP_RETURN_ZERObx  lr  END_ENTRY _objc_msgSendENTRY _objc_msgLookupcbz r0, LNilReceiver_fldr   r9, [r0]        // r9 = self->isaGetClassFromIsa            // r9 = classCacheLookup NORMAL// cache hit, IMP in r12, eq already set for nonstret forwardingbx  lrCacheLookup2 NORMAL// cache missldr   r9, [r0]        // r9 = self->isaGetClassFromIsa            // r9 = classb __objc_msgLookup_uncachedLNilReceiver:MI_GET_ADDRESS(r12, __objc_msgNil)bx  lrEND_ENTRY _objc_msgLookupSTATIC_ENTRY __objc_msgNil// r0 is already zeromov   r1, #0mov   r2, #0mov   r3, #0FP_RETURN_ZERObx  lrEND_ENTRY __objc_msgNil
----------------------------------------------------------------------------------------------------------------
大致思路如下:
id objc_msgSend(id self,SEL _cmd,...){Class c = object_getClass(self);    //寻找类对象IMP imp = cache_lookup(c,_cmd);     //缓存中查找方法if(!imp)imp = class_getMethodImplementation(c,_cmd);    //类中查找方法return imp(self,_cmd,...);          //返回方法实现
}objc_msgSend汇编中主要的函数调用如下
__objc_msgLookup_uncachedMethodTableLookup //宏__class_lookupMethodAndLoadCache3IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{return lookUpImpOrForward(cls, sel, obj, YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup.
* initialize==NO tries to avoid +initialize (but sometimes fails)
* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use initialize==YES and cache==YES.
* inst is an instance of cls or a subclass thereof, or nil if none is known.
*   If cls is an un-initialized metaclass then a non-nil inst is faster.
* May return _objc_msgForward_impcache. IMPs destined for external use
*   must be converted to _objc_msgForward or _objc_msgForward_stret.
*   If you don't want forwarding at all, use lookUpImpOrNil() instead.
**********************************************************************/
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver)
{IMP imp = nil;bool triedResolver = NO;runtimeLock.assertUnlocked();// Optimistic cache lookupif (cache) {                         //cache 为NO 跳过缓存imp = cache_getImp(cls, sel);if (imp) return imp;}// runtimeLock is held during isRealized and isInitialized checking// to prevent races against concurrent realization.// runtimeLock is held during method search to make// method-lookup + cache-fill atomic with respect to method addition.// Otherwise, a category could be added but ignored indefinitely because// the cache was re-filled with the old value after the cache flush on// behalf of the category.runtimeLock.lock();                 //加锁checkIsKnownClass(cls);if (!cls->isRealized()) {cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);// runtimeLock may have been dropped but is now locked again}if (initialize && !cls->isInitialized()) {cls = initializeAndLeaveLocked(cls, inst, runtimeLock);// runtimeLock may have been dropped but is now locked again// If sel == initialize, class_initialize will send +initialize and // then the messenger will send +initialize again after this // procedure finishes. Of course, if this is not being called // from the messenger then it won't happen. 2778172}retry:    runtimeLock.assertLocked();// Try this class's cache.//加锁并且初始化后,再到类对象的缓存中寻找imp = cache_getImp(cls, sel);if (imp) goto done;             //找到了IMP 跳转到done//缓存中没有找到,先到当前类的类对象中查找// Try this class's method lists.{Method meth = getMethodNoSuper_nolock(cls, sel);if (meth) {//当前类找到并存入缓存,以后通过缓存进行访问log_and_fill_cache(cls, meth->imp, sel, inst, cls);imp = meth->imp;goto done;}}//当前类没有找到,再到父类中查找// Try superclass caches and method lists.{unsigned attempts = unreasonableClassCount();for (Class curClass = cls->superclass;curClass != nil;curClass = curClass->superclass){// Halt if there is a cycle in the superclass chain.if (--attempts == 0) {_objc_fatal("Memory corruption in class list.");}// Superclass cache.imp = cache_getImp(curClass, sel);if (imp) {if (imp != (IMP)_objc_msgForward_impcache) {//在父类中找到并存入缓存,以后通过缓存进行访问// Found the method in a superclass. Cache it in this class.log_and_fill_cache(cls, imp, sel, inst, curClass);goto done;}else {// Found a forward:: entry in a superclass.// Stop searching, but don't cache yet; call method // resolver for this class first.break;}}// Superclass method list.Method meth = getMethodNoSuper_nolock(curClass, sel);if (meth) {log_and_fill_cache(cls, meth->imp, sel, inst, curClass);imp = meth->imp;goto done;}}}// No implementation found. Try method resolver once.//上述过程都没有找到,进行方法的动态解析if (resolver  &&  !triedResolver) {runtimeLock.unlock();resolveMethod(cls, sel, inst);runtimeLock.lock();// Don't cache the result; we don't hold the lock so it may have // changed already. Re-do the search from scratch instead.triedResolver = YES;goto retry; //解析成功重新进行方法的查找}// No implementation found, and method resolver didn't help. // Use forwarding.//动态解析失败,进行最后的消息转发imp = (IMP)_objc_msgForward_impcache;cache_fill(cls, sel, imp, inst);done:runtimeLock.unlock();return imp;
}lookUpImpOrForward实现逻辑的大致过程:
1. 不加锁的缓存查重
2. 如果没有初始化类对象,那么初始化
3. 加锁
4. 到当前类对象的缓存中查找对应的方法,如果没有找到再到类对象中查找
5. 当前类和缓存中都没有找到,去父类的缓存中找,父类缓存也没,去父类的方法列表中查找
6. 若4-5重复直到父类为nil,还是没有找到对应的方法,进行消息的动态解析
7. 动态解析失败,进行最后的消息转发
8. 解锁
9. 返回IMP

OC 向一个nil对象发送消息发生什么

什么都不会发生,nil在OC中字面量是0.
根据nil的调用方法,分为如下情况:

  1. 如果方法返回值是一对象,给nil对象发消息返回nil
  2. 如果方法返回值为字面量类型,例如float、double、long double或者long long等整型,返回0
  3. 返回值为结构体,返回0,结构体中的字段都是0
  4. 如果不是上述提到的,返回值是未定义的

内存管理

ARC环境下,autoreleased对象什么时候释放

创建一个 autorelease 对象,这两种方式本质上是一样子的MRC
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//中间写代码
[pool release];ARC
@atuoreleasepool{//逻辑
}当一个对象发送autorelease消息的时候,这个对象被自动加入释放池,但是仍然是一个正当对象。可以在自动释放池作用域中向其发送消息。release立马释放,retaincount 立马减1
autorelease 在对象被使用完后才将retaincount减1

分析自动释放池的原理

int main(int argc, char * argv[]) {NSString * appDelegateClassName;@autoreleasepool {// Setup code that might create autoreleased objects goes here.appDelegateClassName = NSStringFromClass([AppDelegate class]);}return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}ios工程main.m中的@autoreleasepool负责应用所有对象的释放。因此,一般情况下不需要自己手动创建@autoreleasepool。每个应用程序事件循环(runloop)开始的时候,在主线程中创建一个自动释放池,该池被销毁的时候,autorelease对象都会被释放。

注意

  1. 最好不要用NSString来进行测试,因为NSString内容短的时候使用了Tagged Pointer,没有进行内存管理,出了生命周期后就释放。
  2. 通过alloc/new/copy/mutablecopy创建的对象都是release对象,编译器会自动释放,其他方法创建的对象才是autoreleased对象

原理

将main.m文件编译成c++文件,可以观察autoreleasepool
int main(int argc, const char * argv[]) {/* @autoreleasepool */ {__AtAutoreleasePool __autoreleasepool; }return 0;
}struct __AtAutoreleasePool {__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}void * atautoreleasepoolobj;
};伪代码工作流程
int main(int argc,const char * argv[]){{void * autoreleasepoolobj = objc_autoreleasePoolPush();//代码逻辑objc__autoreleasePoolPop(autoreleasepoolobj);}return 0;
}runtime源码中关于自动释放池的相关内容
class AutoreleasePoolPage
{// EMPTY_POOL_PLACEHOLDER is stored in TLS when exactly one pool is // pushed and it has never contained any objects. This saves memory // when the top level (i.e. libdispatch) pushes and pops pools but // never uses them.//EMPTY_POOL_PLACEHOLDER 表示一个空自动释放池的占位符
#   define EMPTY_POOL_PLACEHOLDER ((id*)1)#   define POOL_BOUNDARY nil。 //边界对象static pthread_key_t const key = AUTORELEASE_POOL_KEY;static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasingstatic size_t const SIZE =  //默认是4096字节
#if PROTECT_AUTORELEASEPOOLPAGE_MAX_SIZE;  // must be multiple of vm page size
#elsePAGE_MAX_SIZE;  // size and alignment, power of 2
#endifstatic size_t const COUNT = SIZE / sizeof(id);magic_t const magic;    //校验AutoreleasePoolPage的结构是否完整id *next;               //最新添加的autoreleased对象的下一个位置,初始化时间指向begin()pthread_t const thread; //当前线程AutoreleasePoolPage * const parent;AutoreleasePoolPage *child;uint32_t const depth;   //page深度,从0开始,往后递增1uint32_t hiwat;......
}

AutoreleasePoolPage是一个双向链表,结构图如下

void *
objc_autoreleasePoolPush(void)
{return AutoreleasePoolPage::push();
}void
objc_autoreleasePoolPop(void *ctxt)
{AutoreleasePoolPage::pop(ctxt);
}static inline void *push()
{id *dest;if (DebugPoolAllocation) {          //autoreleasepool出错时进入调试状态// Each autorelease pool starts on a new pool page.dest = autoreleaseNewPage(POOL_BOUNDARY);} else {//传入边界对象,用来区分不同的page对象dest = autoreleaseFast(POOL_BOUNDARY);}assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);return dest;
}static inline id *autoreleaseFast(id obj)
{AutoreleasePoolPage *page = hotPage();  //找到未满的pageif (page && !page->full()) {return page->add(obj);} else if (page) {return autoreleaseFullPage(obj, page);} else {return autoreleaseNoPage(obj);}
}
hotpage出现3种情况
1. 当前page存在但不满,通过page->add(obj)将需要放入自动释放池的obj对象添加到page中
2. page存在且满,通过autoreleaseFullPage(obj, page)创建一个新的page,将obj放入page中
3. page为nil,表示没有创建过,通过autoreleaseNoPage创建第一个page,放入obj
static inline AutoreleasePoolPage *hotPage()
{AutoreleasePoolPage *result = (AutoreleasePoolPage *)tls_get_direct(key);if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;if (result) result->fastcheck();return result;
}三种情况对应的
1. page存在但不满
id *add(id obj)
{assert(!full());unprotect();id *ret = next;  // faster than `return next-1` because of aliasing*next++ = obj;  //next指针指向当前添加位置的后一个protect();return ret;     //将当前添加的位置返回回去
}2. page存在且满
static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{// The hot page is full. // Step to the next non-full page, adding a new page if necessary.// Then add the object to that page.assert(page == hotPage());assert(page->full()  ||  DebugPoolAllocation);do {if (page->child) page = page->child;else page = new AutoreleasePoolPage(page);} while (page->full());setHotPage(page);return page->add(obj);
}
先找到最后一个page,如果最后一个没有满,就添加进去,如果满了创建新的page,然后添加3. page为nil
static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{// "No page" could mean no pool has been pushed// or an empty placeholder pool has been pushed and has no contents yetassert(!hotPage());bool pushExtraBoundary = false;if (haveEmptyPoolPlaceholder()) {// We are pushing a second pool over the empty placeholder pool// or pushing the first object into the empty placeholder pool.// Before doing that, push a pool boundary on behalf of the pool // that is currently represented by the empty placeholder.pushExtraBoundary = true;}else if (obj != POOL_BOUNDARY  &&  DebugMissingPools) {// We are pushing an object with no pool in place, // and no-pool debugging was requested by environment._objc_inform("MISSING POOLS: (%p) Object %p of class %s ""autoreleased with no pool in place - ""just leaking - break on ""objc_autoreleaseNoPool() to debug", pthread_self(), (void*)obj, object_getClassName(obj));objc_autoreleaseNoPool(obj);return nil;}else if (obj == POOL_BOUNDARY  &&  !DebugPoolAllocation) {// We are pushing a pool with no pool in place,// and alloc-per-pool debugging was not requested.// Install and return the empty pool placeholder.return setEmptyPoolPlaceholder();}// We are pushing an object or a non-placeholder'd pool.// Install the first page.AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);setHotPage(page);// Push a boundary on behalf of the previously-placeholder'd pool.if (pushExtraBoundary) {page->add(POOL_BOUNDARY);}// Push the requested object or pool.return page->add(obj);
}
第一个page创建过程
1. 先判断是否存在空的自动释放池
2. 没有的话,通过setEmptyPoolPlaceholder生成占位符,表示一个空的自动释放池
3. 创建一个page,先通过page->add(POOL_BOUNDARY)将边界符放入第一位置,后page->add(obj)添加static inline void pop(void *token)   //token表示需要销毁的autoreleasepool的边界对象
{AutoreleasePoolPage *page;id *stop;if (token == (void*)EMPTY_POOL_PLACEHOLDER) {// Popping the top-level placeholder pool.if (hotPage()) {// Pool was used. Pop its contents normally.// Pool pages remain allocated for re-use as usual.pop(coldPage()->begin());} else {// Pool was never used. Clear the placeholder.setHotPage(nil);}return;}page = pageForPointer(token);stop = (id *)token;if (*stop != POOL_BOUNDARY) {if (stop == page->begin()  &&  !page->parent) {// Start of coldest page may correctly not be POOL_BOUNDARY:// 1. top-level pool is popped, leaving the cold page in place// 2. an object is autoreleased with no pool} else {// Error. For bincompat purposes this is not// fatal in executables built with old SDKs.return badPop(token);}}if (PrintPoolHiwat) printHiwat();page->releaseUntil(stop);// memory: delete empty childrenif (DebugPoolAllocation  &&  page->empty()) {// special case: delete everything during page-per-pool debuggingAutoreleasePoolPage *parent = page->parent;page->kill();setHotPage(parent);} else if (DebugMissingPools  &&  page->empty()  &&  !page->parent) {// special case: delete everything for pop(top)// when debugging missing autorelease poolspage->kill();setHotPage(nil);}else if (page->child) {// hysteresis: keep one empty child if page is more than half fullif (page->lessThanHalfFull()) {page->child->kill();}else if (page->child->child) {page->child->child->kill();}}
}
执行过程:
1. 判断token是否为EMPTY_POOL_PLACEHOLDER,如果是,清空释放池
2. 不是的话,通过pageForPointer找到token所在的自动释放池的收地址
3. 通过releaseUntil方法将这个释放池对象全部销毁
4. 自动释放池内的对象销毁后,如果当前page还有子page,那么销毁子pagevoid releaseUntil(id *stop) {// Not recursive: we don't want to blow out the stack // if a thread accumulates a stupendous amount of garbagewhile (this->next != stop) {// Restart from hotPage() every time, in case -release // autoreleased more objectsAutoreleasePoolPage *page = hotPage();// fixme I think this `while` can be `if`, but I can't prove itwhile (page->empty()) {page = page->parent;setHotPage(page);}page->unprotect();//next指向最后一个对象的后一个位置,需要先减1,才能获取到id obj = *--page->next;memset((void*)page->next, SCRIBBLE, sizeof(*page->next));page->protect();if (obj != POOL_BOUNDARY) {objc_release(obj);}}setHotPage(this);#if DEBUG// we expect any children to be completely emptyfor (AutoreleasePoolPage *page = child; page; page = page->child) {assert(page->empty());}
#endif}

ARC下需不需要手动添加@autoreleasepool

一般情况下main函数中的@autoreleasepool都帮我们处理了所有的autoreleased对象,编译器帮我们处理了released对象。不过对于一些特殊情况,例如内存暴涨需要添加@autoreleasepool

enumerateObjectsUsingBlock在循环遍历的时候,会自动创建释放池,保证内存的稳定,for in循环不会,需要自己注意。

有些情况下需要手动添加@autoreleasepool

  1. 编写的程序不是基于UI框架的,比如命令行工具
  2. 循环中创建了大量的临时对象
  3. 创建了一个辅助线程

ARC下是否需要调用dealloc方法

ARC下OC对象声明周期都是ARC负责,但是非OC对象,如CoreFoundation中的对象无法被ARC管理,需要手动释放,释放的时机重写dealloc方法,并在dealloc方法中将非OC对象置为nil

runloop

循环机制的伪代码

function loop(){initialize();do {get(message);process_message(message);sleep();}while(message != quit)
}

runloop是一个与线程相关的底层机制,用来接收事件和任务调度。保证线程有工作的时候忙碌,没工作的时候睡眠。

线程和runloop一一对应,在某一时候,一个线程只能运行在某一个runloop上

当运行一个程序的时候,系统会为程序的主线程创建一个runloop用来处理主线程的事件(UI刷新,触摸事件)。对于一些辅助线程(子线程)需要显示的运行一个runloop,再将辅助线程放到runloop中,否则线程不会自动开启runloop,运行一次就结束了,无法一直使用。

例如

ios多线程和网络

多线程

自旋锁和互斥锁

临界区:一段能公共访问的代码,每次只允许一个线程访问

互斥锁:sleeping-waiting类型的锁,用于保护临界区,确保同一时刻只有一个线程访问。对于临界区先加锁,如果临界区已经被其他线程加锁,那么当前想进入临界区的线程阻塞睡眠,直到互斥锁被释放才唤醒。完成临界区的访问后,需要释放互斥量进行解锁。互斥锁的线程调度比较大开销。一般用于不经常切换线程且执行耗时的任务。
自旋锁:busy-waiting类型的锁,自旋锁不会进入睡眠,在没有获取到其他线程解锁之前一直处于盲等的状态,直到其他线程释放锁。自旋锁避免了线程切换的开销,在执行时间比较短的任务时,效率高。适用于需要频繁访问临界区的任务,时间短,不适用时间长的任务。

runtime中大量使用自旋锁。特别是引用计数操作上。伪代码表示自旋锁使用。

volatile int spin_lock = 0; //全局锁,一开始没上锁,任何线程都可以申请锁
void Critical(){do{                     //Acquire Lock  申请锁//test_and_set  是原子操作//如果spin_lock为1,这里会一直循环,等待spin_lock为0while(test_and_set(&spin_lock) == 1);       //分号 Critical section    //临界区//Release Lock        释放锁  别的线程可以进入临界区spin_lock = 0;Reminder section    //不需要锁保护的代码}
}
int test_and_set(int * spin_lock){int old_value = *spin_lock; if(old_value == 0)*spin_lock = 1;         //设置新值return old_value;
}test_and_set函数代表原子操作,用于检查spin_lock的值是否为0,如果是0,将spin_lock置1,否则一直检查spin_lock,直到spin_lock为0。TSL机制保证了自旋锁实现。TSL是Test and Set Lock,指令TSL RX, LOCK
1.读取Lock的值  2.把读到的值存入寄存器RX中   3.然后给LOCK设置一个非0的值(设置到LOCK对应的内存中)
以上三个步骤是一个不可拆分的原子操作,执行该指令的CPU将会锁住内存总线(memory bus),所以在该指令执行完成之前其他CPU是无法访问内存的。

自旋锁过程

  1. 从do开始进入循环状态,不断尝试获取锁
  2. 当获取锁后,进入临界区执行临界区代码,如果没有获取锁,一直在这里循环
  3. 执行完临界区代码后,释放锁
  4. 执行后面的代码

TSL和中断屏蔽的区别
当一个CPU将中断屏蔽后,只影响当前屏蔽中断的CPU,其他CPU还是依然可以照样访问内存的(想要中断)。唯一一个当一个CPU在访问内存时阻止其他CPU访问内存的方法就是将内存总线锁住,这个需要硬件的支持,TSL可以达到该目的。(此处需要深入理解:单处理器中使用中断屏蔽,即可在PSW中打开/关闭中断标志位,而由于上述原因,中断屏蔽对于多处理器根本没有效果

深入理解:需要区别忙等待和让权等待

  • 忙等待:连续测量一个变量直到某个值出现是为止。
  • 让权等待:临界区外运行的进程不得阻塞其他进程,出让cpu

深入理解:区别自旋锁和互斥锁

  • 自旋锁:表示一直在原地旋转,并未去睡眠。可能存在的问题:死锁,过多占用cpu资源
  • 互斥锁:如果资源被占用,资源申请者就会进入睡眠状态

互斥锁开销大于自旋锁,互斥锁一般用于I/O操作,临界区竞争激烈或者临界区代码量大的情况下;自旋锁开销小,临界区代码少,但是会一直占有CPU,一般用于频繁切换线程,临界区访问时间较短的任务和CPU不紧张情况下。

ios线程通信

https://blog.csdn.net/sharpyl/article/details/61016634
对象通信:block,KVO,代理
线程通信:

  1. performSelector选择器方式
  2. NSMachPort/CFMachPort(基于Port方式)
  3. GCD方式
performSelector选择器方式
[self performSelector:@selector(print) onThread:thread withObject:nil waitUntilDone:NO];
//和主线程通信
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
//和主线程通信
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
//和任意线程通信
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
//和任意线程通信
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg ;NSMachPort/CFMachPort方式通信
@interface NSMachPort : NSPort {@privateid _delegate;NSUInteger _flags;uint32_t _machPort;NSUInteger _reserved;
}self.remotePort = [NSMachPort port];self.localPort = [NSMachPort port];self.remotePort.delegate = self;self.localPort.delegate = self;[[NSRunLoop currentRunLoop] addPort:self.remotePort forMode:NSDefaultRunLoopMode];dispatch_async(dispatch_get_global_queue(0, 0), ^{NSLog(@"secondary thread: %@", [NSThread currentThread]);[[NSRunLoop currentRunLoop] addPort:self.localPort forMode:NSDefaultRunLoopMode];//很久很久以后才让它失效[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];});NSString *hello = @"hello,I am secondary thread";NSData *data = [hello dataUsingEncoding:NSUTF8StringEncoding];dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSMutableArray *dataArray = [NSMutableArray arrayWithArray:@[self.localPort,data]];//过2秒localPort向remotePort发送一条消息,第一个参数:发送时间。msgid 消息标识。//components,发送消息附带参数。reserved:为头部预留的字节数(从官方文档上看到的,猜测可能是类似请求头的东西...)[self.localPort sendBeforeDate:[NSDate date] msgid:1000 components:dataArray from:self.remotePort reserved:0];});-(void)handlePortMessage:(id )message{NSLog(@"收到消息了,线程为:%@",[NSThread currentThread]);//只能用KVC的方式取值NSArray *array = [message valueForKeyPath:@"components"];NSData *data =  array[1];NSString *s1 = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];NSLog(@"main:%@ secondary:%@ %@ %@",self.remotePort,self.localPort,array[0],s1);
//    NSLog(@"Stack:%@", [NSThread callStackSymbols]);
}secondary thread: <NSThread: 0x600002515e80>{number = 4, name = (null)}
收到消息了,线程为:<NSThread: 0x600002515e80>{number = 4, name = (null)}
main:<NSMachPort: 0x600000748210> secondary:<NSMachPort: 0x6000007482c0> <NSMachPort: 0x6000007482c0> hello,I am secondary thread

GCD 和 NSOperation区别 什么时候用GCD 什么时候用NSOperation

ios4.0后,NSOperation和GCD其实都是一样子的,底层都是用GCD实现的

GCD缺点:无法自动控制异步线程之间的并发顺序和依赖关系,需要手动实现,实现比较复杂,无法终止一个正在执行的进程,一旦执行只能等待结束。通常用于将某些耗时的任务放到子线程中执行,任务之间不具备顺序关系和依赖关系。

NSOperation是对GCD的高层抽象,通常配合NSOperationQueue一起使用。NSOperationQueue不支持FIFO,但是可以设置线程之间的优先级和依赖关系,队列中的线程依据优先级关系和依赖关系进行执行。能够设置线程的并发数量。通过cancel进行取消正在执行的任务,通过KVO进行观察NSOperation的状态。

原则:iOS开发优先选择上层接口,除非上层不能使用或者性能问题,才会使用底层接口,尽量使用NSOperation,而不是GCD。

NSOperation和NSOperationQueue处理ABC三个线程,要求执行完后再执行C

NSOperationQueue *queue = [NSOperationQueue mainQueue];
NSOperation *a = [NSBlockOperation blockOperationWithBlock:^{NSLog(@"Operation A start");[NSThread sleepForTimeInterval:3.0];NSLog(@"Operation A end");
}];
NSOperation *b = [NSBlockOperation blockOperationWithBlock:^{NSLog(@"Operation B start");[NSThread sleepForTimeInterval:3.0];NSLog(@"Operation B end");
}];
NSOperation *c = [NSBlockOperation blockOperationWithBlock:^{NSLog(@"Operation C start");[NSThread sleepForTimeInterval:3.0];NSLog(@"Operation C end");
}];
[c addDependency:a];
[c addDependency:b];[queue addOperation:a];
[queue addOperation:b];
[queue addOperation:c];dispatch_queue_t gcdQueue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
//使用自己创建的队列才行,使用系统的没有作用
//    针对dispatch_barrier_sync 如果你传入的是串行或全局并发队列 则它的作用和 dispatch_sync 一样; 如果是 dispatch_barrier_async 则它的作用和 dispatch_async一样
dispatch_async(gcdQueue, ^{NSLog(@"Operation A start");[NSThread sleepForTimeInterval:3.0];NSLog(@"Operation A end");
});
dispatch_async(gcdQueue, ^{NSLog(@"Operation B start");[NSThread sleepForTimeInterval:3.0];NSLog(@"Operation B end");
});
dispatch_barrier_async(gcdQueue, ^{[NSThread sleepForTimeInterval:2.0];NSLog(@"waiting for c");
});
NSLog(@"w++++++++");
dispatch_async(gcdQueue, ^{NSLog(@"Operation C start");[NSThread sleepForTimeInterval:3.0];NSLog(@"Operation C end");
});

多线程安全的解决方案

synchronized

隐式创建锁对象

-(void)testSynchronized{dispatch_async(dispatch_get_global_queue(0, 0), ^{@synchronized (self) {NSLog(@"线程A = %@",[NSThread currentThread]);self.rank = @"A";[NSThread sleepForTimeInterval:4.f];}});dispatch_async(dispatch_get_global_queue(0, 0), ^{@synchronized (self) {NSLog(@"线程B = %@",[NSThread currentThread]);self.rank = @"B";}});
}2020-07-20 23:32:24.219759+0800 NSoperationTest[7728:406022] 线程A = <NSThread: 0x60000115c7c0>{number = 6, name = (null)}
2020-07-20 23:32:28.221800+0800 NSoperationTest[7728:406024] 线程B = <NSThread: 0x60000115c740>{number = 5, name = (null)}

NSLock

-(void)testNSLock{NSLock *lock = [[NSLock alloc] init];dispatch_async(dispatch_get_global_queue(0, 0), ^{[lock lock];NSLog(@"线程A = %@",[NSThread currentThread]);self.rank = @"A";[NSThread sleepForTimeInterval:4.f];[lock unlock];});dispatch_async(dispatch_get_global_queue(0, 0), ^{[lock lock];NSLog(@"线程B = %@",[NSThread currentThread]);self.rank = @"B";[lock unlock];});
}2020-07-20 23:31:20.223445+0800 NSoperationTest[7715:404545] 线程A = <NSThread: 0x6000012e9780>{number = 3, name = (null)}
2020-07-20 23:31:24.223736+0800 NSoperationTest[7715:404532] 线程B = <NSThread: 0x6000012b42c0>{number = 6, name = (null)}

GCD

-(void)testGCD{dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);dispatch_async(dispatch_get_global_queue(0, 0), ^{dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);NSLog(@"线程A = %@",[NSThread currentThread]);self.rank = @"A";[NSThread sleepForTimeInterval:4.f];dispatch_semaphore_signal(semaphore);});dispatch_async(dispatch_get_global_queue(0, 0), ^{dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);NSLog(@"线程B = %@",[NSThread currentThread]);self.rank = @"B";dispatch_semaphore_signal(semaphore);});
}2020-07-20 23:35:01.907044+0800 NSoperationTest[7772:408844] 线程A = <NSThread: 0x600001482500>{number = 3, name = (null)}
2020-07-20 23:35:05.909183+0800 NSoperationTest[7772:408853] 线程B = <NSThread: 0x6000014f0b00>{number = 5, name = (null)}

网络编程

使用NSURLSession发送网络请求

ios7之后可以用NSURLConnection和NSURLSession进行网络请求,ios9之后支持IPV6,只支持NSURLSession请求。AFN3.X 底层实现全部改成了NSURLSession

//    创建session
NSURLSession *session = [NSURLSession sharedSession];
NSURL *url = [NSURL URLWithString:@"https://www.baidu.com"];
//    初始化一个task ,并在Block中进行返回数据的处理
NSURLSessionTask *task = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];NSLog(@"%@",string);
}];
//    启动任务
[task resume];默认是get请求
注意:NSURLSession本身并不会发送请求,真正发送请求对象的是 NSURLSessionTask ,并且每个task必须调用resum方法,才能进行网络请求的发送。

ios中对象之间的通信

delegate和block区别

delegate和block都是开发中常用的通信技术,delegate是从代理设计模式演变而来的,block是一种闭包的实现。

block

  • 优点:轻便,使用简单。能够直接访问上下文;易于阅读
  • 缺点:如果存在多个防范,需要为每个方法实现一个block;循环引用;出现内存的复制,成本高

delegate

  • 优点:设计更加清晰,规范;易于书写文档;多个相关方式是,delegate实现更加简单;成本低
  • 缺点:代码复杂;不宜阅读维护

delegate weak修饰,为什么不用strong和assgin

使用weka是为了避免循环引用,delegate指针不持有对象。如果用strong,对象持有delegate,delegate持有对象,发生循环引用。assign功能上和weak相同,但是被释放的时候,assign不会讲delegate设置为nil,会发生野指针。

KVC和KVO

KVC:键值编码,在NSKeyValueCoding非正式写一下使用字符串间接访问对象属性的一种机制。如果一个对象符合键值编码的约定,他的属性可以通过一个准确唯一的key(NSString)进行反问。

KVO Cocoa绑定 CoreData都是基于KVC的

KVO

基于KVC实现的一种观察者模式,提供某一属性变化的监听方法。

NSNotification,Delegate,Block和KVO区别

Delegate代理是一种回调机制,一对一关系。通知是基于观察者模式的一对多的关系,消息会发送所有注册为事件观察者的对象。Delegate比Notification执行效率高。

Block和Delegate一样,通常一对一,使用场景相同。在通信场景多的情况下最好使用Delegate

NSNotification,一对多场景。特点:需要被观察者先主动发出通知,然后观察者注册监听后再来进行相应,比KVO多了一步发送通知。优点:监听的不局限于属性的变化,可以对多种多样的状态变化进行监听,范围广。

KVO使用场景是数据的比那话,例如股票价格变化。Delegate使用场景是行为,一般需要别人帮忙做事情。Notification一般全局进行通知。Delegate是强关联,就是委托和代理双方都知道。Notification是弱关联,消息发出,不知道谁发出的也可以做出反应。发消息的人不知道谁接收也可以发。

数据持久化

NSUserDefault 、 Property list 、 归档 、 沙盒 、 SQLite 、 CoreData
NSUserDefault 、 Property list :轻量级数据存储
SQLite 、 CoreData:重量级
沙盒一般用来存储媒体文件

归档,归档中对象属性中还有自定义对象

归档:Archiving,即序列化(Serialization),指将程序语言中的对象转化为二进制流存储到文件中。反序列化(反归档)将二进制流转为对象。
涉及到:NSKeyedArchiver和NSKeyedUnarchiver
默认情况下只能对NSDate,NSNumber,NSString,NSArrayor,NSDictionary进行归档。自定义对象需要遵循NSCoding协议。
如果自定义对象中含有自定义对象属性,先对自定义对象属性进行归档即可,然后继续归档。

NSManagedObject模型

NSManagedObjectContext由一组关联模型对象组成或由一组脱光对象组成,这些模型对象表示一个或多个持久化存储视图。
NSManagedObjectContext为托管对象提供持久化存储的上下文和数据的增删改查,相当于一个缓存层,把所有的数据操作先缓存起来,然后带哦用save方法写入数据库,避免大量耗时的I/O操作。
但NSManagedObjectContext是线程不安全的。

沙盒目录

沙盒是一块相对封闭的独立空间,需要特殊的通道才能访问沙盒外系统中的资源。

Documents目录:非常大的文件,需要频繁更新的数据,能进行iTunes和iCloud备份
NSString *document = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];Library目录含有     Prefercences:存放设置信息,能通过iTunes和iCloud备份Caches目录:数据缓存,体积大的数据,不需要备份
NSString *library = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];tmp目录:临时目录,不能iTunes和iCloud备份
NSString *tmp = NSTemporaryDirectory();

虽然不能直接访问其他应用程序中数据,但是可以通过特定的API访问一些特殊应用

plist文件

Property list:一种用来存储串行化后的对象文件,本质是一个xml文件,不宜存放信息量大的信息

实现复杂对象的存储,通过实现NSCoding协议

Cocao Touch相关

UIView和CALyer关系

UIView:显示界面中视图和响应用户事件。
CALyer:图层类,父类为NSObject,不能响应用户事件,阴影圆角设置

UIView负责响应用户事件,CALyer负责内容显示,相互依赖。UIView通过layer管理CALyer负责内容显示。创建UIView时候,会自动创建一个CALyer,CALyer有CALyerDelegate,CALyerDelegte定义了绘图用到的方法。
CALyer在进行绘图的时候,会回调UIView实现的代理方法。

loadView作用

UIViewController和View关系:创建Controller时候,会生成一个View作为Controller的根视图。子视图通过addSubView添加上去。Controller通过view属性管理这个根视图。
Controller中的view在为nil或者没有被赋值的时候,如果此时调用get方法,会调用loadView方法,即懒加载,后序使用view的get方法的时候,不会触发,除非view为nil。

在重写loadView时候,不需要调用 [super loadView] ,因为super会创建一个空白的view,这个view没用。loadView重写就是实现了自定义Controller的根视图。

viewWillLayoutSubView

当一个视图的bounds变化的时,可以通过重写viewWillLayoutSubView实现子视图的布局。调用addsubView也会发生viewWillLayoutSubView,这里是检测bounds发生变化的时候,做一些事情。

drawRect

绘图操作都是由UIView的drawRect实现的,自定义绘图需要重写drawRect。
注意:

  • 不能直接调用drawRect重绘图,就算调用也没用。因为Apple要求使用UIView的setNeedsDisplay方法重绘,setNeedsDisplay内部会调用drawRect
  • view初始化没有设置frame,drawRect不会调用
  • view 的contentMode属性值为UIViewContentModeRedraw,那么每次设置或者更改bounds会自动调用drawRect方法
  • 直接使用layer绘图会消耗大量内存,可以用CAShaperLayer作为layer的子视图替代layer绘制

UIImageView添加圆角

设置cornerRadius和masksToBounds属性来实现的。不过在使用masksToBounds会造成离屏渲染问题
当前屏幕渲染:GPU的渲染操作在当前用于当前屏幕缓冲区进行
离屏渲染:GPU在当前屏幕缓冲区以外开辟了一个缓冲区进行渲染操作。又分为GPU离屏渲染和CPU离屏渲染。UIImageView是发生GPU离屏渲染,Core Graphic发生CPU离屏渲染

离屏渲染代价更大:1.需要额外创建新的缓冲区存放渲染结果 2.需要多次切换上下文环境,先从当前屏幕切换到离屏,然后进行渲染,等待离屏渲染结束后,再将离屏渲染结果显示到屏幕上,这里又需要从离屏切换到当前屏幕。

CALyer由backgroundColor、contents、borderWidth、borderColor。当imgaeView只有背景色没有image,可以通过指定cornerRadius,此时不会触发离屏渲染。当imageView有image且不透明(contents内容不透明),需要设置maskToBounds实现圆角,此时会触发GPU离屏渲染。

优化方案:

贝塞尔曲线和Core Graphics-(instancetype)drawImageCircle{UIGraphicsBeginImageContextWithOptions(self.size, NO, 0);UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, self.size.width, self.size.height)];[path addClip];[self drawAtPoint:CGPointZero];UIImage *image = UIGraphicsGetImageFromCurrentImageContext();UIGraphicsEndImageContext()return image;
}

第三方框架

AFN

五大模块

  1. 网络通信模块:AFURLSessionManager和AFHTTPSessionManager
  2. 网络安全策略模块:AFSecurityPolicy
  3. 网络状态监听模块:AFNetworkingReachabilityManager
  4. 网络通信数据序列化和反序列化模块:AFURLRequestSerialization和AFURLResponseSerialization
  5. UIKit框架的扩展类模块

最重要的第一个,实现了网络通信的核心功能,其余都是为第一服务的

第一个模块重要的两个类:AFURLSessionManager和AFHTTPSessionManager,其中AFHTTPSessionManager是AFURLSessionManager子类,只是对AFURLSessionManager的上层封装,AFURLSessionManager是对NSURLSession的封装。AFHTTPSessionManager作为外层接口为开发者提供网络通信的 基本操作。,内部功能的实现调用父类AFURLSessionManager的功能,AFURLSessionManager作为底层接口,内部都是通过NSURLSession提供网络通信的具体操作。

AFHTTPSessionManager的定义

@interface AFHTTPSessionManager : AFURLSessionManager <NSSecureCoding, NSCopying>//公共部分的URL   通常是公共部分的域名或者IP
@property (readonly, nonatomic, strong, nullable) NSURL *baseURL;//序列化的请求参数  用于序列化GET HEAD DELETE所带的query string参数
@property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;//序列化的响应参数  用于序列化GET POST返回的参数 默认使用JSON序列化
@property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;//安全策略  用于简历安全链接 如:HTTPS
@property (nonatomic, strong) AFSecurityPolicy *securityPolicy;。。。。。。
@end几个重要的方法//创建默认的AFHTTPSessionManager对象
+ (instancetype)manager;//通过baseurl创建AFHTTPSessionManager对象
- (instancetype)initWithBaseURL:(nullable NSURL *)url;//通过baseurl创建AFHTTPSessionManager对象,并制定NSURLSession配置
- (instancetype)initWithBaseURL:(nullable NSURL *)urlsessionConfiguration:(nullable NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER;//发送GET请求
- (nullable NSURLSessionDataTask *)GET:(NSString *)URLStringparameters:(nullable id)parameterssuccess:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))successfailure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;//GET请求,下载过程
//downloadProgress:更新下载进度时要执行的块对象。请注意,此块是在会话队列而非主队列上调用的
- (nullable NSURLSessionDataTask *)GET:(NSString *)URLStringparameters:(nullable id)parametersprogress:(nullable void (^)(NSProgress *downloadProgress))downloadProgresssuccess:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))successfailure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;常用的方法通过manager创建AFHTTPSessionManager对象,然后发送网络请求,核心的是GET POST方法,其他的HEAD PUT PATCH DELETE方法都是类似

内部实现逻辑

+ (instancetype)manager {return [[[self class] alloc] initWithBaseURL:nil];
}- (instancetype)init {return [self initWithBaseURL:nil];
}- (instancetype)initWithBaseURL:(NSURL *)url {return [self initWithBaseURL:url sessionConfiguration:nil];
}- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {return [self initWithBaseURL:nil sessionConfiguration:configuration];
}- (instancetype)initWithBaseURL:(NSURL *)urlsessionConfiguration:(NSURLSessionConfiguration *)configuration
{self = [super initWithSessionConfiguration:configuration];if (!self) {return nil;}// url 不为空且尾部什么都没有,则在url尾部加上/,目的是拼接urlif ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {url = [url URLByAppendingPathComponent:@""];}self.baseURL = url;self.requestSerializer = [AFHTTPRequestSerializer serializer];self.responseSerializer = [AFJSONResponseSerializer serializer];return self;
}- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {self = [super init];if (!self) {return nil;}if (!configuration) {configuration = [NSURLSessionConfiguration defaultSessionConfiguration];}self.sessionConfiguration = configuration;self.operationQueue = [[NSOperationQueue alloc] init];self.operationQueue.maxConcurrentOperationCount = 1; //最大的并发线程数为1
//初始化代理self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];self.responseSerializer = [AFJSONResponseSerializer serializer];self.securityPolicy = [AFSecurityPolicy defaultPolicy];#if !TARGET_OS_WATCHself.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
#endif//用来存储task标识self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];self.lock = [[NSLock alloc] init];self.lock.name = AFURLSessionManagerLockName;[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {//恢复后台taskfor (NSURLSessionDataTask *task in dataTasks) {[self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];}for (NSURLSessionUploadTask *uploadTask in uploadTasks) {[self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];}for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {[self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];}}];return self;
}
1. 初始化self.sessionConfiguration
2. 创建任务队列self.operationQueue,指定并发数为1
3. 初始化响应格式和安全策略
4. 创建字典,保存task标识
5. 创建锁,用于后面的操作
6. 恢复可能存在的后台task,大多数情况下没有后台task

GET请求过程

- (NSURLSessionDataTask *)GET:(NSString *)URLStringparameters:(id)parameterssuccess:(void (^)(NSURLSessionDataTask *task, id responseObject))successfailure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{return [self GET:URLString parameters:parameters progress:nil success:success failure:failure];
}- (NSURLSessionDataTask *)GET:(NSString *)URLStringparameters:(id)parametersprogress:(void (^)(NSProgress * _Nonnull))downloadProgresssuccess:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))successfailure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"URLString:URLStringparameters:parametersuploadProgress:nildownloadProgress:downloadProgresssuccess:successfailure:failure];[dataTask resume];return dataTask;
}- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)methodURLString:(NSString *)URLStringparameters:(id)parametersuploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressdownloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgresssuccess:(void (^)(NSURLSessionDataTask *, id))successfailure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{NSError *serializationError = nil;//序列化请求参数,将请求信息封装到NSMutableURLRequest中NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];if (serializationError) {if (failure) {dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{failure(nil, serializationError);});}return nil;}__block NSURLSessionDataTask *dataTask = nil;dataTask = [self dataTaskWithRequest:requestuploadProgress:uploadProgressdownloadProgress:downloadProgresscompletionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {if (error) {if (failure) {failure(dataTask, error);}} else {if (success) {success(dataTask, responseObject);}}}];return dataTask;
}1. 将请求信息和参数封装到request中
2. 通过request创建dataTask- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)requestuploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlockdownloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlockcompletionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject,  NSError * _Nullable error))completionHandler {__block NSURLSessionDataTask *dataTask = nil;//串行创建dataTask,防止dataTask的taskIdentifier不唯一url_session_manager_create_task_safely(^{dataTask = [self.session dataTaskWithRequest:request];});
//为dataTask添加代理和上传,下载和结束的回调[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];return dataTask;
}- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTaskuploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlockdownloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlockcompletionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{//创建AFURLSessionManagerTaskDelegate对象AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];delegate.manager = self;delegate.completionHandler = completionHandler;dataTask.taskDescription = self.taskDescriptionForSessionTasks;//设置dataTask的代理[self setDelegate:delegate forTask:dataTask];delegate.uploadProgressBlock = uploadProgressBlock;delegate.downloadProgressBlock = downloadProgressBlock;
}AFURLSessionManagerTaskDelegate是一个委托类,用来处理网络请求中上传,下载和完成的回调,这个委托类并不是一个delegate,只是实现了NSURLSEssionTaskDelegate、NSURLSessionDataDelegate、NSURLSessionDownloadDelegate协议中的方法。为dataTask设置代理
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegateforTask:(NSURLSessionTask *)task
{NSParameterAssert(task);NSParameterAssert(delegate);[self.lock lock];self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;[self addNotificationObserverForTask:task];[self.lock unlock];
}
只要将task的taskIdentifier作为key,delegate作为value放入self.mutableTaskDelegatesKeyedByTaskIdentifier字典中就可以了,这里加锁,避免多线程环境下造成上数据的不一致。

AFN断点续传

思路

  1. 检查服务器上的文件信息
  2. 检查本地文件的信息
  3. 如果本地文件比服务器上的文件小,能够进行断点续传,利用HTTP请求头的Range实现断点续传
  4. 如果本地文件比服务器大,重新下载
  5. 如果本地和服务器文件一样,下载完成

AFN默认的超时时长60S

SDWebImage

一个用于异步加载图片的开源框架,为开发者提供图片的加载,缓存,清理功能以及简单的调用接口

  • Downloader:负责图片下载,其中SDWebImageDownloader用于优化下载的过程,SDWebImageDownloaderOperation才是真正负责下载的类
  • Cache:负责图片的缓存,缓存方式有两种:内存缓存和磁盘缓存,其中磁盘缓存是异步进行的
  • Decoder:负责图片的解码和编码以及压缩和解压过程
  • Utils:工具类,其中最重要的是SDWebImageManager,负责全局管理图片下载和缓存
  • Categories:全是类别,用来扩展UIImage、UIView、NSData
  • WebCache Categories:图片加载的类别,作为UIKit图片加载的接口
  • FLAnimatedImage:GIF动画扩展

最多的是UIImageView+WebCache。

- (void)sd_setImageWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT;
- (void)sd_setImageWithURL:(nullable NSURL *)urlplaceholderImage:(nullable UIImage *)placeholder NS_REFINED_FOR_SWIFT;
- (void)sd_setImageWithURL:(nullable NSURL *)urlplaceholderImage:(nullable UIImage *)placeholderoptions:(SDWebImageOptions)options
- (void)sd_setImageWithURL:(nullable NSURL *)urlplaceholderImage:(nullable UIImage *)placeholderoptions:(SDWebImageOptions)optionscontext:(nullable SDWebImageContext *)context;
- (void)sd_setImageWithURL:(nullable NSURL *)urlcompleted:(nullable SDExternalCompletionBlock)completedBlock;

以上这些底层都是调用同一个函数完成

- (void)sd_setImageWithURL:(nullable NSURL *)url {[self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil];
}- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder {[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];
}- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options {[self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil];
}- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context {[self sd_setImageWithURL:url placeholderImage:placeholder options:options context:context progress:nil completed:nil];
}- (void)sd_setImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock {[self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:completedBlock];
}- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock {[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock];
}
。。。。。。本质都是这个
- (void)sd_setImageWithURL:(nullable NSURL *)urlplaceholderImage:(nullable UIImage *)placeholderoptions:(SDWebImageOptions)optionscontext:(nullable SDWebImageContext *)contextprogress:(nullable SDImageLoaderProgressBlock)progressBlockcompleted:(nullable SDExternalCompletionBlock)completedBlock {[self sd_internalSetImageWithURL:urlplaceholderImage:placeholderoptions:optionscontext:contextsetImageBlock:nilprogress:progressBlockcompleted:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {if (completedBlock) {completedBlock(image, error, cacheType, imageURL);}}];
}- (void)sd_internalSetImageWithURL:(nullable NSURL *)urlplaceholderImage:(nullable UIImage *)placeholderoptions:(SDWebImageOptions)optionscontext:(nullable SDWebImageContext *)contextsetImageBlock:(nullable SDSetImageBlock)setImageBlockprogress:(nullable SDImageLoaderProgressBlock)progressBlockcompleted:(nullable SDInternalCompletionBlock)completedBlock {context = [context copy]; // copy to avoid mutable object//通过所属的类的类名作为Operation的标识符NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];if (!validOperationKey) {validOperationKey = NSStringFromClass([self class]);}self.sd_latestOperationKey = validOperationKey;//每次加载图片之前,先取消之前未完层呢的加载[self sd_cancelImageLoadOperationWithKey:validOperationKey];self.sd_imageURL = url;if (!(options & SDWebImageDelayPlaceholder)) {dispatch_main_async_safe(^{[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];});}if (url) {// reset the progressNSProgress *imageProgress = objc_getAssociatedObject(self, @selector(sd_imageProgress));if (imageProgress) {imageProgress.totalUnitCount = 0;imageProgress.completedUnitCount = 0;}#if SD_UIKIT || SD_MAC// check and start image indicator[self sd_startImageIndicator];id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
#endif//manager管理图片的加载SDWebImageManager *manager = context[SDWebImageContextCustomManager];if (!manager) {manager = [SDWebImageManager sharedManager];}//更新下载进度SDImageLoaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {if (imageProgress) {imageProgress.totalUnitCount = expectedSize;imageProgress.completedUnitCount = receivedSize;}。。。。。。};@weakify(self);//这是关键的一步//开始加载图片,如果缓存中有,那么就从缓存中获取,否则从服务器进行获取id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options context:context progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {@strongify(self);if (!self) { return; }// if the progress not been updated, mark it to complete stateif (imageProgress && finished && !error && imageProgress.totalUnitCount == 0 && imageProgress.completedUnitCount == 0) {imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;}#if SD_UIKIT || SD_MAC// check and stop image indicatorif (finished) {[self sd_stopImageIndicator];}
#endifBOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||(!image && !(options & SDWebImageDelayPlaceholder)));SDWebImageNoParamsBlock callCompletedBlockClojure = ^{if (!self) { return; }if (!shouldNotSetImage) {[self sd_setNeedsLayout];}if (completedBlock && shouldCallCompletedBlock) {completedBlock(image, data, error, cacheType, finished, url);}};// case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set// OR// case 1b: we got no image and the SDWebImageDelayPlaceholder is not set//是否需要手动设置加载好的图片if (shouldNotSetImage) {dispatch_main_async_safe(callCompletedBlockClojure);return;}UIImage *targetImage = nil;NSData *targetData = nil;if (image) {// case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not settargetImage = image;targetData = data;} else if (options & SDWebImageDelayPlaceholder) {// case 2b: we got no image and the SDWebImageDelayPlaceholder flag is settargetImage = placeholder;targetData = nil;}#if SD_UIKIT || SD_MAC// check whether we should use the image transitionSDWebImageTransition *transition = nil;if (finished && (options & SDWebImageForceTransition || cacheType == SDImageCacheTypeNone)) {transition = self.sd_imageTransition;}
#endifdispatch_main_async_safe(^{
#if SD_UIKIT || SD_MAC[self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
#else[self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:cacheType imageURL:imageURL];
#endifcallCompletedBlockClojure();});}];//保存这个operation[self sd_setImageLoadOperation:operation forKey:validOperationKey];} else {//传入url为空,加载失败
#if SD_UIKIT || SD_MAC[self sd_stopImageIndicator];
#endifdispatch_main_async_safe(^{if (completedBlock) {NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}];completedBlock(nil, nil, error, SDImageCacheTypeNone, YES, url);}});}
}

SDWebImageManager这个类

@property (weak, nonatomic, nullable) id <SDWebImageManagerDelegate> delegate;
@property (strong, nonatomic, readonly, nonnull) id<SDImageLoader> imageLoader;
@property (strong, nonatomic, nullable) id<SDImageTransformer> transformer;
@property (nonatomic, strong, nullable) id<SDWebImageCacheKeyFilter> cacheKeyFilter;
@property (nonatomic, strong, nullable) id<SDWebImageCacheSerializer> cacheSerializer;
。。。。。。
- (nonnull instancetype)initWithCache:(nonnull id<SDImageCache>)cache loader:(nonnull id<SDImageLoader>)loader NS_DESIGNATED_INITIALIZER;
- (nullable SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)urloptions:(SDWebImageOptions)optionsprogress:(nullable SDImageLoaderProgressBlock)progressBlockcompleted:(nonnull SDInternalCompletionBlock)completedBlock;
。。。。。。imageCache和imageDownloader分别是用来处理缓存和图片的下载,delegate中定义了图片下载和缓存的方法。
SDWebImageManager的loadImageWithURL:方法实现了图片的加载,调用的时候先去缓存中找,如果缓存找不到再去服务器请求- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)urloptions:(SDWebImageOptions)optionscontext:(nullable SDWebImageContext *)contextprogress:(nullable SDImageLoaderProgressBlock)progressBlockcompleted:(nonnull SDInternalCompletionBlock)completedBlock {//在没有completedBlock的情况下,调用没有意义NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");//验证url的合法性// Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, Xcode won't// throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.if ([url isKindOfClass:NSString.class]) {url = [NSURL URLWithString:(NSString *)url];}// Prevents app crashing on argument type error like sending NSNull instead of NSURLif (![url isKindOfClass:NSURL.class]) {url = nil;}SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];operation.manager = self;BOOL isFailedUrl = NO;if (url) {SD_LOCK(self.failedURLsLock);isFailedUrl = [self.failedURLs containsObject:url];SD_UNLOCK(self.failedURLsLock);}if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}] url:url];return operation;}SD_LOCK(self.runningOperationsLock);[self.runningOperations addObject:operation];SD_UNLOCK(self.runningOperationsLock);// Preprocess the options and context arg to decide the final the result for managerSDWebImageOptionsResult *result = [self processedResultForURL:url options:options context:context];// Start the entry to load image from cache//调用callCompletionBlock,从缓存中查找图片[self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];return operation;
}- (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operationurl:(nonnull NSURL *)urloptions:(SDWebImageOptions)optionscontext:(nullable SDWebImageContext *)contextprogress:(nullable SDImageLoaderProgressBlock)progressBlockcompleted:(nullable SDInternalCompletionBlock)completedBlock {// Check whether we should query cacheBOOL shouldQueryCache = !SD_OPTIONS_CONTAINS(options, SDWebImageFromLoaderOnly);if (shouldQueryCache) {id<SDWebImageCacheKeyFilter> cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];NSString *key = [self cacheKeyForURL:url cacheKeyFilter:cacheKeyFilter];@weakify(operation);operation.cacheOperation = [self.imageCache queryImageForKey:key options:options context:context completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {@strongify(operation);if (!operation || operation.isCancelled) {// Image combined operation cancelled by user[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:nil] url:url];[self safelyRemoveOperationFromRunning:operation];return;}// Continue download process[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:cachedImage cachedData:cachedData cacheType:cacheType progress:progressBlock completed:completedBlock];}];} else {// Continue download process[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:SDImageCacheTypeNone progress:progressBlock completed:completedBlock];}
}
。。。。。。NSString *key = [self cacheKeyForURL:url cacheKeyFilter:cacheKeyFilter];

loadImageWithURL实现比较复杂,涉及到各种条件判断和调用子函数,大致思路如下

  1. 判断url合法性
  2. 创建一个operation负责管理图片的下载和缓存
  3. 将operation添加的runningOperation中,标记为正在运行的operation
  4. 根据url从缓存中查找图片,如果找到,那么回调
  5. 如果没找到,那么去服务器上下载
  6. 下载完成后,将图片保存到缓存中,然后回调
  7. 回调完成后,将operation从runningOperation移除

缓存查找:先去内存中查找,命中直接返回。如果没有命中,去本地磁盘查找图片。两级缓存的目的是:本地磁盘的读取会消耗大量时间在I/O操作上,大量访问本地磁盘会浪费性能在I/O上,将访问过的图片放在内存中,下次访问的时候可以加快图片的访问。

SDWebImage的序列图

  1. 调用sd_setimageWithURL开始加载图片
  2. 内部再调用sd_internalSetImageWithURL
  3. 再调用loadImageWithURL处理加载图片和逻辑
  4. 再调用queryCacheOperationForKey去内存和磁盘查找图片,如果找到,返回image
  5. 如果缓存没有找到,调用downloadImageWithURL去服务器下载
  6. 将下载的图片存储的缓存中,然后返回image
  7. 给UIImageView设置image

设计模式

单例设计模式弊端

  • 优点:

    • 提供对唯一实例的受控访问;节约资源
  • 弊端:
    • 没有抽象层,扩展难度大
    • 职责过多,一定程度违背单一职责原则
    • 连接数据库时候,可能会导致共享连接池对象的程序过多而出现连接池溢出
    • 实例化对象长时间不被利用,系统会认为是垃圾来回收

MVC MVCS 和 MVVM

https://casatwy.com/iosying-yong-jia-gou-tan-viewceng-de-zu-zhi-he-diao-yong-fang-an.html
MVC

  • M应该做的事:

    • 给ViewController提供数据
    • 给ViewController存储数据提供接口
    • 提供经过抽象的业务基本组件,供Controller调度
  • C应该做的事:
    • 管理View Container的生命周期
    • 负责生成所有的View实例,并放入View Container
    • 监听来自View与业务有关的事件,通过与Model的合作,来完成对应事件的业务。
  • V应该做的事:
    • 响应与业务无关的事件,并因此引发动画效果,点击反馈(如果合适的话,尽量还是放在View去做)等。
    • 界面元素表达

MVCS
是基于MVC衍生出来的一套架构。从概念上来说,它拆分的部分是Model部分,拆出来一个Store。这个Store专门负责数据存取。但从实际操作的角度上讲,它拆开的是Controller。

这算是瘦Model的一种方案,瘦Model只是专门用于表达数据,然后存储、数据处理都交给外面的来做。MVCS使用的前提是,它假设了你是瘦Model,同时数据的存储和处理都在Controller去做。所以对应到MVCS,它在一开始就是拆分的Controller。因为Controller做了数据存储的事情,就会变得非常庞大,那么就把Controller专门负责存取数据的那部分抽离出来,交给另一个对象去做,这个对象就是Store。这么调整之后,整个结构也就变成了真正意义上的MVCS。

胖Model包含了部分弱业务逻辑。胖Model要达到的目的是,Controller从胖Model这里拿到数据之后,不用额外做操作或者只要做非常少的操作,就能够将数据直接应用在View上

Raw Data:timestamp:1234567FatModel:@property (nonatomic, assign) CGFloat timestamp;- (NSString *)ymdDateString; // 2015-04-20 15:16- (NSString *)gapString; // 3分钟前、1小时前、一天前、2015-3-13 12:34Controller:self.dateLabel.text = [FatModel ymdDateString];self.gapLabel.text = [FatModel gapString];

把timestamp转换成具体业务上所需要的字符串,这属于业务代码,算是弱业务。FatModel做了这些弱业务之后,Controller就能变得非常skinny,Controller只需要关注强业务代码就行了。众所周知,强业务变动的可能性要比弱业务大得多,弱业务相对稳定,所以弱业务塞进Model里面是没问题的。另一方面,弱业务重复出现的频率要大于强业务,对复用性的要求更高,如果这部分业务写在Controller,类似的代码会洒得到处都是,一旦弱业务有修改(弱业务修改频率低不代表就没有修改),这个事情就是一个灾难。如果塞到Model里面去,改一处很多地方就能跟着改,就能避免这场灾难。

然而其缺点就在于,胖Model相对比较难移植,虽然只是包含弱业务,但好歹也是业务,迁移的时候很容易拔出萝卜带出泥。另外一点,MVC的架构思想更加倾向于Model是一个Layer,而不是一个Object,不应该把一个Layer应该做的事情交给一个Object去做。最后一点,软件是会成长的,FatModel很有可能随着软件的成长越来越Fat,最终难以维护。

瘦Model只负责业务数据的表达,所有业务无论强弱一律扔到Controller。瘦Model要达到的目的是,尽一切可能去编写细粒度Model,然后配套各种helper类或方法来对弱业务做抽象,强业务依旧交给Controller

Raw Data:
{"name":"casa","sex":"male",
}SlimModel:@property (nonatomic, strong) NSString *name;@property (nonatomic, strong) NSString *sex;Helper:#define Male 1;#define Female 0;+ (BOOL)sexWithString:(NSString *)sex;Controller:if ([Helper sexWithString:SlimModel.sex] == Male) {...}

由于SlimModel跟业务完全无关,它的数据可以交给任何一个能处理它数据的Helper或其他的对象,来完成业务。在代码迁移的时候独立性很强,很少会出现拔出萝卜带出泥的情况。另外,由于SlimModel只是数据表达,对它进行维护基本上是0成本,软件膨胀得再厉害,SlimModel也不会大到哪儿去。

缺点就在于,Helper这种做法也不见得很好,这里有一篇文章批判了这个事情。另外,由于Model的操作会出现在各种地方,SlimModel在一定程度上违背了DRY(Don’t Repeat Yourself)的思路,Controller仍然不可避免在一定程度上出现代码膨胀。

MVCS是基于瘦Model的一种架构思路,把原本Model要做的很多事情中的其中一部分关于数据存储的代码抽象成了Store,在一定程度上降低了Controller的压力。

MVVM
MVVM本质上也是从MVC中派生出来的思想,MVVM着重想要解决的问题是尽可能地减少Controller的任务。不管MVVM也好,MVCS也好,他们的共识都是Controller会随着软件的成长,变很大很难维护很难测试。只不过两种架构思路的前提不同。

MVCS是认为Controller做了一部分Model的事情,要把它拆出来变成Store。

MVVM是认为Controller做了太多数据加工的事情,所以MVVM把数据加工的任务从Controller中解放了出来,使得Controller只需要专注于数据调配的工作,ViewModel则去负责数据加工并通过通知机制让View响应ViewModel的改变。

MVVM是基于胖Model的架构思路建立的,然后在胖Model中拆出两部分:Model和ViewModel。关于这个观点我要做一个额外解释:胖Model做的事情是先为Controller减负,然后由于Model变胖,再在此基础上拆出ViewModel,跟业界普遍认知的MVVM本质上是为Controller减负这个说法并不矛盾,因为胖Model做的事情也是为Controller减负。

另外,我前面说MVVM把数据加工的任务从Controller中解放出来,跟MVVM拆分的是胖Model也不矛盾。要做到解放Controller,首先你得有个胖Model,然后再把这个胖Model拆成Model和ViewModel。

在iOS领域大部分MVVM架构都会使用ReactiveCocoa,但是使用ReactiveCocoa的iOS应用就是基于MVVM架构的吗?那当然不是

MVVM的关键是要有View Model!而不是ReactiveCocoa

    Raw Data:{((123, 456),(234, 567),(345, 678))}RawData我们假设是经纬度。然后有一个模块是地图模块,把经纬度数组全部都转变成MKAnnotation或其派生类对于Controller来说是弱业务,(记住,胖Model就是用来做弱业务的),因此我们用ViewModel直接把它转变成MKAnnotation的NSArray,交给Controller之后Controller直接就可以用了不同的部门可以根据这个获取相同的数据,然后进行不同的展示

不用ReactiveCocoa也能MVVM,用ReactiveCocoa能更好地体现MVVM的精髓。前面我举到的例子只是数据从API到View的方向,View的操作也会产生"数据",只不过这里的"数据"更多的是体现在表达用户的操作上,比如输入了什么内容,那么数据就是text、选择了哪个cell,那么数据就是indexPath。那么在数据从view走向API或者Controller的方向上,就是ReactiveCocoa发挥的地方。

ViewModel本质上算是Model层(因为是胖Model里面分出来的一部分),所以View并不适合直接持有ViewModel,那么View一旦产生数据了怎么办?扔信号扔给ViewModel,用谁扔?ReactiveCocoa。

在MVVM中使用ReactiveCocoa的第一个目的就是如上所说,View并不适合直接持有ViewModel。第二个目的就在于,ViewModel有可能并不是只服务于特定的一个View,使用更加松散的绑定关系能够降低ViewModel和View之间的耦合度。

国内外资料阐述MVVM的时候都是这样排布的:View <-> ViewModel <-> Model,造成了MVVM不需要Controller的错觉,现在似乎发展成业界开始出现MVVM是不需要Controller的。的声音了。其实MVVM是一定需要Controller的参与的,虽然MVVM在一定程度上弱化了Controller的存在感,并且给Controller做了减负瘦身(这也是MVVM的主要目的)。但是,这并不代表MVVM中不需要Controller,MMVC和MVVM他们之间的关系应该是这样:

View <-> C <-> ViewModel <-> Model,所以使用MVVM之后,就不需要Controller的说法是不正确的。严格来说MVVM其实是MVCVM。从图中可以得知,Controller夹在View和ViewModel之间做的其中一个主要事情就是将View和ViewModel进行绑定。在逻辑上,Controller知道应当展示哪个View,Controller也知道应当使用哪个ViewModel,然而View和ViewModel它们之间是互相不知道的,所以Controller就负责控制他们的绑定关系,所以叫Controller/控制器就是这个原因。

在MVC的基础上,把C拆出一个ViewModel专门负责数据处理的事情,就是MVVM。然后,为了让View和ViewModel之间能够有比较松散的绑定关系,于是我们使用ReactiveCocoa,因为苹果本身并没有提供一个比较适合这种情况的绑定方法。iOS领域里KVO,Notification,block,delegate和target-action都可以用来做数据通信,从而来实现绑定,但都不如ReactiveCocoa提供的RACSignal来的优雅,如果不用ReactiveCocoa,绑定关系可能就做不到那么松散那么好,但并不影响它还是MVVM。

在实际iOS应用架构中,MVVM应该出现在了大部分创业公司或者老牌公司新App的iOS应用架构图中,据我所知易宝支付旗下的某个iOS应用就整体采用了MVVM架构,他们抽出了一个Action层来装各种ViewModel,也是属于相对合理的结构。

所以Controller在MVVM中,一方面负责View和ViewModel之间的绑定,另一方面也负责常规的UI逻辑处理。

防止ios的反编译

风险:内购破解,网络安全,PATCH破解,源代码安全。

解决:

  1. 本地数据加密
  2. URL编码加密,防止URL静态被分析
  3. 网络传输数据加密
  4. 方法体,方法名高级混淆
  5. 程序结构打乱混排

dSYM

dSYM是iOS中的符号表,组成:<起始地址><结束地址><函数>[<文件名:行号>]
描述调试,crash产生的堆栈信息,将二进制的堆栈信息转换为源码中的函数和行号。
Xcode编译项目后,产生一同名的dSYM文件,其用来保存十六进制函数地址映射信息的中转文件。如果应用在Debug调试模式,可以根据log的输出定位crash的位置。在release模式或者上线后crash,需要通过出错函数地址到dSYM文件中查询对应的函数名和文件名

检测内存泄漏

  • 静态检测:利用Xcode自带的静态分析工具Analyze找出逻辑错误,内存管理错误,声明,API错误
  • 动态监测:Instruments工具中的Leaks组建检测内存泄漏产生的地方

C/C++

全局变量和局部变量有什么区别,能否重名

分配内存空间看:全局变量,静态局部变量,静态全局变量都在静态存储,局部变量在栈里。
可以重名,但是此时局部变量会屏蔽全局变量,需要使用作用域解析运算符“::”。对于有些编译器,同一个函数内可以定义多个同名的局部变量。

用extern修饰已经定义的去全局变量或引用头文件

多个三元运算符在一起,从左到右进行。

位运算只适用于字符型和整数型变量和他们的变体,对其他的类型不适应

隐式类型转换三种:算术转换、赋值转换和输出转换。算术转换:如果参与的数字有不同的类型,在运算的时候总把低的向高精度的转。
unsigned int a = 6; int b = -20;
a + b = 4294967276
a = 0000 0000 0000 0000 0000 0000 0000 0110
b = 1000 0000 0000 0000 0000 0000 0001 0100
b补码 = 1111 1111 1111 1111 1111 1111 1110 1100
a+b = 1111 1111 1111 1111 1111 1111 1111 0010

const int i = 0; 系统会自动将 i 全部进行替换为0,如果这时候通过指针进行修改i的值的话,i是被修改,可是编译出来的已经进行了替换,i仍然运行的是0

数组指针:指向数组地址的指针,本质是指针
指针数组:数组元素全部为指针的数组
int *a[10] 指针数组
int (*a[10])(int) 定了一个大小为10的数组,数组中存放的是返回值int且参数为int的函数指针

new和malloc

new是建立一个对象,malloc是分配一块内存
new初始化对象,调用对象的构造函数,对应的delete调用相应的析构函数,malloc仅仅分配内存,free仅仅回收内存
new和malloc都可用于申请动态内存,new是一个操作符,不需要头文件支持,而malloc是函数,需要头文件支持。

typedef struct_list_t{
struct list_t next;
struct list
prev;
char data[0]; //方便管理内存缓冲区,减少内存碎片化,满足变长度的结构体。使得街头题可变长。对于编译器,数组名本身不占用空间,只是偏移量,数组名这符号代表一个不可修改的地址常量,但是数组大小就可以进行动态分配
}list_t;

iOS程序员面试笔试宝典整理相关推荐

  1. 我的新书——《PHP程序员面试笔试宝典》

    你好,是我琉忆. 一个文艺的PHP开发工程师. 很荣幸能够在这里带来我的第一本新书--<PHP程序员面试笔试宝典>. 一.创作过程 <PHP程序员面试笔试宝典>是我的第一本书, ...

  2. 程序员面试笔试宝典学习笔记(一)

    以下是一些著名互联网企业的部分面试笔试真题以及考察知识点 本文的内容是对一些网址上的知识点介绍做了相应的整理 1.extern的作用 自己理解:应该需要区分extern在C语言中和C++语言中的作用, ...

  3. 《PHP程序员面试笔试宝典》——如何准备集体面试?

    本文摘自<PHP程序员面试笔试宝典>. PHP面试技巧分享,PHP面试题,PHP宝典尽在"琉忆编程库". 集体面试也被称为群面.无领导小组面试.由于计算机发展至今,软件 ...

  4. Java程序员面试笔试宝典刷题总结~11

    虽然申请博客已经有一段时间了,却是第一次写博客,有点激动,不知道该写些什么,刚好大三老学姐正值找实习工作之际,每天都会刷一点题,现在在看Java程序员面试笔试宝典一书,刚好把里面的题每天总结5道,写在 ...

  5. 《PHP程序员面试笔试宝典》——如何巧妙地回答面试官的问题?

    如何巧妙地回答面试官的问题? 本文摘自<PHP程序员面试笔试宝典> 所谓"来者不善,善者不来",程序员面试中,求职者不可避免地需要回答面试官各种"刁钻&quo ...

  6. python程序员面试算法宝典pdf-Python程序员面试笔试宝典

    本书是一本讲解Python程序员面试笔试的百科全书,在写法上,除了讲解如何解答Python程序员面试笔试问题以外,还引入了相关知识点辅以说明,让读者能够更加容易理解.本书将Python程序员面试笔试过 ...

  7. 程序员求职之道(《程序员面试笔试宝典》)之走进微软

    以下内容属于原创,同时发表于Lich's blog L!ch,2014届硕士研究生,签约微软. 一分耕耘,一分收获 谨以此篇,记录我即将结束的学生生涯,和对马上到来的新生活的向往. 现在研三,14年3 ...

  8. 《PHP程序员面试笔试宝典》——如何克服面试中紧张的情绪?

    本文摘自<PHP程序员面试笔试宝典>. PHP面试技巧分享,PHP面试题,PHP宝典尽在"琉忆编程库". 面试的成功与否,往小的方面讲,直接关系到求职者的工作问题,往大 ...

  9. Java程序员面试笔试宝典-Java基础知识(一)

    本文内容基于<Java程序员面试笔试宝典>,何昊.薛鹏.叶向阳著. 1. 基本概念 1.1 Java语言有哪些优点? 1.2 Java与C++有什么异同? 1.3 为什么需要public ...

最新文章

  1. 大学生如何合理使用计算机,大学生计算机合理使用引导分析
  2. Ubuntu root密码设置
  3. web app指南之构建html5离线应用
  4. PADS Layout VX.2.3 制作PCB封装(Decal)时,导入DXF文件
  5. Android 图文混排 通过webview实现并实现点击图片
  6. java 邮件 超链接_将Excel范围中的超链接传输到Outlook电子邮件
  7. mysql 闪回_MySQL数据误删除的快速解决方法(MySQL闪回工具)
  8. matlab学习笔记第七章——常微分方程(ODE)的数值解
  9. OSChina 周一乱弹 —— 有钱人的故事真让人心动
  10. uniapp 即时通讯_在uni-app使用极光IM 开发一个聊天室
  11. 【liunxptp协议栈详解第一部分】
  12. HTML 样式style
  13. Shrinkage: I was in the pool
  14. Delphi-UpperCase 函数
  15. 国密算法(SM2,SM3,SM4)辅助工具升级版(OTP+PBOC3.0)
  16. 四、Linux磁盘与文件系统管理
  17. 2020年个人计划总结和2021年个人计划
  18. 华硕安装linux windows7,【华硕一体机改win7步骤】华硕一体机装win7_华硕一体机安装win7-系统城...
  19. 戴尔新款笔记本装系统不认硬盘解决办法
  20. php计算器验证只能为数字,计算器屏幕显示不全怎么修

热门文章

  1. 众里寻她千百度,他眼仅观她脸处--无处不在的注意力机制(self-attention)
  2. 百万数据使用子查询进行SQL优化
  3. OCR文字识别软件FineReader系列产品双十一特惠!
  4. 前端对接微信公众号网页开发流程,前期配置
  5. 齿轮-转子-轴承系统动力学matlab程序代码
  6. 提高团队成员的工作积极性/团队凝聚力
  7. SCRM:SpringBoot + RabbitMQ + 企微 实现发送消息到企业微信
  8. vue.runtime.esm.js?2b0e:619 [Vue warn]: Avoid mutating a prop
  9. 数据清洗是什么?如何进行数据清洗?
  10. [面试]英文面试问答