1. 程式人生 > >利用KVO重新整理UITableView和KVO的實現機理

利用KVO重新整理UITableView和KVO的實現機理

一,前言

Objective-C 中的鍵(key)-值(value)觀察(KVO)並不是什麼新鮮事物,它來源於設計模式中的觀察者模式,其基本思想就是:

一個目標物件管理所有依賴於它的觀察者物件,並在它自身的狀態改變時主動通知觀察者物件。這個主動通知通常是通過呼叫各觀察者物件所提供的介面方法來實現的。觀察者模式較完美地將目標物件與觀察者物件解耦。

在 Objective-C 中有兩種使用鍵值觀察的方式:手動或自動,此外還支援註冊依賴鍵(即一個鍵依賴於其他鍵,其他鍵的變化也會作用到該鍵)。下面將一一講述這些,並會深入 Objective-C 內部一窺鍵值觀察是如何實現的。

本文原始碼下載:點此下載

二,運用鍵值觀察

1,註冊與解除註冊

如果我們已經有了包含可供鍵值觀察屬性的類,那麼就可以通過在該類的物件(被觀察物件)上呼叫名為 NSKeyValueObserverRegistration 的 category 方法將觀察者物件與被觀察者物件註冊與解除註冊:

- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
- (void
)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

這兩個方法的定義在 Foundation/NSKeyValueObserving.h 中,NSObject,NSArray,NSSet均實現了以上方法,因此我們不僅可以觀察普通物件,還可以觀察陣列或結合類物件。在該標頭檔案中,我們還可以看到 NSObject 還實現了 NSKeyValueObserverNotification 的 category 方法(更多類似方法,請檢視該標頭檔案):

- (void)willChangeValueForKey:(NSString *)key;
- (void
)didChangeValueForKey:(NSString *)key;

這兩個方法在手動實現鍵值觀察時會用到,暫且不提。

值得注意的是:不要忘記解除註冊,否則會導致資源洩露。

2,設定屬性

將觀察者與被觀察者註冊好之後,就可以對觀察者物件的屬性進行操作,這些變更操作就會被通知給觀察者物件。注意,只有遵循 KVO 方式來設定屬性,觀察者物件才會獲取通知,也就是說遵循使用屬性的 setter 方法,或通過 key-path 來設定:

[target setAge:30]; 
[target setValue:[NSNumber numberWithInt:30] forKey:@"age"];

3,處理變更通知

觀察者需要實現名為 NSKeyValueObserving 的 category 方法來處理收到的變更通知:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;

在這裡,change 這個字典儲存了變更資訊,具體是哪些資訊取決於註冊時的 NSKeyValueObservingOptions。

4,下面來看看一個完整的使用示例:

觀察者類:

// Observer.h@interface Observer : NSObject
@end

// Observer.m#import "Observer.h"
#import <objc/runtime.h>
#import "Target.h"

@implementation Observer

- (void) observeValueForKeyPath:(NSString *)keyPath
                       ofObject:(id)object 
                         change:(NSDictionary *)change
                        context:(void *)context
{
    if ([keyPath isEqualToString:@"age"])
    {
        Class classInfo = (Class)context;
        NSString * className = [NSString stringWithCString:object_getClassName(classInfo)
                                                  encoding:NSUTF8StringEncoding];
        NSLog(@" >> class: %@, Age changed", className);

        NSLog(@" old age is %@", [change objectForKey:@"old"]);
        NSLog(@" new age is %@", [change objectForKey:@"new"]);
    }
    else
    {
        [super observeValueForKeyPath:keyPath
                             ofObject:object
                               change:change
                              context:context];
    }
}

@end

注意:在實現處理變更通知方法 observeValueForKeyPath 時,要將不能處理的 key 轉發給 super 的 observeValueForKeyPath 來處理。

使用示例:

Observer * observer = [[[Observer alloc] init] autorelease];

Target * target = [[[Target alloc] init] autorelease];
[target addObserver:observer
         forKeyPath:@"age"
            options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
            context:[Target class]];

[target setAge:30];
//[target setValue:[NSNumber numberWithInt:30] forKey:@"age"];
[target removeObserver:observer forKeyPath:@"age"];

在這裡 observer 觀察 target 的 age 屬性變化,執行結果如下:

  >> class: Target, Age changed

  old age is 10

  new age is 30

三,手動實現鍵值觀察

上面的 Target 應該怎麼實現呢?首先來看手動實現。

@interface Target : NSObject
{
    int age;
}

// for manual KVO - age- (int) age;
- (void) setAge:(int)theAge;

@end

@implementation Target

- (id) init
{
    self = [super init];
    if (nil != self)
    {
        age = 10;
    }
    
    return self;
}

// for manual KVO - age- (int) age
{
    return age;
}

- (void) setAge:(int)theAge
{
    [self willChangeValueForKey:@"age"];
    age = theAge;
    [self didChangeValueForKey:@"age"];
}

+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key {
    if ([key isEqualToString:@"age"]) {
        return NO;
    }

    return [super automaticallyNotifiesObserversForKey:key];
}

@end

首先,需要手動實現屬性的 setter 方法,並在設定操作的前後分別呼叫 willChangeValueForKey: 和 didChangeValueForKey方法,這兩個方法用於通知系統該 key 的屬性值即將和已經變更了;

其次,要實現類方法 automaticallyNotifiesObserversForKey,並在其中設定對該 key 不自動傳送通知(返回 NO 即可)。這裡要注意,對其它非手動實現的 key,要轉交給 super 來處理。

四,自動實現鍵值觀察

自動實現鍵值觀察就非常簡單了,只要使用了屬性即可。

@interface Target : NSObject// for automatic KVO - age@property (nonatomic, readwrite) int age;
@end

@implementation Target
@synthesize age; // for automatic KVO - age
- (id) init
{
    self = [super init];
    if (nil != self)
    {
        age = 10;
    }
    
    return self;
}

@end

五,鍵值觀察依賴鍵

有時候一個屬性的值依賴於另一物件中的一個或多個屬性,如果這些屬性中任一屬性的值發生變更,被依賴的屬性值也應當為其變更進行標記。因此,object 引入了依賴鍵。

1,觀察依賴鍵

觀察依賴鍵的方式與前面描述的一樣,下面先在 Observer 的 observeValueForKeyPath:ofObject:change:context: 中新增處理變更通知的程式碼:

#import "TargetWrapper.h"

- (void) observeValueForKeyPath:(NSString *)keyPath
                       ofObject:(id)object 
                         change:(NSDictionary *)change
                        context:(void *)context
{
    if ([keyPath isEqualToString:@"age"])
    {
        Class classInfo = (Class)context;
        NSString * className = [NSString stringWithCString:object_getClassName(classInfo)
                                                  encoding:NSUTF8StringEncoding];
        NSLog(@" >> class: %@, Age changed", className);

        NSLog(@" old age is %@", [change objectForKey:@"old"]);
        NSLog(@" new age is %@", [change objectForKey:@"new"]);
    }
    else if ([keyPath isEqualToString:@"information"])
    {
        Class classInfo = (Class)context;
        NSString * className = [NSString stringWithCString:object_getClassName(classInfo)
                                                  encoding:NSUTF8StringEncoding];
        NSLog(@" >> class: %@, Information changed", className);
        NSLog(@" old information is %@", [change objectForKey:@"old"]);
        NSLog(@" new information is %@", [change objectForKey:@"new"]);
    }
    else
    {
        [super observeValueForKeyPath:keyPath
                             ofObject:object
                               change:change
                              context:context];
    }
}

2,實現依賴鍵

在這裡,觀察的是 TargetWrapper 類的 information 屬性,該屬性是依賴於 Target 類的 age 和 grade 屬性。為此,我在 Target 中添加了 grade 屬性:

@interface Target : NSObject
@property (nonatomic, readwrite) int grade;
@property (nonatomic, readwrite) int age;
@end

@implementation Target
@synthesize age; // for automatic KVO - age@synthesize grade;
@end

下面來看看 TragetWrapper 中的依賴鍵屬性是如何實現的。

@class Target;

@interface TargetWrapper : NSObject
{
@private
    Target * _target;
}

@property(nonatomic, assign) NSString * information;
@property(nonatomic, retain) Target * target;

-(id) init:(Target *)aTarget;

@end

#import "TargetWrapper.h"
#import "Target.h"

@implementation TargetWrapper

@synthesize target = _target;

-(id) init:(Target *)aTarget
{
    self = [super init];
    if (nil != self) {
        _target = [aTarget retain];
    }
    
    return self;
}

-(void) dealloc
{
    self.target = nil;
    [super dealloc];
}

- (NSString *)information
{
    return [[[NSString alloc] initWithFormat:@"%d#%d", [_target grade], [_target age]] autorelease];
}

- (void)setInformation:(NSString *)theInformation
{
    NSArray * array = [theInformation componentsSeparatedByString:@"#"];
    [_target setGrade:[[array objectAtIndex:0] intValue]];
    [_target setAge:[[array objectAtIndex:1] intValue]];
}

+ (NSSet *)keyPathsForValuesAffectingInformation
{
    NSSet * keyPaths = [NSSet setWithObjects:@"target.age", @"target.grade", nil];
    return keyPaths;
}

//+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
//{
//    NSSet * keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
//    NSArray * moreKeyPaths = nil;
////    if ([key isEqualToString:@"information"])
//    {
//        moreKeyPaths = [NSArray arrayWithObjects:@"target.age", @"target.grade", nil];
//    }
////    if (moreKeyPaths)
//    {
//        keyPaths = [keyPaths setByAddingObjectsFromArray:moreKeyPaths];
//    }
////    return keyPaths;
//}
@end

首先,要手動實現屬性 information 的 setter/getter 方法,在其中使用 Target 的屬性來完成其 setter 和 getter。

其次,要實現 keyPathsForValuesAffectingInformation  或 keyPathsForValuesAffectingValueForKey: 方法來告訴系統 information 屬性依賴於哪些其他屬性,這兩個方法都返回一個key-path 的集合。在這裡要多說幾句,如果選擇實現 keyPathsForValuesAffectingValueForKey,要先獲取 super 返回的結果 set,然後判斷 key 是不是目標 key,如果是就將依賴屬性的 key-path 結合追加到 super 返回的結果 set 中,否則直接返回 super的結果。

在這裡,information 屬性依賴於 target 的 age 和 grade 屬性,target 的 age/grade 屬性任一發生變化,information 的觀察者都會得到通知。

3,使用示例:

Observer * observer = [[[Observer alloc] init] autorelease];
Target * target = [[[Target alloc] init] autorelease];

TargetWrapper * wrapper = [[[TargetWrapper alloc] init:target] autorelease];
[wrapper addObserver:observer
          forKeyPath:@"information"
             options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
             context:[TargetWrapper class]];

[target setAge:30];
[target setGrade:1];
[wrapper removeObserver:observer forKeyPath:@"information"];

輸出結果:

 >> class: TargetWrapper, Information changed

  old information is 0#10

  new information is 0#30

  >> class: TargetWrapper, Information changed

  old information is 0#30

  new information is 1#30

六,鍵值觀察是如何實現的

1,實現機理

鍵值觀察用處很多,Core Binding 背後的實現就有它的身影,那鍵值觀察背後的實現又如何呢?想一想在上面的自動實現方式中,我們並不需要在被觀察物件 Target 中新增額外的程式碼,就能獲得鍵值觀察的功能,這很好很強大,這是怎麼做到的呢?答案就是 Objective C 強大的 runtime 動態能力,下面我們一起來窺探下其內部實現過程。

當某個類的物件第一次被觀察時,系統就會在執行期動態地建立該類的一個派生類,在這個派生類中重寫基類中任何被觀察屬性的 setter 方法。

派生類在被重寫的 setter 方法實現真正的通知機制,就如前面手動實現鍵值觀察那樣。這麼做是基於設定屬性會呼叫 setter 方法,而通過重寫就獲得了 KVO 需要的通知機制。當然前提是要通過遵循 KVO 的屬性設定方式來變更屬性值,如果僅是直接修改屬性對應的成員變數,是無法實現 KVO 的。

同時派生類還重寫了 class 方法以“欺騙”外部呼叫者它就是起初的那個類。然後系統將這個物件的 isa 指標指向這個新誕生的派生類,因此這個物件就成為該派生類的物件了,因而在該物件上對 setter 的呼叫就會呼叫重寫的 setter,從而啟用鍵值通知機制。此外,派生類還重寫了 dealloc 方法來釋放資源。

如果你對類和物件的關係不太明白,請閱讀《深入淺出Cocoa之類與物件》;如果你對如何動態建立類不太明白,請閱讀《深入淺出Cocoa 之動態建立類》。

蘋果官方文件說得很簡潔:

Key-Value Observing Implementation Details

Automatic key-value observing is implemented using a technique called isa-swizzling.

The isa pointer, as the name suggests, points to the object's class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data.

When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance.

You should never rely on the isa pointer to determine class membership. Instead, you should use the class method to determine the class of an object instance.

2,程式碼分析

由於派生類中被重寫的 class 對我們撒謊(它說它就是起初的基類),我們只有通過呼叫 runtime 函式才能揭開派生類的真面目。 下面來看  Mike Ash 的程式碼:

首先是帶有 x, y, z 三個屬性的觀察目標 Foo:

@interface Foo : NSObject
{
    int x;
    int y;
    int z;
}

@property int x;
@property int y;
@property int z;

@end

@implementation Foo
@synthesize x, y, z;
@end

下面是檢驗程式碼:

#import <objc/runtime.h>

static NSArray * ClassMethodNames(Class c)
{
    NSMutableArray * array = [NSMutableArray array];
    
    unsigned int methodCount = 0;
    Method * methodList = class_copyMethodList(c, &methodCount);
    unsigned int i;
    for(i = 0; i < methodCount; i++) {
        [array addObject: NSStringFromSelector(method_getName(methodList[i]))];
    }

    free(methodList);
    
    return array;
}

static void PrintDescription(NSString * name, id obj)
{
    NSString * str = [NSString stringWithFormat:
                      @"\n\t%@: %@\n\tNSObject class %s\n\tlibobjc class %s\n\timplements methods <%@>",
                      name,
                      obj,
                      class_getName([obj class]),
                      class_getName(obj->isa),
                      [ClassMethodNames(obj->isa) componentsJoinedByString:@", "]];
    NSLog(@"%@", str);
}

int main (int argc, const char * argv[])
{
    @autoreleasepool {
        // Deep into KVO: [email protected]
        //
        Foo * anything = [[Foo alloc] init];
        Foo * x = [[Foo alloc] init];
        Foo * y = [[Foo alloc] init];
        Foo * xy = [[Foo alloc] init];
        Foo * control = [[Foo alloc] init];
        
        [x addObserver:anything forKeyPath:@"x" options:0 context:NULL];
        [y addObserver:anything forKeyPath:@"y" options:0 context:NULL];
        
        [xy addObserver:anything forKeyPath:@"x" options:0 context:NULL];
        [xy addObserver:anything forKeyPath:@"y" options:0 context:NULL];
        
        PrintDescription(@"control", control);
        PrintDescription(@"x", x);
        PrintDescription(@"y", y);
        PrintDescription(@"xy", xy);
        
        NSLog(@"\n\tUsing NSObject methods, normal setX: is %p, overridden setX: is %p\n",
               [control methodForSelector:@selector(setX:)],
               [x methodForSelector:@selector(setX:)]);
        NSLog(@"\n\tUsing libobjc functions, normal setX: is %p, overridden setX: is %p\n",
               method_getImplementation(class_getInstanceMethod(object_getClass(control),
                                                                @selector(setX:))),
               method_getImplementation(class_getInstanceMethod(object_getClass(x),
                                                                @selector(setX:))));
    }

    return 0;
}

在上面的程式碼中,輔助函式 ClassMethodNames 使用 runtime 函式來獲取類的方法列表,PrintDescription 列印物件的資訊,包括通過 -class 獲取的類名, isa 指標指向的類的名字以及其中方法列表。

 在這裡,我建立了四個物件,x 物件的 x 屬性被觀察,y 物件的 y 屬性被觀察,xy 物件的 x 和 y 屬性均被觀察,參照物件 control 沒有屬性被觀察。在程式碼的最後部分,分別通過兩種方式(物件方法和 runtime 方法)打印出引數物件 control 和被觀察物件 x 物件的 setX 方面的實現地址,來對比顯示正常情況下 setter 實現以及派生類中重寫的 setter 實現。 

編譯執行,輸出如下:

control: <Foo: 0x10010c980>

NSObject class Foo

libobjc class Foo

implements methods <x, setX:, y, setY:, z, setZ:>

x: <Foo: 0x10010c920>

NSObject class Foo

libobjc class NSKVONotifying_Foo

implements methods <setY:, setX:, class, dealloc, _isKVOA>

y: <Foo: 0x10010c940>

NSObject class Foo

libobjc class NSKVONotifying_Foo

implements methods <setY:, setX:, class, dealloc, _isKVOA>

xy: <Foo: 0x10010c960>

NSObject class Foo

libobjc class NSKVONotifying_Foo

implements methods <setY:, setX:, class, dealloc, _isKVOA>

Using NSObject methods, normal setX: is 0x100001df0, overridden setX: is 0x100001df0

Using libobjc functions, normal setX: is 0x100001df0, overridden setX: is 0x7fff8458e025

從上面的輸出可以看到,如果使用物件的 -class 方面輸出類名始終為:Foo,這是因為新誕生的派生類重寫了 -class 方法聲稱它就是起初的基類,只有使用 runtime 函式 object_getClass 才能一睹芳容:NSKVONotifying_Foo。注意看:x,y 以及 xy 三個被觀察物件真正的型別都是 NSKVONotifying_Foo,而且該類實現了:setY:, setX:, class, dealloc, _isKVOA 這些方法。其中 setX:, setY:, class 和 dealloc 前面已經講到過,私有方法 _isKVOA 估計是用來標示該類是一個 KVO 機制聲稱的類。在這裡 Objective C 做了一些優化,它對所有被觀察物件只生成一個派生類,該派生類實現所有被觀察物件的 setter 方法,這樣就減少了派生類的數量,提供了效率。所有 NSKVONotifying_Foo 這個派生類重寫了 setX,setY方法(留意:沒有必要重寫 setZ 方法)。

接著來看最後兩行輸出,地址 0x100001df0 是 Foo 類中的實現,而地址是 0x7fff8458e025 是派生類 NSKVONotifying_Foo 類中的實現。那後面那個地址到底是什麼呢?可以通過 GDB 的 info 命令加 symbol 引數來檢視該地址的資訊:

 (gdb) info symbol 0x7fff8458e025

_NSSetIntValueAndNotify in section LC_SEGMENT.__TEXT.__text of /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation

看起來它是 Foundation 框架提供的私有函式:_NSSetIntValueAndNotify。更進一步,我們來看看 Foundation 到底提供了哪些用於 KVO 的輔助函式。開啟 terminal,使用 nm -a 命令檢視 Foundation 中的資訊:

nm -a /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation

其中查詢到我們關注的函式:

00000000000233e7 t __NSSetDoubleValueAndNotify
00000000000f32ba t __NSSetFloatValueAndNotify
0000000000025025 t __NSSetIntValueAndNotify
000000000007fbb5 t __NSSetLongLongValueAndNotify
00000000000f33e8 t __NSSetLongValueAndNotify
000000000002d36c t __NSSetObjectValueAndNotify
0000000000024dc5 t __NSSetPointValueAndNotify
00000000000f39ba t __NSSetRangeValueAndNotify
00000000000f3aeb t __NSSetRectValueAndNotify
00000000000f3512 t __NSSetShortValueAndNotify
00000000000f3c2f t __NSSetSizeValueAndNotify
00000000000f363b t __NSSetUnsignedCharValueAndNotify
000000000006e91f t __NSSetUnsignedIntValueAndNotify
0000000000034b5b t __NSSetUnsignedLongLongValueAndNotify
00000000000f3766 t __NSSetUnsignedLongValueAndNotify
00000000000f3890 t __NSSetUnsignedShortValueAndNotify
00000000000f3060 t __NSSetValueAndNotifyForKeyInIvar
00000000000f30d7 t __NSSetValueAndNotifyForUndefinedKey

Foundation 提供了大部分基礎資料型別的輔助函式(Objective C中的 Boolean 只是 unsigned char 的 typedef,所以包括了,但沒有 C++中的 bool),此外還包括一些常見的 Cocoa 結構體如 Point, Range, Rect, Size,這表明這些結構體也可以用於自動鍵值觀察,但要注意除此之外的結構體就不能用於自動鍵值觀察了。對於所有 Objective C 物件對應的是 __NSSetObjectValueAndNotify 方法。

七,總結

KVO 並不是什麼新事物,換湯不換藥,它只是觀察者模式在 Objective C 中的一種運用,這是 KVO 的指導思想所在。其他語言實現中也有“KVO”,如 WPF 中的 binding。而在 Objective C 中又是通過強大的 runtime 來實現自動鍵值觀察的。至此,對 KVO 的使用以及注意事項,內部實現都介紹完畢,對 KVO 的理解又深入一層了。Objective 中的 KVO 雖然可以用,但卻非完美,有興趣的瞭解朋友請檢視《KVO 的缺陷》 以及改良實現 MAKVONotificationCenter 。

八,引用

深入淺出Cocoa之類與物件
  

相關推薦

利用KVO重新整理UITableViewKVO實現機理

一,前言 Objective-C 中的鍵(key)-值(value)觀察(KVO)並不是什麼新鮮事物,它來源於設計模式中的觀察者模式,其基本思想就是: 一個目標物件管理所有依賴於它的觀察者物件,並在它自身的狀態改變時主動通知觀察者物件。這個主動通知通常是通過呼叫各觀察者物件所提供的介面方法

利用c++的privatestatic實現單例模式

精髓就是 將建構函式設定為private屬性,並且將複製建構函式和賦值建構函式也設定為private屬性,這樣的話,就無法在外部建立物件,所以此時還需要一個public的函式:getHumanInterface(),這個函式來呼叫private屬性的建構函式來生成我們需要的物件,並且將這個物

利用Java庫函式自己實現的解析演算法來讀取X.509證書

目錄 Java自帶庫函式讀取X.509證書欄位 理解證書資料格式,然後解析 基本概念 演算法思路 核心步驟 Java自帶庫函式讀取X.509證書欄位  這個很簡單,直接上程式碼。讀取然後全部輸出,或者輸出對應欄位。 import java.securit

一點一點學maven(13)--利用maven的filterprofile實現不同環境使用不同的配製

在我們平常的java開發中,會經常使用到很多配製檔案(xxx.properties,xxx.xml),而當我們在本地開發(dev),測試環境測試(test),線上生產使用(product)時,需要不停的去修改這些配製檔案,次數一多,相當麻煩。現在,利用maven

利用cookie, session Filter實現簡單的自動登陸

需求: 1.當用戶請求主頁面時如果沒有登陸轉發到登陸介面 2.將使用者資訊存入到session中,賬號密碼存入cookie。 3.利用Filter過濾全域性檢測cookie,呼叫service實現登陸。 密碼加密,登陸,註冊頁面不能自動登陸 j

KVO實現機理,隨便解釋一下isa-swizzling

isa-swizzling isa, is a kind of swizzling, 混合,攪合。 KVO的基礎KVC。 KVC主要通過isa-swizzling,來實現。 [site setValue:@"sitename" forKey:@"name"]; 編

利用SharePrefrenceGson實現實體類的保存與獲取

getclass 類繼承 brush gets static 實現 highlight model htm 實現需要Gson獲者其他能將實體類轉換成json的jar包 接下來是主要方法: 保存實體類 public static void putClass(ModelBa

利用<object><embed>實現視頻播放

nbsp 是否 eba 名稱 height con com album min 直接使用<object>或<embed>都可以實現視頻播放,那麽兩者的區別是什麽? 1、是為了兼容不同瀏覽器,IE只支持對Object的解析;火狐,谷歌,Safari只支

手機商城第四天,利用GridViewviewpager實現頻道按鈕熱門活動圖片的展示

手機商城第四天 利用gridview和viewpager實現頻道按鈕和熱門活動圖片的展示 代碼已經上傳碼雲,有興趣的小夥伴可以下載看看: https://git.oschina.net/joy_yuan/ShoppingMall 下面是這次的效果圖:其中哪些服飾、遊戲、動漫等欄目

(轉)利用 SVG CSS3 實現有趣的邊框動畫

但是 cin 有一個 orm har arr edi 下載 嘗試 原文地址 今天我們來探索一下Carl Philipe Brenner的網站上一個微妙而有趣的動畫效果。當鼠標經過網格元素時,會有一個微妙的動畫發生——網格元素變得透明,每條邊有個順時針的動畫,創造了非常好的效

[C#][代碼收集] - 利用開源的TaskScheduler組件實現監控管理windows計劃任務

span releases [] log code str 示例 schedule 調度框架 [轉載] - 軟件人生 - 利用開源的TaskScheduler組件實現監控和管理windows計劃任務 [編輯] 對於計劃任務的執行有很多種解決方案,如利用開源Quartz作業調

利用inotifyrsync實現數據同步

幫助信息 delete 版本 rbo modify 啟用 gre 多個 roo 一.rsync是什麽? 它是Linux系統下文件同步可數據傳輸的工具,采用rsync算法使客戶機與服務器,主服務器與備份服務器數據同步。rsync也能實現中斷後恢復傳輸。rsync支持增量備份。

kvckvo的使用情況的了解

sele enter acc 組合 ets 字符串 exp ace map 了解cocoa:Cocoa是蘋果公司為Mac OS X所創建的原生面向對象的API,是M

Qt利用QLocalSocketQLocalServer實現IPC

handler delet qstring OS client 但是 main his windows系統 QLocalServer提供了一種基於本地套接字的服務器,實現了接收本地socket的連接的功能。 通過調用listen()監聽特定的連接,每次與client連接上時

C++實現利用(前序中序生成二叉樹)以及(二叉樹的鏡像)

lse pub 非遞歸 ace 方法 [] reorder spa push #include<iostream> #include<string.h> #include<stack> using namespace std; type

Java利用waitnotify實現執行緒間通訊

       Java的Object類提供了wait和notify方法用於實現執行緒間通訊(因為所有的java類都繼承了Object類,所以所有的java類都有這兩個方法)。這兩個方法在Object類中籤名如下: pu

利用python_opencvdlib實現從視訊中抓取人臉照片並儲存(親測有效)

系統:win10 編譯環境:pycharm python庫:cv2、dlib(自己安裝) 程式碼: import dlib import cv2 # 載入並初始化檢測器 detector = dlib.get_frontal_face_detector() camera = cv2.

利用AjaxJSON實現關於查詢省市名稱的二級聯動功能

  功能實現的思路:我們經常碰見網上購物時候填寫收件地址會用到這個查詢省市縣的三級聯動查詢功能,我們可以利用Ajax和JSON技術模擬這個功能,說白了同樣是使用Ajax的區域性資料更新功能這個特性。因為省市都會有很多個,所以還需要使用JSONArray物件。當我們選擇某一個省的時候,會自動觸發一個區域性更新功

利用floatmargin實現兩端對齊佈局

 實現效果如下圖: 這種佈局方式一般我們都會想到直接使用float浮動外加margin-right做右側留白,既實現多個小模組按順序排列, 超出一行又會自動換行,從而實現這個效果。但是,實現過程中會遇到一個問題就是最右邊的留白處理問題, 當然可以根據選擇每行會有多少個模組,設定:nth-.

根據字串的形式,自動匯入模組並使用反射找到模組中的類,並例項化物件,利用importlibgetattr實現

例如: auth資料夾下一個SCRF.py檔案,裡面有一個Cors類 class CORS(object): def process_request(self): print('666') auth資料