因?yàn)橹暗墓静昧艘徊▎T以及自己的能力太差了,衍生了想換家公司的想法,也挺神奇的,其實(shí)面試真的是相當(dāng)順利,但選擇實(shí)在是太難了,遇到了超級nice的小哥哥大哥哥們,以及hr小姐姐們~ 很幸運(yùn)被大家value,也能成為彼此生命中的過客/朋友,雖然我實(shí)在是太渣了。。
anyway終于塵埃落定,以后要跟著超級厲害的超級溫柔的奶爸mentor小哥哥混啦~ 以及要跟著同組的超級厲害的大神們學(xué)習(xí)寫代碼了,真的超級慌的,他們怎么能寫的這么好。。于是開啟一個(gè)新的collection記錄日常的小知識吧~
目錄:
- GCD如何cancel任務(wù)
- category如何加個(gè)weak屬性
- crash分類
- 為啥消息轉(zhuǎn)發(fā)要有methodSignatureForSelector
- app啟動(dòng)加載類過程
- 靜態(tài)庫動(dòng)態(tài)庫
1. GCD如何cancel任務(wù)
NSOperation其實(shí)就是封裝了GCD,但NSOperation是可以cancel任務(wù)的,那么GCD是咋做的嘞~
可以參考:http://www.lxweimin.com/p/ead365ef069f
iOS8之后提供了
dispatch_block_cancel
這個(gè)接口來cancel之前已經(jīng)拋入的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會不會被執(zhí)行呢?答案是不會哦,除了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這個(gè)事兒是怎樣的嘞~ cancel可以取消還沒分配給線程去執(zhí)行的任務(wù),但是如果已經(jīng)拋給線程了就不能取消啦;dispatch_after會在規(guī)定時(shí)間將任務(wù)拋入,cancel也可以取消掉還沒有拋入的任務(wù)。 所以其實(shí)cancel是不一定能夠cancel掉的,是有隨機(jī)性的,要看是不是已經(jīng)開始執(zhí)行了,開始就不能取消啦。
2. category如何加個(gè)weak屬性
參考:http://www.lxweimin.com/p/18d8cd4ff6c6
這個(gè)問題的源頭是其實(shí)管理對象是不提供weak的policy的,那么category要怎么實(shí)現(xiàn)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. */
};
我們要做的就是如何讓被持有對象沒有引用計(jì)數(shù),并且銷毀以后我們的持有也會被釋放。
※ 方法一:借用dealloc block來置空持有
雖然關(guān)聯(lián)對象沒有提供weak
的policy,但是它提供了assign的,或者strong的。(注意其實(shí)assign可以修飾對象只是dealloc以后再訪問會crash,而基礎(chǔ)數(shù)據(jù)是不能用strong修飾的哦,因?yàn)榛A(chǔ)數(shù)據(jù)不算對象)如果我們能做到在對象dealloc的時(shí)候清空指針就可以啦。
但是如果對象要能走到dealloc說明已經(jīng)沒有引用了,所以這里我們只能用assign修飾它,然后dealloc是清空指針。
但是我們是不能用category覆寫dealloc的,因?yàn)槠鋵?shí)如果覆寫了就完全覆蓋了對象本來的方法。那么要怎么知道對象銷毀了呢?
對象銷毀的時(shí)候會析構(gòu)把它持有的引用也就是property們都置空,如果我們給它設(shè)置一個(gè)strong的property,它dealloc的時(shí)候會把這個(gè)property也dealloc,而這個(gè)property我們是可以隨意覆寫dealloc
的只是作為一個(gè)中間對象而已。
所以這個(gè)strong的property可以是醬紫的:
// 定義一個(gè)對象,使用block來回調(diào)析構(gòu)函數(shù)。
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);
}];
// 這里關(guān)聯(lián)的key必須唯一,如果使用_cmd,對一個(gè)對象多次關(guān)聯(lián)的時(shí)候,前面的對象關(guān)聯(lián)會失效。
// 給需要被 assign 修飾的對象添加一個(gè) 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弱持有對象
這個(gè)方法說起來有點(diǎn)兒丟人,其實(shí)還是借用了weak來實(shí)現(xiàn)weak。
我們可以給category加一個(gè)NSMapTable
的屬性,strong持有就可以,用這個(gè)table來存所有的weak屬性,key就是屬性名可以strong持有,value就是想weak持有的對象(需要設(shè)置為NSMapTableWeakMemory
)。
3. crash分類
請參考:http://wzxing55.com/2018/11/30/ios-app-crash類型總結(jié)/
4. 為啥消息轉(zhuǎn)發(fā)要有methodSignatureForSelector
消息轉(zhuǎn)發(fā)大致分三步,最后一步就是forwardInvocation
。
在給程序添加消息轉(zhuǎn)發(fā)功能以前,必須覆蓋兩個(gè)方法,即
methodSignatureForSelector:
和forwardInvocation:
。methodSignatureForSelector:
的作用在于為另一個(gè)類實(shí)現(xiàn)的消息創(chuàng)建一個(gè)有效的方法簽名,必須實(shí)現(xiàn),并且返回不為空的methodSignature,否則會crash。
那么,為啥要有methodSignatureForSelector:
這個(gè)方法的存在呢?
我們先看下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:
的作用就是返回方法簽名,這個(gè)簽名就是用于生成傳給forwardInvocation:
的NSInvocation
的,所以如果你需要接受這個(gè)消息,必須讓方法簽名返回非空哦
5. app啟動(dòng)加載類過程
可參考:https://www.cnblogs.com/dengzhuli/p/4443134.html & http://www.lxweimin.com/p/3bccab8f17b3
app啟動(dòng)的時(shí)候會先加載各個(gè)類,執(zhí)行他們的load方法,然后才是application的生命周期回調(diào)~
在一個(gè)程序開始運(yùn)行之前(在main函數(shù)開始執(zhí)行之前),類文件開始被程序加載,load方法就會開始被執(zhí)行;因此load方法總是在main函數(shù)之前調(diào)用。
當(dāng)父類和子類都實(shí)現(xiàn)load方法時(shí),父類的load方法會被先執(zhí)行。load方法是系統(tǒng)自動(dòng)加載的,因此不需要使用[super load]方法調(diào)用父類的load方法,否則父類的load方法會多次執(zhí)行。在Category中寫load方法是不會替換原始類中的load方法的,原始類和Category中的load方法都會被執(zhí)行,原始類的load方法會先被執(zhí)行,再執(zhí)行Category中的load方法。當(dāng)有多個(gè)Category都實(shí)現(xiàn)了load方法,在Compile Sources中文件的排放順序就是這幾個(gè)load方法裝載順序。特別注意的是:如果一個(gè)類沒有實(shí)現(xiàn)load方法,那么就不會調(diào)用它父類的load方法。
具體可以參考:http://www.cocoachina.com/articles/16273
※ load時(shí)機(jī)
我們都知道load是類加載的時(shí)候做的,我開始想的是每個(gè)類會挨個(gè)加載,然后立刻調(diào)用該類的load。
如果基于這個(gè)設(shè)想,如果一個(gè)類Class A專門用來swizzle method交換方法,然后它在load里面會交換Class B的兩個(gè)方法,如果Class B還沒有l(wèi)oad的時(shí)候A已經(jīng)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
此時(shí)我在主VC里面初始化一個(gè)B對象,調(diào)用他的print1,打印的結(jié)果是:
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方法執(zhí)行的時(shí)候,B的method們已經(jīng)加載過了。
我還嘗試了在A的load方法里面初始化一個(gè)B對象,結(jié)果也是正常的。雖然A的load方法先于B的load方法,但是實(shí)際上類都會先加載以后再執(zhí)行他們的load方法,而非一個(gè)個(gè)挨個(gè)加載&執(zhí)行l(wèi)oad,是整體的加載后,挨個(gè)執(zhí)行l(wèi)oad。(大概對應(yīng)上面文章里面的鏡像加載以后執(zhí)行l(wèi)oad),但是還是不能確定在load的時(shí)候某個(gè)類一定存在,這個(gè)我理解可能是鏡像不一樣的情況之類的吧。
6. 靜態(tài)庫動(dòng)態(tài)庫
庫(Library)說白了就是一段編譯好的二進(jìn)制代碼,加上頭文件就可以供別人使用。我們在和別人合作的時(shí)候,一種情況是某些代碼需要給別人使用,但是我們不希望別人看到源碼,就需要以庫的形式進(jìn)行封裝,只暴露出頭文件。另外一種情況是,對于某些不會進(jìn)行大的改動(dòng)的代碼,我們想減少編譯的時(shí)間,就可以把它打包成庫,因?yàn)閹焓且呀?jīng)編譯好的二進(jìn)制了,編譯的時(shí)候只需要 Link 一下,不會浪費(fèi)編譯時(shí)間。
靜態(tài)庫:
常用.a .lib,在鏈接階段會將匯編生成的目標(biāo)文件(.o)完整的復(fù)制到可執(zhí)行文件中,所以如果兩個(gè)程序都用了某個(gè)靜態(tài)庫,那么每個(gè)二進(jìn)制可執(zhí)行文件里面其實(shí)都含有這份靜態(tài)庫的代碼。動(dòng)態(tài)庫:
常用.so .framework .dl等,鏈接時(shí)不復(fù)制,在程序啟動(dòng)后用動(dòng)態(tài)加載,然后再?zèng)Q議符號,所以理論上動(dòng)態(tài)庫只用存在一份,好多個(gè)程序都可以動(dòng)態(tài)鏈接到這個(gè)動(dòng)態(tài)庫上面,達(dá)到了節(jié)省內(nèi)存(不是磁盤是內(nèi)存中只有一份動(dòng)態(tài)庫),還有另外一個(gè)好處,由于動(dòng)態(tài)庫并不綁定到可執(zhí)行程序上,所以我們想升級這個(gè)動(dòng)態(tài)庫就很容易,windows和linux上面一般插件和模塊機(jī)制都是這樣實(shí)現(xiàn)的。
好處是減少打包app的體積,共享內(nèi)存,熱更新(更新動(dòng)態(tài)庫);但缺點(diǎn)是由于動(dòng)態(tài)庫可以進(jìn)行更新操作,容易被注入惡意代碼,就會變得不穩(wěn)定不安全。常用動(dòng)態(tài)庫有UIKit、libsystems、libobjc、CFFoundation框架等。系統(tǒng)框架以動(dòng)態(tài)庫的形式保存在/System/Library/Caches/com.apple.dyld/中,這樣每個(gè)app都能使用這些庫。也不需要每個(gè)app中都包含這些庫。只需要在使用時(shí)調(diào)用就行
這里說的是常用類型哦~ framework格式可以是靜態(tài)也可是動(dòng)態(tài)庫哈~ 感謝樓下小哥哥提示~
※ 動(dòng)態(tài)庫放在哪里?
在其它大部分平臺上,動(dòng)態(tài)庫都可以用于不同應(yīng)用間共享, 共享可執(zhí)行文件,這就大大節(jié)省了內(nèi)存。
iOS平臺在iOS8 之前,蘋果不允許第三方框架使用動(dòng)態(tài)方式加載,從 iOS8 開始允許開發(fā)者有條件地創(chuàng)建和使用動(dòng)態(tài)框架,這種框架叫做 Cocoa Touch Framework。
雖然同樣是動(dòng)態(tài)框架,但是和系統(tǒng) framework 不同,app 中使用 Cocoa Touch Framework 制作的動(dòng)態(tài)庫在打包和提交 app 時(shí)會被放到 app main bundle 的根目錄中,運(yùn)行在沙盒里,而不是系統(tǒng)中。也就是說,不同的 app 就算使用了同樣的 framework,但還是會有多份的框架被分別簽名,打包和加載。不過 iOS8 上開放了 App Extension 功能,可以為一個(gè)應(yīng)用創(chuàng)建插件,這樣主app和插件之間共享動(dòng)態(tài)庫還是可行的。
蘋果系統(tǒng)專屬的framework 是共享的(如UIKit,會放在系統(tǒng)目錄),但是我們自己使用 Cocoa Touch Framework 制作的動(dòng)態(tài)庫是放到 app bundle 中,運(yùn)行在沙盒中的。
關(guān)于build到可執(zhí)行文件經(jīng)歷了神馬可以參考:http://www.lxweimin.com/p/fda47fdc94de
程序從編譯到被翻譯成匯編語言,最后鏈接.o文件生成可執(zhí)行文件