[iOS] 七七八八的小姿势(1)

因为之前的公司裁了一波员以及自己的能力太差了,衍生了想换家公司的想法,也挺神奇的,其实面试真的是相当顺利,但选择实在是太难了,遇到了超级nice的小哥哥大哥哥们,以及hr小姐姐们~ 很幸运被大家value,也能成为彼此生命中的过客/朋友,虽然我实在是太渣了。。

anyway终于尘埃落定,以后要跟着超级厉害的超级温柔的奶爸mentor小哥哥混啦~ 以及要跟着同组的超级厉害的大神们学习写代码了,真的超级慌的,他们怎么能写的这么好。。于是开启一个新的collection记录日常的小知识吧~


目录:
  1. GCD如何cancel任务
  2. category如何加个weak属性
  3. crash分类
  4. 为啥消息转发要有methodSignatureForSelector
  5. app启动加载类过程
  6. 静态库动态库

1. GCD如何cancel任务

NSOperation其实就是封装了GCD,但NSOperation是可以cancel任务的,那么GCD是咋做的嘞~

可以参考:https://www.jianshu.com/p/ead365ef069f

iOS8之后提供了dispatch_block_cancel这个接口来cancel之前已经抛入的block

- (void)testCancel {
    NSLog(@"test cancel");
    dispatch_queue_t serialQueue = dispatch_queue_create("serial_queue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_block_t block1 = dispatch_block_create(0, ^{
        NSLog(@"block1 begin");
        sleep(5);
        NSLog(@"block1 end");
    });

    dispatch_block_t block2 = dispatch_block_create(0, ^{
        NSLog(@"block2 ");
    });

    dispatch_async(serialQueue, block1);
    dispatch_async(serialQueue, block2);
    
    dispatch_block_cancel(block1);
    dispatch_block_cancel(block2);
}

上面的代码输出会是神马呢?block1会不会被执行呢?答案是不会哦,除了test cancel啥也没输出。

如果改为dispatch_queue_t serialQueue = dispatch_queue_create("serial_queue", DISPATCH_QUEUE_CONCURRENT);,原谅我懒得改名字了0.0

输出仍旧是除了test cancel没别的~

如果dispatch_after一下呢:

dispatch_async(serialQueue, block2);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), block1);

dispatch_block_cancel(block1);
dispatch_block_cancel(block2);

输出偶尔是test cancel & block2偶尔是test cancel

所以cancel这个事儿是怎样的嘞~ cancel可以取消还没分配给线程去执行的任务,但是如果已经抛给线程了就不能取消啦;dispatch_after会在规定时间将任务抛入,cancel也可以取消掉还没有抛入的任务。 所以其实cancel是不一定能够cancel掉的,是有随机性的,要看是不是已经开始执行了,开始就不能取消啦。


2. category如何加个weak属性

参考:https://www.jianshu.com/p/18d8cd4ff6c6

这个问题的源头是其实管理对象是不提供weak的policy的,那么category要怎么实现weak呢?

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};

我们要做的就是如何让被持有对象没有引用计数,并且销毁以后我们的持有也会被释放。

※ 方法一:借用dealloc block来置空持有

虽然关联对象没有提供weak的policy,但是它提供了assign的,或者strong的。(注意其实assign可以修饰对象只是dealloc以后再访问会crash,而基础数据是不能用strong修饰的哦,因为基础数据不算对象)如果我们能做到在对象dealloc的时候清空指针就可以啦。

但是如果对象要能走到dealloc说明已经没有引用了,所以这里我们只能用assign修饰它,然后dealloc是清空指针。

但是我们是不能用category覆写dealloc的,因为其实如果覆写了就完全覆盖了对象本来的方法。那么要怎么知道对象销毁了呢?

对象销毁的时候会析构把它持有的引用也就是property们都置空,如果我们给它设置一个strong的property,它dealloc的时候会把这个property也dealloc,而这个property我们是可以随意覆写dealloc的只是作为一个中间对象而已。

借用dealloc实现指针清空

所以这个strong的property可以是酱紫的:

// 定义一个对象,使用block来回调析构函数。
typedef void (^DeallocBlock)();
@interface OriginalObject : NSObject
@property (nonatomic, copy) DeallocBlock block;
- (instancetype)initWithBlock:(DeallocBlock)block;
@end

@implementation OriginalObject

- (instancetype)initWithBlock:(DeallocBlock)block
{
    self = [super init];
    if (self) {
        self.block = block;
    }
    return self;
}
- (void)dealloc {
    self.block ? self.block() : nil;
}
@end

然后我们的category是酱紫:

// Category 
// NSObject+property.h
@interface NSObject (property)
@property (nonatomic, weak) id objc_weak_id;
@end

// NSObject+property.m
@implementation NSObject (property)
- (id)objc_weak_id {
    return objc_getAssociatedObject(self, _cmd);
}

- (void)setObjc_weak_id:(id)objc_weak_id {
    OriginalObject *ob = [[OriginalObject alloc] initWithBlock:^{
        objc_setAssociatedObject(self, @selector(objc_weak_id), nil, OBJC_ASSOCIATION_ASSIGN);
    }];
    // 这里关联的key必须唯一,如果使用_cmd,对一个对象多次关联的时候,前面的对象关联会失效。
// 给需要被 assign 修饰的对象添加一个 strong 对象.
    objc_setAssociatedObject(objc_weak_id, (__bridge const void *)(ob.block), ob, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    objc_setAssociatedObject(self, @selector(objc_weak_id), objc_weak_id, OBJC_ASSOCIATION_ASSIGN);
}
※ 方法二:借用NSMapTable弱持有对象

这个方法说起来有点儿丢人,其实还是借用了weak来实现weak。

我们可以给category加一个NSMapTable的属性,strong持有就可以,用这个table来存所有的weak属性,key就是属性名可以strong持有,value就是想weak持有的对象(需要设置为NSMapTableWeakMemory)。


3. crash分类

请参考:http://wzxing55.com/2018/11/30/ios-app-crash类型总结/


4. 为啥消息转发要有methodSignatureForSelector

消息转发大致分三步,最后一步就是forwardInvocation

消息转发

在给程序添加消息转发功能以前,必须覆盖两个方法,即methodSignatureForSelector:forwardInvocation:methodSignatureForSelector:的作用在于为另一个类实现的消息创建一个有效的方法签名,必须实现,并且返回不为空的methodSignature,否则会crash。

那么,为啥要有methodSignatureForSelector:这个方法的存在呢?

我们先看下NSInvocation的定义:

NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available")
@interface NSInvocation : NSObject

+ (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig;

@property (readonly, retain) NSMethodSignature *methodSignature;

- (void)retainArguments;
@property (readonly) BOOL argumentsRetained;

@property (nullable, assign) id target;
@property SEL selector;

methodSignatureForSelector:的作用就是返回方法签名,这个签名就是用于生成传给forwardInvocation:NSInvocation,所以如果你需要接受这个消息,必须让方法签名返回非空哦


5. app启动加载类过程

可参考:https://www.cnblogs.com/dengzhuli/p/4443134.html & https://www.jianshu.com/p/3bccab8f17b3

app启动的时候会先加载各个类,执行他们的load方法,然后才是application的生命周期回调~

在一个程序开始运行之前(在main函数开始执行之前),类文件开始被程序加载,load方法就会开始被执行;因此load方法总是在main函数之前调用。

当父类和子类都实现load方法时,父类的load方法会被先执行。load方法是系统自动加载的,因此不需要使用[super load]方法调用父类的load方法,否则父类的load方法会多次执行。在Category中写load方法是不会替换原始类中的load方法的,原始类和Category中的load方法都会被执行,原始类的load方法会先被执行,再执行Category中的load方法。当有多个Category都实现了load方法,在Compile Sources中文件的排放顺序就是这几个load方法装载顺序。特别注意的是:如果一个类没有实现load方法,那么就不会调用它父类的load方法。

具体可以参考:http://www.cocoachina.com/articles/16273


※ load时机

我们都知道load是类加载的时候做的,我开始想的是每个类会挨个加载,然后立刻调用该类的load。

如果基于这个设想,如果一个类Class A专门用来swizzle method交换方法,然后它在load里面会交换Class B的两个方法,如果Class B还没有load的时候A已经load了交换会成功么?会有crash么?

#import <objc/runtime.h>

#import "ClassB.h"
#import "ClassA.h"

@implementation ClassA

+ (void)load {
    NSLog(@"class a loaded");
    
    Method originalMethod = class_getInstanceMethod([ClassB class], @selector(print1));
    Method swizzledMethod = class_getInstanceMethod([ClassB class], @selector(print2));
    
    method_exchangeImplementations(originalMethod, swizzledMethod);
}

@end

========================
#import "ClassB.h"

@implementation ClassB

+ (void)load {
    NSLog(@"class b loaded");
}

- (void) print1 {
    NSLog(@"Print 1");
}

- (void) print2 {
    NSLog(@"Print 2");
}

@end

此时我在主VC里面初始化一个B对象,调用他的print1,打印的结果是:

2019-11-27 00:29:40.431287+0800 Example1[34414:339768] class a loaded
2019-11-27 00:29:47.682128+0800 Example1[34414:339768] class b loaded
2019-11-27 00:29:47.779207+0800 Example1[34414:339768] Print 2

也就是说虽然Class B的load方法在A之后,但是在A的load方法执行的时候,B的method们已经加载过了。

我还尝试了在A的load方法里面初始化一个B对象,结果也是正常的。虽然A的load方法先于B的load方法,但是实际上类都会先加载以后再执行他们的load方法,而非一个个挨个加载&执行load,是整体的加载后,挨个执行load。(大概对应上面文章里面的镜像加载以后执行load),但是还是不能确定在load的时候某个类一定存在,这个我理解可能是镜像不一样的情况之类的吧。


6. 静态库动态库

库(Library)说白了就是一段编译好的二进制代码,加上头文件就可以供别人使用。我们在和别人合作的时候,一种情况是某些代码需要给别人使用,但是我们不希望别人看到源码,就需要以库的形式进行封装,只暴露出头文件。另外一种情况是,对于某些不会进行大的改动的代码,我们想减少编译的时间,就可以把它打包成库,因为库是已经编译好的二进制了,编译的时候只需要 Link 一下,不会浪费编译时间。

  • 静态库:
    常用.a .lib,在链接阶段会将汇编生成的目标文件(.o)完整的复制到可执行文件中,所以如果两个程序都用了某个静态库,那么每个二进制可执行文件里面其实都含有这份静态库的代码。

  • 动态库:
    常用.so .framework .dl等,链接时不复制,在程序启动后用动态加载,然后再决议符号,所以理论上动态库只用存在一份,好多个程序都可以动态链接到这个动态库上面,达到了节省内存(不是磁盘是内存中只有一份动态库),还有另外一个好处,由于动态库并不绑定到可执行程序上,所以我们想升级这个动态库就很容易,windows和linux上面一般插件和模块机制都是这样实现的。
    好处是减少打包app的体积,共享内存,热更新(更新动态库);但缺点是由于动态库可以进行更新操作,容易被注入恶意代码,就会变得不稳定不安全。常用动态库有UIKit、libsystems、libobjc、CFFoundation框架等。系统框架以动态库的形式保存在/System/Library/Caches/com.apple.dyld/中,这样每个app都能使用这些库。也不需要每个app中都包含这些库。只需要在使用时调用就行

这里说的是常用类型哦~ framework格式可以是静态也可是动态库哈~ 感谢楼下小哥哥提示~

※ 动态库放在哪里?

在其它大部分平台上,动态库都可以用于不同应用间共享, 共享可执行文件,这就大大节省了内存。

iOS平台在iOS8 之前,苹果不允许第三方框架使用动态方式加载,从 iOS8 开始允许开发者有条件地创建和使用动态框架,这种框架叫做 Cocoa Touch Framework。

虽然同样是动态框架,但是和系统 framework 不同,app 中使用 Cocoa Touch Framework 制作的动态库在打包和提交 app 时会被放到 app main bundle 的根目录中,运行在沙盒里,而不是系统中。也就是说,不同的 app 就算使用了同样的 framework,但还是会有多份的框架被分别签名,打包和加载。不过 iOS8 上开放了 App Extension 功能,可以为一个应用创建插件,这样主app和插件之间共享动态库还是可行的

苹果系统专属的framework 是共享的(如UIKit,会放在系统目录),但是我们自己使用 Cocoa Touch Framework 制作的动态库是放到 app bundle 中,运行在沙盒中的。

关于build到可执行文件经历了神马可以参考:https://www.jianshu.com/p/fda47fdc94de

程序从编译到被翻译成汇编语言,最后链接.o文件生成可执行文件

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,711评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,079评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,194评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,089评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,197评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,306评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,338评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,119评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,541评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,846评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,014评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,694评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,322评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,026评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,257评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,863评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,895评论 2 351