2013-03-31 3 views
16

사용자 지정 개체가 있으며 NSObject에서 상속합니다. 이 객체는 "몇 가지"를 수행합니다. 그 중 하나는 UIKit 객체 (UILabel, UIButtons ecc ecc ...)가있는 UIView를 만드는 것입니다. 이 객체에는 포함 된 UIKit 객체의 모양을 사용자 정의하는 데 사용되는 textColor, font, backgroundColor ...와 같은 몇 가지 속성이 있습니다.사용자 지정 개체에 대한 UIA 유사 프록시

이 개체의 모든 생성 된 인스턴스에 대해이 속성을 "원샷"으로 사용자 지정하고 UIAppearance 프로토콜을 살펴 보았습니다.

표준 UIKit 객체는 이미 UIAppearance 프로토콜을 준수하지만 모든 UILabels 또는 UIButton에 스타일을 적용하고 싶지 않습니다. 내 개체 인스턴스 안에 포함 된 UILabels 및 UIButtons에만 스타일을 적용하고 싶습니다. 또한, 사용자 정의 개체를 사용하는 개발자가 그 안에 "포함 된"개체의 종류를 알지 못하기 때문에 appearanceWhenContainedIn을 사용할 수 없습니다 (그리고 나는 원하지 않습니다).

그래서, 내 사용자 지정 개체를 UIAppearance 프로토콜을 따르는 방법을 살펴 보았습니다.

AFAIK 그것은

+ (id)appearance 

방법을 구현해야합니다. 이 메서드는 모든 사용자 지정을 보낼 수있는 프록시 개체를 반환해야합니다. 그러나 UIKit 객체의 모양 메서드를 살펴보면 개인 객체가 반환되는 것을 알 수 있습니다. _UIAppearance 클래스의 객체입니다.

그래서 내 생각에 애플은 내 자신을 커스터마이징하기위한 표준 프록시 객체를 제공하지 않는다. 맞습니까, 아니면 무언가를 잃어 가고 있습니까?

감사

답변

14

일부 재 작업 후 표준 Apple 객체 사용에 대해 "포기"합니다. 현재 존재하지 않습니다. 나 자신의 프록시를 만들었는데, 이것은 매우 간단합니다 (지금까지는 "외양 :"으로 만 작동합니다).

설명해 드리겠습니다. NSObject 하위 클래스에서 "textColor"모양을 설정하려면 "FLObject"라고합시다. FLObject를 UIAppearance 프로토콜에 따르고 모양 메서드를 재정의합니다. 작동 원리

+ (id)appearance 
{ 
    return [FLAppearance appearanceForClass:[self class]]; 
} 

:이 방법에서는 , 당신은 프록시 클래스 (내가 만든 하나)을 반환해야합니까? FLAppearance는 appearanceForClass : 메서드에서 전달한 각 클래스에 대해 자체의 단일 인스턴스를 만듭니다. 동일한 클래스에 대해 두 번 호출하면 동일한 인스턴스가 반환됩니다.

그런 다음 같은 것을 할 수 있습니다 : 그것은 보낸 모든 방법을 받아들이도록 방법 :

[[FLObject appearance] setTextColor:[UIColor redColor]]; 

FLAppearance는 forwardInvocation 우선합니다. 그런 다음 모든 호출을 배열에 넣습니다. FLObject 초기화하면 ,

[(FLAppearance *)[FLAppearance appearanceForClass:[self class]] startForwarding:self]; 

에 대한 간단한 호출은 호출을 전송하고 모양을 설정하기 시작합니다. 물론, 약간의 튜닝과 오류 검사가 필요하지만, 좋은 시작이라고 생각합니다.

@interface FLAppearance() 

@property (strong, nonatomic) Class mainClass; 
@property (strong, nonatomic) NSMutableArray *invocations; 

@end 

static NSMutableDictionary *dictionaryOfClasses = nil; 

@implementation FLAppearance 

// this method return the same object instance for each different class 
+ (id) appearanceForClass:(Class)thisClass 
{ 
    // create the dictionary if not exists 
    // use a dispatch to avoid problems in case of concurrent calls 
    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
     if (!dictionaryOfClasses) 
      dictionaryOfClasses = [[NSMutableDictionary alloc]init]; 
    }); 



    if (![dictionaryOfClasses objectForKey:NSStringFromClass(thisClass)]) 
    { 
     id thisAppearance = [[self alloc]initWithClass:thisClass]; 
     [dictionaryOfClasses setObject:thisAppearance forKey:NSStringFromClass(thisClass)]; 
     return thisAppearance; 
    } 
    else 
     return [dictionaryOfClasses objectForKey:NSStringFromClass(thisClass)]; 
} 

- (id)initWithClass:(Class)thisClass 
{ 
    self = [self initPrivate]; 
    if (self) { 
     self.mainClass = thisClass; 
     self.invocations = [NSMutableArray array]; 
    } 
    return self; 
} 

- (id)init 
{ 
    [NSException exceptionWithName:@"InvalidOperation" reason:@"Cannot invoke init. Use appearanceForClass: method" userInfo:nil]; 
    return nil; 
} 

- (id)initPrivate 
{ 
    if (self = [super init]) { 

    } 
    return self; 
} 

-(void)forwardInvocation:(NSInvocation *)anInvocation; 
{ 
    // tell the invocation to retain arguments 
    [anInvocation retainArguments]; 

    // add the invocation to the array 
    [self.invocations addObject:anInvocation]; 
} 

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { 
    return [self.mainClass instanceMethodSignatureForSelector:aSelector]; 
} 

-(void)startForwarding:(id)sender 
{ 
    for (NSInvocation *invocation in self.invocations) { 
     [invocation setTarget:sender]; 
     [invocation invoke]; 
    } 
} 
+0

잘 작동합니다. 클래스 계층 구조를 확장하여 부모 클래스의 옵션도 설정할 수 있도록 확장했습니다. https://gist.github.com/vkodocha/5500276 (코드는 인라인을 추가하는 데 길어집니다). – Chris

+0

멋진 작품! 개선 사항으로'id' 대신에'instancetype'을 리턴 타입으로 사용할 수 있습니다. 따라서 명시 적 캐스트를 건너 뛸 수 있습니다. 'FLAppearance'. –

+0

@GabrielePetronella 힌트를 보내 주셔서 감사합니다 .-) – LombaX

0

체크 아웃 http://logicalthought.co/blog/2012/10/8/uiappearance-and-custom-views

은 기본적으로 그냥 UI_APPEARANCE_SELECTOR하여 속성 태그를 추가해야 모든 것이 한 클래스는 개인 _UIAppearance 클래스의 실제 자동 판매기를 처리 할 UIView의 서브 클래스로 작동 .


편집 :

당신은 아마 떨어져 그냥 자신의 싱글을 사용하여 솔루션 및 일부 클래스 메소드 롤링보다는 런타임 무서운 뭔가를 시도하는 것이 더 낫다. 귀하의 유스 케이스를 지원하는 것 같지 않은 UIAppearance입니다.

반면에 사용자가 판매 한 각 객체를 UIView 전용 하위 클래스에 붙인 다음 해당 하위 클래스의 인스턴스를 대신 판매 할 수 있습니다. 그런 다음 NSObject으로 보낸 모양 메시지를 구입 한 인스턴스로 전달하고 appearanceWhenContainedIn:<your private subclass>을 사용할 수 있습니다. 그것은 지저분해질 수 있고 당신의 종류의 소비자를 혼동시킬 수 있습니다.

+0

감사합니다. UIView 하위 클래스에서이를 사용하는 방법을 알고 있습니다. 내가 지정한대로 NSObject 하위 클래스에 UIAppearance를 구현하고 싶습니다. – LombaX

+0

내 편집을 참조하십시오. 그게 전혀 도움이 되나요? –

+0

나는 UIAppearance 프로토콜과 호환되는 내 자신의 프록시를 만들 것을 선택했습니다. 그것은 생각보다 쉽습니다 :-) 나는 코드를 대답에 추가했습니다. – LombaX

2

니스 구현, 나는 약간의 코드를 수정 NSProxy의 서브 클래스로 클래스를 만들었습니다. 프로젝트에서 그것을 사용하여 메모리 누수가 발견되었습니다 :

예 : 프록시를 사용하여 전역 설정/모양을 설정하면 해당 클래스의 각 인스턴스는 refCount 0에 도달하지 않으므로 dealloc은 절대로 호출되지 않습니다.

누출 코드 :

-(void)forwardInvocation:(NSInvocation *)anInvocation; 
{ 
    [...] 

    // !! This will retain also the target 

    [anInvocation retainArguments]; 

    [...] 
} 

가 수정 :

-(void)forwardInvocation:(NSInvocation *)anInvocation 
{ 
    [anInvocation setTarget:nil]; 
    [anInvocation retainArguments]; 

    // add the invocation to the array 
    [self.invocations addObject:anInvocation]; 
} 

-(void)startForwarding:(id)sender 
{ 
    for (NSInvocation *invocation in self.invocations) { 

     // Create a new copy of the stored invocation, 
     // otherwise setting the new target, this will never be released 
     // because the invocation in the array is still alive after the call 

     NSInvocation *targetInvocation = [invocation copy]; 
     [targetInvocation setTarget:sender]; 
     [targetInvocation invoke]; 
     targetInvocation = nil; 
    } 
} 

복사있는 NSInvocation

에 대한 카테고리 내 자신의 프로젝트의 목적
-(id)copy 
{ 
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self methodSignature]]; 
    NSUInteger numberOfArguments = [[self methodSignature] numberOfArguments]; 

    [invocation setTarget:self.target]; 
    [invocation setSelector:self.selector]; 

    if (numberOfArguments > 2) { 
     for (int i = 0; i < (numberOfArguments - 2); i++) { 
      char buffer[sizeof(intmax_t)]; 
      [self getArgument:(void *)&buffer atIndex:i + 2]; 
      [invocation setArgument:(void *)&buffer atIndex:i + 2]; 
     } 
    } 

    return invocation; 
} 
+1

답은 startForwarding에서 약간의 실수가 있습니다. NSInvocation * targetInvocation = [invocation copy]; [targetInvocation setTarget : sender]; [targetInvocation invoke]; targetInvocation = nil; – mientus

4

, 내가 모든 것을 함께하고 발표 정의 UIApperance를 수집 오픈 소스 프로젝트로서의 프록시 MZApperance