понедельник, 30 ноября 2020 г.

Как из старого Xcode запускать приложения на новых симуляторах и устройствах

На тот случай, если проект еще не портировался на последний Xcode, а ошибки в новых iOS уже есть и их надо отлаживать.

Скачиваем Xcode, который поддерживает требуемую версию iOS, устанавливаем.

Настраиваем отладку на новом симуляторе

sudo xcode-select --switch /Applications/Xcode11.4.app/Contents/Developer 

Смотрим список доступных симуляторов

xcrun simctl list

Запускаем тот который нужен 

xcrun simctl boot DEVICE_ID

ИсточникHow to Debug on Recent iOS Simulators and Devices Using Old Xcode?

четверг, 14 марта 2019 г.

performSelector с простыми типами

Старая тема, когда-то вроде сталкивался, но вот опять пришлось.

-(void)test:(NSString*)str number:(float)num{
    NSLog(@"str: %@, number: %lf", str, num);
}
float flt = 123.321;

performSelector:@selector(test:number:) withObject:@"@(float)" withObject:@(flt)];
    [self performSelector:@selector(test:number:) withObject:@"NSNumber(float)" withObject:[NSNumber numberWithFloat:flt]];
    [self performSelector:@selector(test:number:) withObject:@"NSValue(float)" withObject:[NSValue valueWithBytes:&flt objCType:@encode(float)]];

В консоли:
2019-03-14 18:32:25.777125+0300 testapp[25482:1642693] str: @(float), number: 0.000000
2019-03-14 18:32:25.777223+0300 testapp[25482:1642693] str: NSNumber(float), number: 0.000000
2019-03-14 18:32:25.777325+0300 testapp[25482:1642693] str: NSValue(float), number: 0.000000

Предлагают пользоваться NSInvocation, но у меня с полтычка не получилось

    NSMethodSignature *signature = [self methodSignatureForSelector:@selector(test:number:)];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    
    [invocation setTarget:self];
    [invocation setSelector:@selector(test:number:)];
    [invocation setArgument:&flt atIndex:2];
    
    [invocation invoke];

четверг, 28 февраля 2019 г.

Concurrent Versus Non-concurrent Operations

Хотя обычно вы выпоняетет операции добавляя их в очередь, так делать не обязательно. Так же возможно выполнять объект операции вручную, вызвав метод start, но такой способ не гарантирует, что операция выполнится параллельно с остальным вашим кодом. Метод isConcurrent в классе NSOperation говорит вам будет ли выполнена операция синхронно или асинхронно относительно потока, в котором вызван метод start. По умолчанию этот метод возвращает NO, что значит операция выполнится синхронно в вызывающем потоке.

Если вы хотите выполнить параллельную операцию - это которая выполняется асинхронно относительно вызывающего потока - вы должны написать дополнительный код чтобы запустить операцию асинхронно. Например, вы должны запустить отдельный поток, вызвать асинхронную системную функцию или сделать что-нибудь еще чтобы убедиться, что метод запустил задачу и вернулся мгновенно, по всей вероятности, до того, как задача будет завершена.

Most developers should never need to implement concurrent operation objects. If you always add your operations to an operation queue, you do not need to implement concurrent operations. When you submit a nonconcurrent operation to an operation queue, the queue itself creates a thread on which to run your operation. Thus, adding a nonconcurrent operation to an operation queue still results in the asynchronous execution of your operation object code. The ability to define concurrent operations is only necessary in cases where you need to execute the operation asynchronously without adding it to an operation queue.

Большинству разработчиков никогда не понадобится реализовывать параллельную операцию. Если вы всегда добавляете операцию в очередь операций, вам не нужно реализовывать параллельные операции. Когда вы добавляете в непараллельную операцию в очередь, сама очередь создает поток в котором выполняется операция. Таким образом, добавление непараллельной (непоследовательной) операции в очередь операций все еще приводит к асинхронному выполнению кода объекта операции. Возможность создавать параллельные операции нужно только в тех случаях, когда вам нужно выполнять операцию асинхронно, не добавляя ее в очередь операций.

https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationObjects/OperationObjects.html

Although you typically execute operations by adding them to an operation queue, doing so is not required. It is also possible to execute an operation object manually by calling its start method, but doing so does not guarantee that the operation runs concurrently with the rest of your code. The isConcurrent method of the NSOperation class tells you whether an operation runs synchronously or asynchronously with respect to the thread in which its start method was called. By default, this method returns NO, which means the operation runs synchronously in the calling thread.

If you want to implement a concurrent operation—that is, one that runs asynchronously with respect to the calling thread—you must write additional code to start the operation asynchronously. For example, you might spawn a separate thread, call an asynchronous system function, or do anything else to ensure that the start method starts the task and returns immediately and, in all likelihood, before the task is finished.

Most developers should never need to implement concurrent operation objects. If you always add your operations to an operation queue, you do not need to implement concurrent operations. When you submit a nonconcurrent operation to an operation queue, the queue itself creates a thread on which to run your operation. Thus, adding a nonconcurrent operation to an operation queue still results in the asynchronous execution of your operation object code. The ability to define concurrent operations is only necessary in cases where you need to execute the operation asynchronously without adding it to an operation queue.

For information about how to create a concurrent operation, see Configuring Operations for Concurrent Execution and NSOperation Class Reference.

Dispatch queues

Dispatch queues have other benefits:

  • They provide a straightforward and simple programming interface.
  • They offer automatic and holistic thread pool management.
  • They provide the speed of tuned assembly.
  • They are much more memory efficient (because thread stacks do not linger in application memory).
  • They do not trap to the kernel under load.
  • The asynchronous dispatching of tasks to a dispatch queue cannot deadlock the queue.
  • They scale gracefully under contention.
  • Serial dispatch queues offer a more efficient alternative to locks and other synchronization primitives.

Асинхронная NSOperation (Concurrent)

concurrent operation (параллельная операция) это объект операции, который не выполняет свою задачу в потоке, из которого был вызван его метод start. concurrent operation обычно устанавливает свой собственный поток или вызывает интерфейс, который устанавливает отдельный поток для выполнения работы.

Что надо переопределить обязательно:
  • start - для ручного запуска, надо будет его вызвать. Там должен засэтапиться thread или другое окружение для задачи. При этом не надо вызывать [super start] 
  • isConcurrent - надо переопределить и вернуть YES.
Что переопределять не обязательно, но можно:
  • main - можно переопределить просто для удобства или разделения кода. 
  • isReady - если требуется поддерживать зависимости
  • если меняется 
Далее пример из документации. Все кроме start и main

@interface MyOperation : NSOperation {
    BOOL        executing;
    BOOL        finished;
}
- (void)completeOperation;
@end

@implementation MyOperation
- (id)init {
    self = [super init];
    if (self) {
        executing = NO;
        finished = NO;
    }
    return self;
}

- (BOOL)isConcurrent {
    return YES;
}

- (BOOL)isExecuting {
    return executing;
}

- (BOOL)isFinished {
    return finished;
}
@end



Что из себя представляет start. Не вызываем super, но вызываем main.

- (void)start {
   // Always check for cancellation before launching the task.
   if ([self isCancelled])
   {
      // Must move the operation to the finished state if it is canceled.
      [self willChangeValueForKey:@"isFinished"];
      finished = YES;
      [self didChangeValueForKey:@"isFinished"];
      return;
   }

   // If the operation is not canceled, begin executing the task.
   [self willChangeValueForKey:@"isExecuting"];
   [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
   executing = YES;
   [self didChangeValueForKey:@"isExecuting"];
}

Теперь main и дополнительно метод completeOperation.

- (void)main {
   @try {

       // Do the main work of the operation here.

       [self completeOperation];
   }
   @catch(...) {
      // Do not rethrow exceptions.
   }
}

- (void)completeOperation {
    [self willChangeValueForKey:@"isFinished"];
    [self willChangeValueForKey:@"isExecuting"];

    executing = NO;
    finished = YES;

    [self didChangeValueForKey:@"isExecuting"];
    [self didChangeValueForKey:@"isFinished"];

}

Важное дополнение:
Даже если операция отменена, вы всегда должны уведомлять наблюдателей KVO о том, что ваша операция завершена. Когда объект операции зависит от завершения других объектов операции, он отслеживает путь ключа isFinished для этих объектов. Только когда все объекты сообщают, что они завершены, зависимая операция сигнализирует, что она готова к запуску. Поэтому невозможность генерировать уведомление о завершении может помешать выполнению других операций в вашем приложении.

Even if an operation is canceled, you should always notify KVO observers that your operation is now finished with its work. When an operation object is dependent on the completion of other operation objects, it monitors the isFinished key path for those objects. Only when all objects report that they are finished does the dependent operation signal that it is ready to run. Failing to generate a finish notification can therefore prevent the execution of other operations in your application.

Синхронная NSOperation (Non-concurrent)

Все достаточно просто.
Главное переопределить main. После этого можно добавлять операция в очередь. Далее:
- очередь вызовет start из другого потока
- из start вызовется main

Когда завершится main, все корректно выгрузится.
В main можно периодически проверять isCancelled, если надо отреагировать на отмену и завершиться раньше.

@interface TSyncOperation : NSOperation

@property (nonatomic, assign) NSInteger maxCount;
@property (nonatomic, assign) NSTimeInterval delay;

@end


@implementation TSyncOperation

-(id)initWithMaxCount:(NSInteger)maxCount delay:(NSTimeInterval)delay{
    self = [super init];
    if( self ){
        self.maxCount = maxCount;
        self.delay = delay<=0 ? 1.0 : delay;
    }
    return self;
}

-(void)start{
    TestLog(@"start begin");
    // have to call of super, or do not override start
    [super start];
    TestLog(@"start finish");
}

-(void)main{
    TestLog(@"main begin");
    for( int i=0; i< self.maxCount && self.isCancelled==NO; ++i ){
        TestLog(@"count = %i", i);
        sleep(self.delay);
    }
    TestLog(@"main finish");
}

@end

performSelector: withObject: afterDelay:

- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;

Прикольная тема, казалось бы можно просто делать из любого потока, например так:

-(void)callRecurtion{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self recurtion:@(0)];
    });
}

-(void)recurtion:(NSNumber*)counter{
    
    if( counter.integerValue < self.maxCount ){
        TestLog(@"recurtion count = %@", counter);
        counter = @(counter.integerValue+1);
        [self performSelector:@selector(recurtion:) withObject:counter afterDelay:self.delay];
    }
}

Но эта штука сэтапит таймер, который просто так не выполняется в произвольной очереди.