Runtime

来源:http://www.cnblogs.com/EchoHG/archive/2017/06/17/7041778.html
-Advertisement-
Play Games

參考資料:ios模式詳解,runtime完整總結 類和對象 Objective-C語言是一門動態語言,它將很多靜態語言在編譯和鏈接時期做的事放到了運行時來處理。這種動態語言的優勢在於:我們寫代碼時更具靈活性,如我們可以把消息轉發給我們想要的對象,或者隨意交換一個方法的實現等。 這種特性意味著Obje ...


參考資料:ios模式詳解runtime完整總結 

類和對象

 

Objective-C語言是一門動態語言,它將很多靜態語言在編譯和鏈接時期做的事放到了運行時來處理。這種動態語言的優勢在於:我們寫代碼時更具靈活性,如我們可以把消息轉發給我們想要的對象,或者隨意交換一個方法的實現等。

 

這種特性意味著Objective-C不僅需要一個編譯器,還需要一個運行時系統來執行編譯的代碼。對於Objective-C來說,這個運行時系統就像一個操作系統一樣:它讓所有的工作可以正常的運行。這個運行時系統即Objc Runtime。Objc Runtime其實是一個Runtime庫,它基本上是用C和彙編寫的,這個庫使得C語言有了面向對象的能力。

 

runtime 概念


Objective-C 是基於 C 的,它為 C 添加了面向對象的特性。它將很多靜態語言在編譯和鏈接時期做的事放到了 runtime 運行時來處理,可以說 runtime 是我們 Objective-C 幕後工作者。

  • runtime(簡稱運行時),是一套 純C(C和彙編寫的) 的API。而 OC 就是 運行時機制,也就是在運行時候的一些機制,其中最主要的是 消息機制。

  • 對於 C 語言,函數的調用在編譯的時候會決定調用哪個函數。

  • OC的函數調用成為消息發送,屬於 動態調用過程。在編譯的時候並不能決定真正調用哪個函數,只有在真正運行的時候才會根據函數的名稱找到對應的函數來調用。

  • 事實證明:在編譯階段,OC 可以 調用任何函數,即使這個函數並未實現,只要聲明過就不會報錯,只有當運行的時候才會報錯,這是因為OC是運行時動態調用的。而 C 語言 調用未實現的函數 就會報錯

 類與對象基礎數據結構

Class

Objective-C類是由Class類型來表示的,它實際上是一個指向objc_class結構體的指針。它的定義如下:

typedef struct objc_class *Class;

 

查看objc/runtime.h中objc_class結構體的定義如下:
 1 struct objc_class {
 2     Class isa  OBJC_ISA_AVAILABILITY;
 3 
 4 #if !__OBJC2__
 5     Class super_class                       OBJC2_UNAVAILABLE;  // 父類
 6     const char *name                        OBJC2_UNAVAILABLE;  // 類名
 7     long version                            OBJC2_UNAVAILABLE;  // 類的版本信息,預設為0
 8     long info                               OBJC2_UNAVAILABLE;  // 類信息,供運行期使用的一些位標識
 9     long instance_size                      OBJC2_UNAVAILABLE;  // 該類的實例變數大小
10     struct objc_ivar_list *ivars            OBJC2_UNAVAILABLE;  // 該類的成員變數鏈表
11     struct objc_method_list **methodLists   OBJC2_UNAVAILABLE;  // 方法定義的鏈表
12     struct objc_cache *cache                OBJC2_UNAVAILABLE;  // 方法緩存
13     struct objc_protocol_list *protocols    OBJC2_UNAVAILABLE;  // 協議鏈表
14 #endif
15 
16 } OBJC2_UNAVAILABLE;

 

在這個定義中,下麵幾個欄位是我們感興趣的
  • isa:需要註意的是在Objective-C中,所有的類自身也是一個對象,這個對象的Class裡面也有一個isa指針,它指向metaClass(元類),我們會在後面介紹它。

  • super_class:指向該類的父類,如果該類已經是最頂層的根類(如NSObject或NSProxy),則super_class為NULL。

  • cache:用於緩存最近使用的方法。一個接收者對象接收到一個消息時,它會根據isa指針去查找能夠響應這個消息的對象。在實際使用中,這個對象只有一部分方法是常用的,很多方法其實很少用或者根本用不上。這種情況下,如果每次消息來時,我們都是methodLists中遍歷一遍,性能勢必很差。這時,cache就派上用場了。在我們每次調用過一個方法後,這個方法就會被緩存到cache列表中,下次調用的時候runtime就會優先去cache中查找,如果cache沒有,才去methodLists中查找方法。這樣,對於那些經常用到的方法的調用,但提高了調用的效率。

  • version:我們可以使用這個欄位來提供類的版本信息。這對於對象的序列化非常有用,它可是讓我們識別出不同類定義版本中實例變數佈局的改變。

 

Runtime庫主要做下麵幾件事:

封裝:在這個庫中,對象可以用C語言中的結構體表示,而方法可以用C函數來實現,另外再加上了一些額外的特性。這些結構體和函數被runtime函數封裝後,我們就可以在程式運行時創建,檢查,修改類、對象和它們的方法了。
找出方法的最終執行代碼:當程式執行[object doSomething]時,會向消息接收者(object)發送一條消息(doSomething),runtime會根據消息接收者是否能響應該消息而做出不同的反應。這將在後面詳細介紹。
Objective-C runtime目前有兩個版本:Modern runtime和Legacy runtime。Modern Runtime 覆蓋了64位的Mac OS X Apps,還有 iOS Apps,Legacy Runtime 是早期用來給32位 Mac OS X Apps 用的,也就是可以不用管就是了。

 

針對cache,我們用下麵例子來說明其執行過程:

1 NSArray *array = [[NSArray alloc] init];

 

其流程是:
  • [NSArray alloc]先被執行。因為NSArray沒有+alloc方法,於是去父類NSObject去查找。

  • 檢測NSObject是否響應+alloc方法,發現響應,於是檢測NSArray類,並根據其所需的記憶體空間大小開始分配記憶體空間,然後把isa指針指向NSArray類。同時,+alloc也被加進cache列表裡面。

  • 接著,執行-init方法,如果NSArray響應該方法,則直接將其加入cache;如果不響應,則去父類查找。

  • 在後期的操作中,如果再以[[NSArray alloc] init]這種方式來創建數組,則會直接從cache中取出相應的方法,直接調用。

 

objc_object與id

objc_object是表示一個類的實例的結構體,它的定義如下(objc/objc.h):

1 struct objc_object 
2 {
3     Class isa  OBJC_ISA_AVAILABILITY;
4 };
5 
6 typedef struct objc_object *id;

 

可以看到,這個結構體只有一個字體,即指向其類的isa指針。這樣,當我們向一個Objective-C對象發送消息時,運行時庫會根據實例對象的isa指針找到這個實例對象所屬的類。Runtime庫會在類的方法列表及父類的方法列表中去尋找與消息對應的selector指向的方法。找到後即運行這個方法。

當創建一個特定類的實例對象時,分配的記憶體包含一個objc_object數據結構,然後是類的實例變數的數據。NSObject類的alloc和allocWithZone:方法使用函數class_createInstance來創建objc_object數據結構。

另外還有我們常見的id,它是一個objc_object結構類型的指針。它的存在可以讓我們實現類似於C++中泛型的一些操作。

 

元類(Meta Class)

在上面我們提到,所有的類自身也是一個對象,我們可以向這個對象發送消息(即調用類方法)。如:

1 NSArray *array = [NSArray array];

 

這個例子中,+array消息發送給了NSArray類,而這個NSArray也是一個對象。既然是對象,那麼它也是一個objc_object指針,它包含一個指向其類的一個isa指針。那麼這些就有一個問題了,這個isa指針指向什麼呢?為了調用+array方法,這個類的isa指針必須指向一個包含這些類方法的一個objc_class結構體。這就引出了meta-class的概念

meta-class是一個類對象的類。

當我們向一個對象發送消息時,runtime會在這個對象所屬的這個類的方法列表中查找方法;而向一個類發送消息時,會在這個類的meta-class的方法列表中查找。

meta-class之所以重要,是因為它存儲著一個類的所有類方法。每個類都會有一個單獨的meta-class,因為每個類的類方法基本不可能完全相同。

再深入一下,meta-class也是一個類,也可以向它發送一個消息,那麼它的isa又是指向什麼呢?為了不讓這種結構無限延伸下去,Objective-C的設計者讓所有的meta-class的isa指向基類的meta-class,以此作為它們的所屬類。即,任何NSObject繼承體系下的meta-class都使用NSObject的meta-class作為自己的所屬類,而基類的meta-class的isa指針是指向它自己。這樣就形成了一個完美的閉環。

 

講了這麼多,我們還是來寫個例子吧:

 1 void TestMetaClass(id self, SEL _cmd) {
 2 
 3     NSLog(@"This objcet is %p", self);
 4     NSLog(@"Class is %@, super class is %@", [self class], [self superclass]);
 5 
 6     Class currentClass = [self class];
 7     for (int i = 0; i < 4; i++) {
 8         NSLog(@"Following the isa pointer %d times gives %p", i, currentClass);
 9         currentClass = objc_getClass((__bridge void *)currentClass);
10     }
11 
12     NSLog(@"NSObject's class is %p", [NSObject class]);
13     NSLog(@"NSObject's meta class is %p", objc_getClass((__bridge void *)[NSObject class]));
14 }
15 
16 #pragma mark -
17 
18 @implementation Test
19 
20 - (void)ex_registerClassPair {
21 
22     Class newClass = objc_allocateClassPair([NSError class], "TestClass", 0);
23     class_addMethod(newClass, @selector(testMetaClass), (IMP)TestMetaClass, "v@:");
24     objc_registerClassPair(newClass);
25 
26     id instance = [[newClass alloc] initWithDomain:@"some domain" code:0 userInfo:nil];
27     [instance performSelector:@selector(testMetaClass)];
28 }
29 
30 @end
View Code

 

這個例子是在運行時創建了一個NSError的子類TestClass,然後為這個子類添加一個方法testMetaClass,這個方法的實現是TestMetaClass函數。

運行後,列印結果是

2014-10-20 22:57:07.352 mountain[1303:41490] This objcet is 0x7a6e22b0
2014-10-20 22:57:07.353 mountain[1303:41490] Class is TestStringClass, super class is NSError
2014-10-20 22:57:07.353 mountain[1303:41490] Following the isa pointer 0 times gives 0x7a6e21b0
2014-10-20 22:57:07.353 mountain[1303:41490] Following the isa pointer 1 times gives 0x0
2014-10-20 22:57:07.353 mountain[1303:41490] Following the isa pointer 2 times gives 0x0
2014-10-20 22:57:07.353 mountain[1303:41490] Following the isa pointer 3 times gives 0x0
2014-10-20 22:57:07.353 mountain[1303:41490] NSObject's class is 0xe10000

 

2014-10-20 22:57:07.354 mountain[1303:41490] NSObject's meta class is 0x0

我們在for迴圈中,我們通過objc_getClass來獲取對象的isa,並將其列印出來,依此一直回溯到NSObject的meta-class。分析列印結果,可以看到最後指針指向的地址是0x0,即NSObject的meta-class的類地址。

這裡需要註意的是:我們在一個類對象調用class方法是無法獲取meta-class,它只是返回類而已。

 

 

runtime 常見作用


  • 動態交換兩個方法的實現

  • 動態添加屬性

  • 實現字典轉模型的自動轉換

  • 發送消息

  • 動態添加方法 (面試用到)

  • 攔截並替換方法

  • 實現 NSCoding 的自動歸檔和解檔

 

動態添加方法

應用場景:如果一個類方法非常多,載入類到記憶體的時候也比較耗費資源,需要給每個方法生成映射表,可以使用動態給某個類,添加方法解決。

註解:OC 中我們很習慣的會用懶載入,當用到的時候才去載入它,但是實際上只要一個類實現了某個方法,就會被載入進記憶體。當我們不想載入這麼多方法的時候,就會使用到 runtime 動態的添加方法。

 

實現NSCoding的自動歸檔和解檔

如果你實現過自定義模型數據持久化的過程,那麼你也肯定明白,如果一個模型有許多個屬性,那麼我們需要對每個屬性都實現一遍encodeObject 和 decodeObjectForKey方法,如果這樣的模型又有很多個,這還真的是一個十分麻煩的事情。下麵來看看簡單的實現方式。

 

runtime 消息機制


我們寫 OC 代碼,它在運行的時候也是轉換成了 runtime 方式運行的。任何方法調用本質:就是發送一個消息(用 runtime發送消息,OC 底層實現通過 runtime 實現)。

消息機制原理:對象根據方法編號SEL去映射表查找對應的方法實現。

每一個 OC 的方法,底層必然有一個與之對應的 runtime 方法。

 

示例代碼:OC 方法-->runtime 方法

說明: eat(無參) 和 run(有參) 是 Person模型類中的私有方法「可以幫我調用私有方法」;
 1 // Person *p = [Person alloc];
 2 // 底層的實際寫法
 3 Person *p = objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"));
 4 
 5 // p = [p init];
 6 p = objc_msgSend(p, sel_registerName("init"));
 7 
 8 // 調用對象方法(本質:讓對象發送消息)
 9 //[p eat];
10 
11 // 本質:讓類對象發送消息
12 objc_msgSend(p, @selector(eat));
13 objc_msgSend([Person class], @selector(run:),20);
14 
15 //--------------------------- <#我是分割線#> ------------------------------//
16 // 也許下麵這種好理解一點
17 
18 // id objc = [NSObject alloc];
19 id objc = objc_msgSend([NSObject class], @selector(alloc));
20 
21 // objc = [objc init];
22 objc = objc_msgSend(objc, @selector(init));
View Code

 

runtime 方法調用流程「消息機制」


面試:消息機制方法調用流程

  • 怎麼去調用eat方法,對象方法:(保存到類對象的方法列表) ,類方法:(保存到元類(Meta Class)中方法列表)。
    • 1.OC 在向一個對象發送消息時,runtime 庫會根據對象的 isa指針找到該對象對應的類或其父類中查找方法。。
    • 2.註冊方法編號(這裡用方法編號的好處,可以快速查找)。
    • 3.根據方法編號去查找對應方法。
    • 4.找到只是最終函數實現地址,根據地址去方法區調用對應函數。
  • 補充:一個objc 對象的 isa 的指針指向什麼?有什麼作用?
    • 每一個對象內部都有一個isa指針,這個指針是指向它的真實類型,根據這個指針就能知道將來調用哪個類的方法。
 

runtime 下Class的各項操作

 

  • 獲取屬性列表

    1 objc_property_t *propertyList = class_copyPropertyList([self class], &count);
    2 for (unsigned int i=0; i<count; i++) {
    3    const char *propertyName = property_getName(propertyList[i]);
    4    NSLog(@"property---->%@", [NSString stringWithUTF8String:propertyName]);
    5 }

     

  • 獲取方法列表
    1 Method *methodList = class_copyMethodList([self class], &count);
    2 for (unsigned int i; i<count; i++) {
    3    Method method = methodList[i];
    4    NSLog(@"method---->%@", NSStringFromSelector(method_getName(method)));
    5 }
    
    

     

    
    
  • 獲取成員變數列表

  • 1 Ivar *ivarList = class_copyIvarList([self class], &count);
    2 for (unsigned int i; i<count; i++) {
    3     Ivar myIvar = ivarList[i];
    4     const char *ivarName = ivar_getName(myIvar);
    5     NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]);
    6 }

     

  • 獲取協議列表

  •  

    1 __unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
    2 for (unsigned int i; i<count; i++) {
    3     Protocol *myProtocal = protocolList[i];
    4     const char *protocolName = protocol_getName(myProtocal);
    5     NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]);
    6 }

     

 

現在有一個Person類,和person創建的xiaoming對象,有test1和test2兩個方法

 

  • 獲得類方法

    1 Class PersonClass = object_getClass([Person class]);
    2 SEL oriSEL = @selector(test1);
    3 Method oriMethod = _class_getMethod(xiaomingClass, oriSEL);

     

  • 獲得實例方法
  • 1 Class PersonClass = object_getClass([xiaoming class]);
    2 SEL oriSEL = @selector(test2);
    3 Method cusMethod = class_getInstanceMethod(xiaomingClass, oriSEL);

     

  • 添加方法
  • BOOL addSucc = class_addMethod(xiaomingClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));

     

    
    
  • 替換原方法實現
  • class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));

     

  • 交換兩個方法的實現

    method_exchangeImplementations(oriMethod, cusMethod);
runtime 幾個參數概念

 

1、objc_msgSend

這是個最基本的用於發送消息的函數。其實編譯器會根據情況在objc_msgSend, objc_msgSend_stret,,objc_msgSendSuper, 或 objc_msgSendSuper_stret 四個方法中選擇一個來調用。如果消息是傳遞給超類,那麼會調用名字帶有 Super 的函數;如果消息返回值是數據結構而不是簡單值時,那麼會調用名字帶有stret的函數。

 

2、SEL
objc_msgSend函數第二個參數類型為SEL,它是selector在Objc中的表示類型(Swift中是Selector類)。selector是方法選擇器,可以理解為區分方法的 ID,而這個 ID 的數據結構是SEL:
typedef struct objc_selector *SEL;
其實它就是個映射到方法的C字元串,你可以用 Objc 編譯器命令@selector()``或者 Runtime 系統的sel_registerName函數來獲得一個SEL類型的方法選擇器。

 

3、id

objc_msgSend第一個參數類型為id,大家對它都不陌生,它是一個指向類實例的指針:
typedef struct objc_object *id;
objc_object又是啥呢:
struct objc_object { Class isa; };
objc_object結構體包含一個isa指針,根據isa指針就可以順藤摸瓜找到對象所屬的類。

 

4、runtime.h里Class的定義

 1 struct objc_class {
 2     Class isa  OBJC_ISA_AVAILABILITY;//每個Class都有一個isa指針
 3 
 4 #if !__OBJC2__
 5     Class super_class                                        OBJC2_UNAVAILABLE;//父類
 6     const char *name                                         OBJC2_UNAVAILABLE;//類名
 7     long version                                             OBJC2_UNAVAILABLE;//類版本
 8     long info                                                OBJC2_UNAVAILABLE;//!*!供運行期使用的一些位標識。如:CLS_CLASS (0x1L)表示該類為普通class; CLS_META(0x2L)表示該類為metaclass等(runtime.h中有詳細列出)
 9     long instance_size                                       OBJC2_UNAVAILABLE;//實例大小
10     struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;//存儲每個實例變數的記憶體地址
11     struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;//!*!根據info的信息確定是類還是實例,運行什麼函數方法等
12     struct objc_cache *cache                                 OBJC2_UNAVAILABLE;//緩存
13     struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;//協議
14 #endif
15 
16 } OBJC2_UNAVAILABLE;
17 
18  

 

 

可以看到運行時一個類還關聯了它的超類指針,類名,成員變數,方法,緩存,還有附屬的協議。
objc_class結構體中:`ivars是objc_ivar_list指針;methodLists是指向objc_method_list指針的指針。也就是說可以動態修改*methodLists的值來添加成員方法,這也是Category`實現的原理。

 

什麼是 method swizzling(俗稱黑魔法)

  • 簡單說就是進行方法交換

  • Objective-C中調用一個方法,其實是向一個對象發送消息,查找消息的唯一依據是selector的名字。利用Objective-C的動態特性,可以實現在運行時偷換selector對應的方法實現,達到給方法掛鉤的目的

  • 每個類都有一個方法列表,存放著方法的名字和方法實現的映射關係,selector的本質其實就是方法名,IMP有點類似函數指針,指向具體的Method實現,通過selector就可以找到對應的IMP


selector --> 對應的IMP
    • 交換方法的幾種實現方式
      • 利用 method_exchangeImplementations 交換兩個方法的實現
      • 利用 class_replaceMethod 替換方法的實現
      • 利用 method_setImplementation 來直接設置某個方法的IMP

                                        交換方法  

 

 

 

類型編碼(Type Encoding)

作為對Runtime的補充,編譯器將每個方法的返回值和參數類型編碼為一個字元串,並將其與方法的selector關聯在一起。這種編碼方案在其它情況下也是非常有用的,因此我們可以使用@encode編譯器指令來獲取它。當給定一個類型時,@encode返回這個類型的字元串編碼。這些類型可以是諸如int、指針這樣的基本類型,也可以是結構體、類等類型。事實上,任何可以作為sizeof()操作參數的類型都可以用於@encode()。

在Objective-C Runtime Programming Guide中的Type Encoding一節中,列出了Objective-C中所有的類型編碼。需要註意的是這些類型很多是與我們用於存檔和分發的編碼類型是相同的。但有一些不能在存檔時使用。

註:Objective-C不支持long double類型。@encode(long double)返回d,與double是一樣的。

一個數組的類型編碼位於方括弧中;其中包含數組元素的個數及元素類型。如以下示例:

1 float a[] = {1.0, 2.0, 3.0};
2 NSLog(@"array encoding type: %s", @encode(typeof(a)));

 

輸出是:
2014-10-28 11:44:54.731 RuntimeTest[942:50791] array encoding type: [3f]

 

其它類型可參考Type Encoding,在此不細說。

另外,還有些編碼類型,@encode雖然不會直接返回它們,但它們可以作為協議中聲明的方法的類型限定符。可以參考Type Encoding。

對於屬性而言,還會有一些特殊的類型編碼,以表明屬性是只讀、拷貝、retain等等,詳情可以參考Property Type String。

 

方法和消息

SEL又叫選擇器,是表示一個方法的selector的指針,其定義如下:

typedef struct objc_selector *SEL;

objc_selector結構體的詳細定義沒有在頭文件中找到。方法的selector用於表示運行時方 法的名字。Objective-C在編譯時,會依據每一個方法的名字、參數序列,生成一個唯一的整型標識(Int類型的地址),這個標識就是SEL。如下 代碼所示:

SEL sel1 = @selector(method1);
NSLog(@"sel : %p", sel1);

上面的輸出為:

2014-10-30 18:40:07.518 RuntimeTest[52734:466626] sel : 0x100002d72

兩個類之間,不管它們是父類與子類的關係,還是之間沒有這種關係,只要方法名相同,那麼方法的SEL就是一樣的。每一個方法都對應著一個SEL。所以在 Objective-C同一個類(及類的繼承體系)中,不能存在2個同名的方法,即使參數類型不同也不行。相同的方法只能對應一個SEL。這也就導致 Objective-C在處理相同方法名且參數個數相同但類型不同的方法方面的能力很差。如在某個類中定義以下兩個方法:

- (void)setWidth:(int)width;
- (void)setWidth:(double)width;

當然,不同的類可以擁有相同的selector,這個沒有問題。不同類的實例對象執行相同的selector時,會在各自的方法列表中去根據selector去尋找自己對應的IMP。

工程中的所有的SEL組成一個Set集合,Set的特點就是唯一,因此SEL是唯一的。因此,如果我們想到這個方法集合中查找某個方法時,只需要去 找到這個方法對應的SEL就行了,SEL實際上就是根據方法名hash化了的一個字元串,而對於字元串的比較僅僅需要比較他們的地址就可以了,可以說速度 上無語倫比!!但是,有一個問題,就是數量增多會增大hash衝突而導致的性能下降(或是沒有衝突,因為也可能用的是perfect hash)。但是不管使用什麼樣的方法加速,如果能夠將總量減少(多個方法可能對應同一個SEL),那將是最犀利的方法。那麼,我們就不難理解,為什麼 SEL僅僅是函數名了。

本質上,SEL只是一個指向方法的指針(準確的說,只是一個根據方法名hash化了的KEY值,能唯一代表一個方法),它的存在只是為了加快方法的查詢速度。這個查找過程我們將在下麵討論。

我們可以在運行時添加新的selector,也可以在運行時獲取已存在的selector,我們可以通過下麵三種方法來獲取SEL:

  1. sel_registerName函數

  2. Objective-C編譯器提供的@selector()

  3. NSSelectorFromString()方法

     

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 相關技能 HTML5+CSS3(實現頁面佈局和動態效果) Iconfont(使用矢量圖標庫添加播放器相關圖標) LESS (動態CSS編寫) jQuery(快速編寫js腳本) gulp+webpack(自動化構建工具,實現LESS,CSS,JS等編譯和壓縮代碼) 實現的功能 播放暫停(點擊切換播放狀 ...
  • 今天介紹一個網路上並不常用的插件two.js,剛開始學習的過程中,發現網上並沒有合適的教程,在此發表基本操作 two.js是一款網頁二維繪圖軟體,可以在指定區域內產生自設的各種動畫效果 下載網址如下: https://two.js.org/#download class1: 一:如何使用: 首先在頁 ...
  • 做頁面需要兩個時間輸入框一個顯示當前時間,一個顯示之前的時間,並且需要一個select下拉框控制兩個時間輸入框之間的差,效果如下圖: 這裡使用的是My97DatePicer,簡單方便,引入my97插件,設置input時間框的格式,這裡設置的時間最大是當前時間,開始時間框不能比結束時間框的時間大 弄完 ...
  • 前言 個人觀點,供您參考 觀點源自作者的使用經驗和日常研究 排名基於框架的受歡迎度, 語法結構, 易用性等特性 ...
  • 今天逛園子,偶然看到最多推薦,有點好奇。 F12查看元素,發現是在css中加了一個after,內容中增加了一個“w”。 本著娛樂至上的準則,自己也試試。複製以下css到設置自定義css中 #digg_count:after{ content: 'w'; } :after, :before { web ...
  • 1、BUG-In android7 phone can not slide above 註:Android 7.0以上,iScroll滑動緩慢遲鈍問題解決 What browser are you using? There was a fix to iScroll's handling of pas ...
  • 介紹 vue schart 是使用vue.js封裝了sChart.js圖表庫的一個小組件。支持vue.js 1.x & 2.x 倉庫地址: "https://github.com/lin xin/vue schart" sChart.js 作為一個小型簡單的圖表庫,沒有過多的圖表類型,只包含了柱狀圖 ...
  • 本來是在看阮大神寫的ajax教程,突然發現點擊目錄文字會跳轉到相對應的文本內容,於是乎激發了我的興趣。 這個究竟怎麼做的,剛開始看的時候一知半解,找度娘就是:“點擊跳轉到頁面指定位置”,找了下,原來專業術語叫:錨點。 度娘真是個博大精深的地方,有著多種的方法可以使用。 最簡單的一種: 設置a標簽的錨 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...